go get golang.org/x/tools@master
 go mod tidy
 go mod vendor

in both cmd and src, for (enforced) consistency.

Also: GOWORK=off go generate -run=bundle std

This will enable use of modernize and inline.

Change-Id: I6348dd97ec2c41437b3ca899ed91f10815f2fe26
Reviewed-on: https://go-review.googlesource.com/c/go/+/707135
Reviewed-by: Michael Matloob <matloob@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Alan Donovan 2025-09-26 13:03:12 -04:00 committed by Gopher Robot
parent 45eee553e2
commit 6cbe0920c4
70 changed files with 1600 additions and 1070 deletions

View file

@ -6,16 +6,16 @@ require (
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5
golang.org/x/arch v0.20.1-0.20250808194827-46ba08e3ae58
golang.org/x/build v0.0.0-20250806225920-b7c66c047964
golang.org/x/mod v0.28.0
golang.org/x/mod v0.29.0
golang.org/x/sync v0.17.0
golang.org/x/sys v0.36.0
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053
golang.org/x/sys v0.37.0
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8
golang.org/x/term v0.34.0
golang.org/x/tools v0.37.1-0.20250924232827-4df13e317ce4
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5
)
require (
github.com/ianlancetaylor/demangle v0.0.0-20250417193237-f615e6bd150b // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/text v0.30.0 // indirect
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect
)

View file

@ -10,19 +10,19 @@ golang.org/x/arch v0.20.1-0.20250808194827-46ba08e3ae58 h1:uxPa6+/WsUfzikIAPMqpT
golang.org/x/arch v0.20.1-0.20250808194827-46ba08e3ae58/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053 h1:dHQOQddU4YHS5gY33/6klKjq7Gp3WwMyOXGNp5nzRj8=
golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053/go.mod h1:+nZKN+XVh4LCiA9DV3ywrzN4gumyCnKjau3NGb9SGoE=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.37.1-0.20250924232827-4df13e317ce4 h1:IcXDtHggZZo+GzNzvVRPyNFLnOc2/Z1gg3ZVIWF2uCU=
golang.org/x/tools v0.37.1-0.20250924232827-4df13e317ce4/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5 h1:cz7f45KGWAtyIrz6bm45Gc+lw8beIxBSW3EQh4Bwbg4=
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=

View file

@ -41,6 +41,15 @@ func (s *CPUSet) Zero() {
clear(s[:])
}
// Fill adds all possible CPU bits to the set s. On Linux, [SchedSetaffinity]
// will silently ignore any invalid CPU bits in [CPUSet] so this is an
// efficient way of resetting the CPU affinity of a process.
func (s *CPUSet) Fill() {
for i := range s {
s[i] = ^cpuMask(0)
}
}
func cpuBitsIndex(cpu int) int {
return cpu / _NCPUBITS
}

View file

@ -23,7 +23,5 @@ func (fds *FdSet) IsSet(fd int) bool {
// Zero clears the set fds.
func (fds *FdSet) Zero() {
for i := range fds.Bits {
fds.Bits[i] = 0
}
clear(fds.Bits[:])
}

View file

@ -111,9 +111,7 @@ func (ifr *Ifreq) SetUint32(v uint32) {
// clear zeroes the ifreq's union field to prevent trailing garbage data from
// being sent to the kernel if an ifreq is reused.
func (ifr *Ifreq) clear() {
for i := range ifr.raw.Ifru {
ifr.raw.Ifru[i] = 0
}
clear(ifr.raw.Ifru[:])
}
// TODO(mdlayher): export as IfreqData? For now we can provide helpers such as

View file

@ -49,6 +49,7 @@ esac
if [[ "$GOOS" = "linux" ]]; then
# Use the Docker-based build system
# Files generated through docker (use $cmd so you can Ctl-C the build or run)
set -e
$cmd docker build --tag generate:$GOOS $GOOS
$cmd docker run --interactive --tty --volume $(cd -- "$(dirname -- "$0")/.." && pwd):/build generate:$GOOS
exit

View file

@ -801,9 +801,7 @@ func (sa *SockaddrPPPoE) sockaddr() (unsafe.Pointer, _Socklen, error) {
// one. The kernel expects SID to be in network byte order.
binary.BigEndian.PutUint16(sa.raw[6:8], sa.SID)
copy(sa.raw[8:14], sa.Remote)
for i := 14; i < 14+IFNAMSIZ; i++ {
sa.raw[i] = 0
}
clear(sa.raw[14 : 14+IFNAMSIZ])
copy(sa.raw[14:], sa.Dev)
return unsafe.Pointer(&sa.raw), SizeofSockaddrPPPoX, nil
}

View file

@ -248,6 +248,23 @@ func Statvfs(path string, buf *Statvfs_t) (err error) {
return Statvfs1(path, buf, ST_WAIT)
}
func Getvfsstat(buf []Statvfs_t, flags int) (n int, err error) {
var (
_p0 unsafe.Pointer
bufsize uintptr
)
if len(buf) > 0 {
_p0 = unsafe.Pointer(&buf[0])
bufsize = unsafe.Sizeof(Statvfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := Syscall(SYS_GETVFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
n = int(r0)
if e1 != 0 {
err = e1
}
return
}
/*
* Exposed directly
*/

View file

@ -321,6 +321,8 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys SetConsoleOutputCP(cp uint32) (err error) = kernel32.SetConsoleOutputCP
//sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW
//sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW
//sys GetNumberOfConsoleInputEvents(console Handle, numevents *uint32) (err error) = kernel32.GetNumberOfConsoleInputEvents
//sys FlushConsoleInputBuffer(console Handle) (err error) = kernel32.FlushConsoleInputBuffer
//sys resizePseudoConsole(pconsole Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole
//sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot
//sys Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW

View file

@ -65,6 +65,22 @@ var signals = [...]string{
15: "terminated",
}
// File flags for [os.OpenFile]. The O_ prefix is used to indicate
// that these flags are specific to the OpenFile function.
const (
O_FILE_FLAG_OPEN_NO_RECALL = FILE_FLAG_OPEN_NO_RECALL
O_FILE_FLAG_OPEN_REPARSE_POINT = FILE_FLAG_OPEN_REPARSE_POINT
O_FILE_FLAG_SESSION_AWARE = FILE_FLAG_SESSION_AWARE
O_FILE_FLAG_POSIX_SEMANTICS = FILE_FLAG_POSIX_SEMANTICS
O_FILE_FLAG_BACKUP_SEMANTICS = FILE_FLAG_BACKUP_SEMANTICS
O_FILE_FLAG_DELETE_ON_CLOSE = FILE_FLAG_DELETE_ON_CLOSE
O_FILE_FLAG_SEQUENTIAL_SCAN = FILE_FLAG_SEQUENTIAL_SCAN
O_FILE_FLAG_RANDOM_ACCESS = FILE_FLAG_RANDOM_ACCESS
O_FILE_FLAG_NO_BUFFERING = FILE_FLAG_NO_BUFFERING
O_FILE_FLAG_OVERLAPPED = FILE_FLAG_OVERLAPPED
O_FILE_FLAG_WRITE_THROUGH = FILE_FLAG_WRITE_THROUGH
)
const (
FILE_READ_DATA = 0x00000001
FILE_READ_ATTRIBUTES = 0x00000080

View file

@ -238,6 +238,7 @@ var (
procFindResourceW = modkernel32.NewProc("FindResourceW")
procFindVolumeClose = modkernel32.NewProc("FindVolumeClose")
procFindVolumeMountPointClose = modkernel32.NewProc("FindVolumeMountPointClose")
procFlushConsoleInputBuffer = modkernel32.NewProc("FlushConsoleInputBuffer")
procFlushFileBuffers = modkernel32.NewProc("FlushFileBuffers")
procFlushViewOfFile = modkernel32.NewProc("FlushViewOfFile")
procFormatMessageW = modkernel32.NewProc("FormatMessageW")
@ -284,6 +285,7 @@ var (
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeServerProcessId = modkernel32.NewProc("GetNamedPipeServerProcessId")
procGetNumberOfConsoleInputEvents = modkernel32.NewProc("GetNumberOfConsoleInputEvents")
procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult")
procGetPriorityClass = modkernel32.NewProc("GetPriorityClass")
procGetProcAddress = modkernel32.NewProc("GetProcAddress")
@ -2111,6 +2113,14 @@ func FindVolumeMountPointClose(findVolumeMountPoint Handle) (err error) {
return
}
func FlushConsoleInputBuffer(console Handle) (err error) {
r1, _, e1 := syscall.SyscallN(procFlushConsoleInputBuffer.Addr(), uintptr(console))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func FlushFileBuffers(handle Handle) (err error) {
r1, _, e1 := syscall.SyscallN(procFlushFileBuffers.Addr(), uintptr(handle))
if r1 == 0 {
@ -2481,6 +2491,14 @@ func GetNamedPipeServerProcessId(pipe Handle, serverProcessID *uint32) (err erro
return
}
func GetNumberOfConsoleInputEvents(console Handle, numevents *uint32) (err error) {
r1, _, e1 := syscall.SyscallN(procGetNumberOfConsoleInputEvents.Addr(), uintptr(console), uintptr(unsafe.Pointer(numevents)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetOverlappedResult(handle Handle, overlapped *Overlapped, done *uint32, wait bool) (err error) {
var _p0 uint32
if wait {

View file

@ -326,11 +326,3 @@ func parseStackPCs(crash string) ([]uintptr, error) {
}
return pcs, nil
}
func min(x, y int) int {
if x < y {
return x
} else {
return y
}
}

View file

@ -17,10 +17,26 @@ import (
const help = `PROGNAME is a tool for static analysis of Go programs.
PROGNAME examines Go source code and reports suspicious constructs,
such as Printf calls whose arguments do not align with the format
string. It uses heuristics that do not guarantee all reports are
genuine problems, but it can find errors not caught by the compilers.
PROGNAME examines Go source code and reports diagnostics for
suspicious constructs or opportunities for improvement.
Diagnostics may include suggested fixes.
An example of a suspicious construct is a Printf call whose arguments
do not align with the format string. Analyzers may use heuristics that
do not guarantee all reports are genuine problems, but can find
mistakes not caught by the compiler.
An example of an opportunity for improvement is a loop over
strings.Split(doc, "\n"), which may be replaced by a loop over the
strings.SplitSeq iterator, avoiding an array allocation.
Diagnostics in such cases may report non-problems,
but should carry fixes that may be safely applied.
For analyzers of the first kind, use "go vet -vettool=PROGRAM"
to run the tool and report diagnostics.
For analyzers of the second kind, use "go fix -fixtool=PROGRAM"
to run the tool and apply the fixes it suggests.
`
// Help implements the help subcommand for a multichecker or unitchecker
@ -29,7 +45,7 @@ genuine problems, but it can find errors not caught by the compilers.
func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
// No args: show summary of all analyzers.
if len(args) == 0 {
fmt.Println(strings.Replace(help, "PROGNAME", progname, -1))
fmt.Println(strings.ReplaceAll(help, "PROGNAME", progname))
fmt.Println("Registered analyzers:")
fmt.Println()
sort.Slice(analyzers, func(i, j int) bool {

View file

@ -13,9 +13,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "appends",
Doc: analysisutil.MustExtractDoc(doc, "appends"),
Doc: analysisinternal.MustExtractDoc(doc, "appends"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -19,7 +19,7 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/internal/analysisinternal"
)
const Doc = "report mismatches between assembly files and Go declarations"
@ -175,7 +175,7 @@ func run(pass *analysis.Pass) (any, error) {
Files:
for _, fname := range sfiles {
content, tf, err := analysisutil.ReadFile(pass, fname)
content, tf, err := analysisinternal.ReadFile(pass, fname)
if err != nil {
return nil, err
}
@ -211,7 +211,7 @@ Files:
resultStr = "result register"
}
for _, line := range retLine {
pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr)
pass.Reportf(tf.LineStart(line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr)
}
}
retLine = nil
@ -227,7 +227,7 @@ Files:
lineno++
badf := func(format string, args ...any) {
pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
pass.Reportf(tf.LineStart(lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
}
if arch == "" {

View file

@ -17,9 +17,11 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -27,26 +29,26 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "assign",
Doc: analysisutil.MustExtractDoc(doc, "assign"),
Doc: analysisinternal.MustExtractDoc(doc, "assign"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
)
nodeFilter := []ast.Node{
(*ast.AssignStmt)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
stmt := n.(*ast.AssignStmt)
for curAssign := range inspect.Root().Preorder((*ast.AssignStmt)(nil)) {
stmt := curAssign.Node().(*ast.AssignStmt)
if stmt.Tok != token.ASSIGN {
return // ignore :=
continue // ignore :=
}
if len(stmt.Lhs) != len(stmt.Rhs) {
// If LHS and RHS have different cardinality, they can't be the same.
return
continue
}
// Delete redundant LHS, RHS pairs, taking care
@ -61,13 +63,13 @@ func run(pass *analysis.Pass) (any, error) {
isSelfAssign := false
var le string
if !analysisutil.HasSideEffects(pass.TypesInfo, lhs) &&
!analysisutil.HasSideEffects(pass.TypesInfo, rhs) &&
!isMapIndex(pass.TypesInfo, lhs) &&
if typesinternal.NoEffects(info, lhs) &&
typesinternal.NoEffects(info, rhs) &&
!isMapIndex(info, lhs) &&
reflect.TypeOf(lhs) == reflect.TypeOf(rhs) { // short-circuit the heavy-weight gofmt check
le = analysisinternal.Format(pass.Fset, lhs)
re := analysisinternal.Format(pass.Fset, rhs)
le = astutil.Format(pass.Fset, lhs)
re := astutil.Format(pass.Fset, rhs)
if le == re {
isSelfAssign = true
}
@ -109,13 +111,14 @@ func run(pass *analysis.Pass) (any, error) {
}
if len(exprs) == 0 {
return
continue
}
if len(exprs) == len(stmt.Lhs) {
// If every part of the statement is a self-assignment,
// remove the whole statement.
edits = []analysis.TextEdit{{Pos: stmt.Pos(), End: stmt.End()}}
tokFile := pass.Fset.File(stmt.Pos())
edits = refactor.DeleteStmt(tokFile, curAssign)
}
pass.Report(analysis.Diagnostic{
@ -126,7 +129,7 @@ func run(pass *analysis.Pass) (any, error) {
TextEdits: edits,
}},
})
})
}
return nil, nil
}

View file

@ -11,10 +11,11 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -22,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "atomic",
Doc: analysisutil.MustExtractDoc(doc, "atomic"),
Doc: analysisinternal.MustExtractDoc(doc, "atomic"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
@ -30,7 +31,7 @@ var Analyzer = &analysis.Analyzer{
}
func run(pass *analysis.Pass) (any, error) {
if !analysisinternal.Imports(pass.Pkg, "sync/atomic") {
if !typesinternal.Imports(pass.Pkg, "sync/atomic") {
return nil, nil // doesn't directly import sync/atomic
}
@ -54,7 +55,7 @@ func run(pass *analysis.Pass) (any, error) {
continue
}
obj := typeutil.Callee(pass.TypesInfo, call)
if analysisinternal.IsFunctionNamed(obj, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") {
if typesinternal.IsFunctionNamed(obj, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") {
checkAtomicAddAssignment(pass, n.Lhs[i], call)
}
}
@ -72,7 +73,7 @@ func checkAtomicAddAssignment(pass *analysis.Pass, left ast.Expr, call *ast.Call
arg := call.Args[0]
broken := false
gofmt := func(e ast.Expr) string { return analysisinternal.Format(pass.Fset, e) }
gofmt := func(e ast.Expr) string { return astutil.Format(pass.Fset, e) }
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
broken = gofmt(left) == gofmt(uarg.X)

View file

@ -13,9 +13,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
const Doc = "check for common mistakes involving boolean operators"
@ -84,7 +84,7 @@ func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*
i := 0
var sets [][]ast.Expr
for j := 0; j <= len(exprs); j++ {
if j == len(exprs) || analysisutil.HasSideEffects(info, exprs[j]) {
if j == len(exprs) || !typesinternal.NoEffects(info, exprs[j]) {
if i < j {
sets = append(sets, exprs[i:j])
}
@ -104,7 +104,7 @@ func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*
func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
seen := make(map[string]bool)
for _, e := range exprs {
efmt := analysisinternal.Format(pass.Fset, e)
efmt := astutil.Format(pass.Fset, e)
if seen[efmt] {
pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
} else {
@ -150,8 +150,8 @@ func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
}
// e is of the form 'x != c' or 'x == c'.
xfmt := analysisinternal.Format(pass.Fset, x)
efmt := analysisinternal.Format(pass.Fset, e)
xfmt := astutil.Format(pass.Fset, x)
efmt := astutil.Format(pass.Fset, e)
if prev, found := seen[xfmt]; found {
// checkRedundant handles the case in which efmt == prev.
if efmt != prev {

View file

@ -14,7 +14,7 @@ import (
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/internal/analysisinternal"
)
const Doc = "check //go:build and // +build directives"
@ -86,7 +86,7 @@ func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since this may not be a Go source file.
// Read the raw bytes instead.
content, tf, err := analysisutil.ReadFile(pass, filename)
content, tf, err := analysisinternal.ReadFile(pass, filename)
if err != nil {
return err
}

View file

@ -18,7 +18,7 @@ import (
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
const debug = false
@ -41,7 +41,7 @@ var Analyzer = &analysis.Analyzer{
}
func run(pass *analysis.Pass) (any, error) {
if !analysisinternal.Imports(pass.Pkg, "runtime/cgo") {
if !typesinternal.Imports(pass.Pkg, "runtime/cgo") {
return nil, nil // doesn't use cgo
}

View file

@ -16,8 +16,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
@ -86,7 +87,7 @@ func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion
lhs := assign.Lhs
for i, x := range assign.Rhs {
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisinternal.Format(pass.Fset, assign.Lhs[i]), path)
pass.ReportRangef(x, "assignment copies lock value to %v: %v", astutil.Format(pass.Fset, assign.Lhs[i]), path)
lhs = nil // An lhs has been reported. We prefer the assignment warning and do not report twice.
}
}
@ -100,7 +101,7 @@ func checkCopyLocksAssign(pass *analysis.Pass, assign *ast.AssignStmt, goversion
if id, ok := l.(*ast.Ident); ok && id.Name != "_" {
if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil {
if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil {
pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", analysisinternal.Format(pass.Fset, l), path)
pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", astutil.Format(pass.Fset, l), path)
}
}
}
@ -132,7 +133,7 @@ func checkCopyLocksCompositeLit(pass *analysis.Pass, cl *ast.CompositeLit) {
x = node.Value
}
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisinternal.Format(pass.Fset, x), path)
pass.ReportRangef(x, "literal copies lock value from %v: %v", astutil.Format(pass.Fset, x), path)
}
}
}
@ -166,7 +167,7 @@ func checkCopyLocksCallExpr(pass *analysis.Pass, ce *ast.CallExpr) {
}
for _, x := range ce.Args {
if path := lockPathRhs(pass, x); path != nil {
pass.ReportRangef(x, "call of %s copies lock value: %v", analysisinternal.Format(pass.Fset, ce.Fun), path)
pass.ReportRangef(x, "call of %s copies lock value: %v", astutil.Format(pass.Fset, ce.Fun), path)
}
}
}
@ -233,7 +234,7 @@ func checkCopyLocksRangeVar(pass *analysis.Pass, rtok token.Token, e ast.Expr) {
return
}
if path := lockPath(pass.Pkg, typ, nil); path != nil {
pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisinternal.Format(pass.Fset, e), path)
pass.Reportf(e.Pos(), "range var %s copies lock: %v", astutil.Format(pass.Fset, e), path)
}
}
@ -353,7 +354,7 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ
// In go1.10, sync.noCopy did not implement Locker.
// (The Unlock method was added only in CL 121876.)
// TODO(adonovan): remove workaround when we drop go1.10.
if analysisinternal.IsTypeNamed(typ, "sync", "noCopy") {
if typesinternal.IsTypeNamed(typ, "sync", "noCopy") {
return []string{typ.String()}
}

View file

@ -10,10 +10,10 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -23,20 +23,20 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "defers",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Doc: analysisinternal.MustExtractDoc(doc, "defers"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers",
Doc: analysisutil.MustExtractDoc(doc, "defers"),
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !analysisinternal.Imports(pass.Pkg, "time") {
if !typesinternal.Imports(pass.Pkg, "time") {
return nil, nil
}
checkDeferCall := func(node ast.Node) bool {
switch v := node.(type) {
case *ast.CallExpr:
if analysisinternal.IsFunctionNamed(typeutil.Callee(pass.TypesInfo, v), "time", "Since") {
if typesinternal.IsFunctionNamed(typeutil.Callee(pass.TypesInfo, v), "time", "Since") {
pass.Reportf(v.Pos(), "call to time.Since is not deferred")
}
case *ast.FuncLit:

View file

@ -14,7 +14,7 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/internal/analysisinternal"
)
const Doc = `check Go toolchain directives such as //go:debug
@ -86,7 +86,7 @@ func checkGoFile(pass *analysis.Pass, f *ast.File) {
func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since is not a Go source file.
// Read the raw bytes instead.
content, tf, err := analysisutil.ReadFile(pass, filename)
content, tf, err := analysisinternal.ReadFile(pass, filename)
if err != nil {
return err
}

View file

@ -12,22 +12,20 @@ import (
"go/types"
"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/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
const Doc = `report passing non-pointer or non-error values to errors.As
The errorsas analysis reports calls to errors.As where the type
The errorsas analyzer reports calls to errors.As where the type
of the second argument is not a pointer to a type implementing error.`
var Analyzer = &analysis.Analyzer{
Name: "errorsas",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
Run: run,
}
@ -39,38 +37,31 @@ func run(pass *analysis.Pass) (any, error) {
return nil, nil
}
if !analysisinternal.Imports(pass.Pkg, "errors") {
return nil, nil // doesn't directly import errors
}
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if !analysisinternal.IsFunctionNamed(obj, "errors", "As") {
return
}
for curCall := range index.Calls(index.Object("errors", "As")) {
call := curCall.Node().(*ast.CallExpr)
if len(call.Args) < 2 {
return // not enough arguments, e.g. called with return values of another function
continue // spread call: errors.As(pair())
}
if err := checkAsTarget(pass, call.Args[1]); err != nil {
// Check for incorrect arguments.
if err := checkAsTarget(info, call.Args[1]); err != nil {
pass.ReportRangef(call, "%v", err)
continue
}
}
})
return nil, nil
}
var errorType = types.Universe.Lookup("error").Type()
// checkAsTarget reports an error if the second argument to errors.As is invalid.
func checkAsTarget(pass *analysis.Pass, e ast.Expr) error {
t := pass.TypesInfo.Types[e].Type
if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
// A target of interface{} is always allowed, since it often indicates
func checkAsTarget(info *types.Info, e ast.Expr) error {
t := info.Types[e].Type
if types.Identical(t.Underlying(), anyType) {
// A target of any is always allowed, since it often indicates
// a value forwarded from another source.
return nil
}
@ -78,12 +69,16 @@ func checkAsTarget(pass *analysis.Pass, e ast.Expr) error {
if !ok {
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
}
if pt.Elem() == errorType {
if types.Identical(pt.Elem(), errorType) {
return errors.New("second argument to errors.As should not be *error")
}
_, ok = pt.Elem().Underlying().(*types.Interface)
if ok || types.Implements(pt.Elem(), errorType.Underlying().(*types.Interface)) {
return nil
}
if !types.IsInterface(pt.Elem()) && !types.AssignableTo(pt.Elem(), errorType) {
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
}
return nil
}
var (
anyType = types.Universe.Lookup("any").Type()
errorType = types.Universe.Lookup("error").Type()
)

View file

@ -13,7 +13,7 @@ import (
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/internal/analysisinternal"
)
const Doc = "report assembly that clobbers the frame pointer before saving it"
@ -98,7 +98,7 @@ func run(pass *analysis.Pass) (any, error) {
}
for _, fname := range sfiles {
content, tf, err := analysisutil.ReadFile(pass, fname)
content, tf, err := analysisinternal.ReadFile(pass, fname)
if err != nil {
return nil, err
}
@ -127,7 +127,7 @@ func run(pass *analysis.Pass) (any, error) {
}
if arch.isFPWrite(line) {
pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
pass.Reportf(tf.LineStart(lineno), "frame pointer is clobbered before saving")
active = false
continue
}

View file

@ -13,7 +13,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
@ -46,7 +45,7 @@ func run(pass *analysis.Pass) (any, error) {
// Fast path: if the package doesn't import net/http,
// skip the traversal.
if !analysisinternal.Imports(pass.Pkg, "net/http") {
if !typesinternal.Imports(pass.Pkg, "net/http") {
return nil, nil
}
@ -118,7 +117,7 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
return false // the function called does not return two values.
}
isPtr, named := typesinternal.ReceiverNamed(res.At(0))
if !isPtr || named == nil || !analysisinternal.IsTypeNamed(named, "net/http", "Response") {
if !isPtr || named == nil || !typesinternal.IsTypeNamed(named, "net/http", "Response") {
return false // the first return type is not *http.Response.
}
@ -133,11 +132,11 @@ func isHTTPFuncOrMethodOnClient(info *types.Info, expr *ast.CallExpr) bool {
return ok && id.Name == "http" // function in net/http package.
}
if analysisinternal.IsTypeNamed(typ, "net/http", "Client") {
if typesinternal.IsTypeNamed(typ, "net/http", "Client") {
return true // method on http.Client.
}
ptr, ok := types.Unalias(typ).(*types.Pointer)
return ok && analysisinternal.IsTypeNamed(ptr.Elem(), "net/http", "Client") // method on *http.Client.
return ok && typesinternal.IsTypeNamed(ptr.Elem(), "net/http", "Client") // method on *http.Client.
}
// restOfBlock, given a traversal stack, finds the innermost containing

View file

@ -11,8 +11,8 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typeparams"
)
@ -21,7 +21,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "ifaceassert",
Doc: analysisutil.MustExtractDoc(doc, "ifaceassert"),
Doc: analysisinternal.MustExtractDoc(doc, "ifaceassert"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -1,99 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package analysisutil defines various helper functions
// used by two or more packages beneath go/analysis.
package analysisutil
import (
"go/ast"
"go/token"
"go/types"
"os"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
)
// HasSideEffects reports whether evaluation of e has side effects.
func HasSideEffects(info *types.Info, e ast.Expr) bool {
safe := true
ast.Inspect(e, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
typVal := info.Types[n.Fun]
switch {
case typVal.IsType():
// Type conversion, which is safe.
case typVal.IsBuiltin():
// Builtin func, conservatively assumed to not
// be safe for now.
safe = false
return false
default:
// A non-builtin func or method call.
// Conservatively assume that all of them have
// side effects for now.
safe = false
return false
}
case *ast.UnaryExpr:
if n.Op == token.ARROW {
safe = false
return false
}
}
return true
})
return !safe
}
// ReadFile reads a file and adds it to the FileSet
// so that we can report errors against it using lineStart.
func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {
readFile := pass.ReadFile
if readFile == nil {
readFile = os.ReadFile
}
content, err := readFile(filename)
if err != nil {
return nil, nil, err
}
tf := pass.Fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)
return content, tf, nil
}
// LineStart returns the position of the start of the specified line
// within file f, or NoPos if there is no line of that number.
func LineStart(f *token.File, line int) token.Pos {
// Use binary search to find the start offset of this line.
//
// TODO(adonovan): eventually replace this function with the
// simpler and more efficient (*go/token.File).LineStart, added
// in go1.12.
min := 0 // inclusive
max := f.Size() // exclusive
for {
offset := (min + max) / 2
pos := f.Pos(offset)
posn := f.Position(pos)
if posn.Line == line {
return pos - (token.Pos(posn.Column) - 1)
}
if min+1 >= max {
return token.NoPos
}
if posn.Line < line {
min = offset
} else {
max = offset
}
}
}
var MustExtractDoc = analysisinternal.MustExtractDoc

View file

@ -11,7 +11,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
@ -24,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "loopclosure",
Doc: analysisutil.MustExtractDoc(doc, "loopclosure"),
Doc: analysisinternal.MustExtractDoc(doc, "loopclosure"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/loopclosure",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -369,5 +368,5 @@ func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method str
// Check that the receiver is a <pkgPath>.<typeName> or
// *<pkgPath>.<typeName>.
_, named := typesinternal.ReceiverNamed(recv)
return analysisinternal.IsTypeNamed(named, pkgPath, typeName)
return typesinternal.IsTypeNamed(named, pkgPath, typeName)
}

View file

@ -13,11 +13,11 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
"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/go/cfg"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -25,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "lostcancel",
Doc: analysisutil.MustExtractDoc(doc, "lostcancel"),
Doc: analysisinternal.MustExtractDoc(doc, "lostcancel"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel",
Run: run,
Requires: []*analysis.Analyzer{
@ -50,7 +50,7 @@ var contextPackage = "context"
// checkLostCancel analyzes a single named or literal function.
func run(pass *analysis.Pass) (any, error) {
// Fast path: bypass check if file doesn't use context.WithCancel.
if !analysisinternal.Imports(pass.Pkg, contextPackage) {
if !typesinternal.Imports(pass.Pkg, contextPackage) {
return nil, nil
}

View file

@ -14,8 +14,8 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "nilfunc",
Doc: analysisutil.MustExtractDoc(doc, "nilfunc"),
Doc: analysisinternal.MustExtractDoc(doc, "nilfunc"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -82,6 +82,16 @@
// ...
// }
//
// A local function may also be inferred as a printf wrapper. If it
// is assigned to a variable, each call made through that variable will
// be checked just like a call to a function:
//
// logf := func(format string, args ...any) {
// message := fmt.Sprintf(format, args...)
// log.Printf("%s: %s", prefix, message)
// }
// logf("%s", 123) // logf format %s has arg 123 of wrong type int
//
// # Specifying printf wrappers by flag
//
// The -funcs flag specifies a comma-separated list of names of

View file

@ -18,13 +18,14 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/fmtstr"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
@ -37,11 +38,11 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "printf",
Doc: analysisutil.MustExtractDoc(doc, "printf"),
Doc: analysisinternal.MustExtractDoc(doc, "printf"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
ResultType: reflect.TypeOf((*Result)(nil)),
ResultType: reflect.TypeFor[*Result](),
FactTypes: []analysis.Fact{new(isWrapper)},
}
@ -70,7 +71,7 @@ func (kind Kind) String() string {
// Result is the printf analyzer's result type. Clients may query the result
// to learn whether a function behaves like fmt.Print or fmt.Printf.
type Result struct {
funcs map[*types.Func]Kind
funcs map[types.Object]Kind
}
// Kind reports whether fn behaves like fmt.Print or fmt.Printf.
@ -111,149 +112,210 @@ func (f *isWrapper) String() string {
func run(pass *analysis.Pass) (any, error) {
res := &Result{
funcs: make(map[*types.Func]Kind),
funcs: make(map[types.Object]Kind),
}
findPrintfLike(pass, res)
checkCalls(pass)
findPrintLike(pass, res)
checkCalls(pass, res)
return res, nil
}
type printfWrapper struct {
obj *types.Func
fdecl *ast.FuncDecl
format *types.Var
args *types.Var
// A wrapper is a candidate print/printf wrapper function.
//
// We represent functions generally as types.Object, not *Func, so
// that we can analyze anonymous functions such as
//
// printf := func(format string, args ...any) {...},
//
// representing them by the *types.Var symbol for the local variable
// 'printf'.
type wrapper struct {
obj types.Object // *Func or *Var
curBody inspector.Cursor // for *ast.BlockStmt
format *types.Var // optional "format string" parameter in the Func{Decl,Lit}
args *types.Var // "args ...any" parameter in the Func{Decl,Lit}
callers []printfCaller
failed bool // if true, not a printf wrapper
}
type printfCaller struct {
w *printfWrapper
w *wrapper
call *ast.CallExpr
}
// maybePrintfWrapper decides whether decl (a declared function) may be a wrapper
// around a fmt.Printf or fmt.Print function. If so it returns a printfWrapper
// function describing the declaration. Later processing will analyze the
// graph of potential printf wrappers to pick out the ones that are true wrappers.
// A function may be a Printf or Print wrapper if its last argument is ...interface{}.
// If the next-to-last argument is a string, then this may be a Printf wrapper.
// Otherwise it may be a Print wrapper.
func maybePrintfWrapper(info *types.Info, decl ast.Decl) *printfWrapper {
// Look for functions with final argument type ...interface{}.
fdecl, ok := decl.(*ast.FuncDecl)
if !ok || fdecl.Body == nil {
return nil
}
fn, ok := info.Defs[fdecl.Name].(*types.Func)
// Type information may be incomplete.
if !ok {
return nil
}
sig := fn.Type().(*types.Signature)
// formatArgsParams returns the "format string" and "args ...any"
// parameters of a potential print or printf wrapper function.
// (The format is nil in the print-like case.)
func formatArgsParams(sig *types.Signature) (format, args *types.Var) {
if !sig.Variadic() {
return nil // not variadic
return nil, nil // not variadic
}
params := sig.Params()
nparams := params.Len() // variadic => nonzero
// Check final parameter is "args ...interface{}".
args := params.At(nparams - 1)
iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
if !ok || !iface.Empty() {
return nil
}
// Is second last param 'format string'?
var format *types.Var
if nparams >= 2 {
if p := params.At(nparams - 2); p.Type() == types.Typ[types.String] {
format = p
}
}
return &printfWrapper{
obj: fn,
fdecl: fdecl,
format: format,
args: args,
}
// Check final parameter is "args ...any".
// (variadic => slice)
args = params.At(nparams - 1)
iface, ok := types.Unalias(args.Type().(*types.Slice).Elem()).(*types.Interface)
if !ok || !iface.Empty() {
return nil, nil
}
// findPrintfLike scans the entire package to find printf-like functions.
func findPrintfLike(pass *analysis.Pass, res *Result) (any, error) {
// Gather potential wrappers and call graph between them.
byObj := make(map[*types.Func]*printfWrapper)
var wrappers []*printfWrapper
for _, file := range pass.Files {
for _, decl := range file.Decls {
w := maybePrintfWrapper(pass.TypesInfo, decl)
if w == nil {
continue
return format, args
}
// findPrintLike scans the entire package to find print or printf-like functions.
// When it returns, all such functions have been identified.
func findPrintLike(pass *analysis.Pass, res *Result) {
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info = pass.TypesInfo
)
// Pass 1: gather candidate wrapper functions (and populate wrappers).
var (
wrappers []*wrapper
byObj = make(map[types.Object]*wrapper)
)
for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
var (
curBody inspector.Cursor // for *ast.BlockStmt
sig *types.Signature
obj types.Object
)
switch f := cur.Node().(type) {
case *ast.FuncDecl:
// named function or method:
//
// func wrapf(format string, args ...any) {...}
if f.Body != nil {
curBody = cur.ChildAt(edge.FuncDecl_Body, -1)
obj = info.Defs[f.Name]
sig = obj.Type().(*types.Signature)
}
case *ast.FuncLit:
// anonymous function directly assigned to a variable:
//
// var wrapf = func(format string, args ...any) {...}
// wrapf := func(format string, args ...any) {...}
// wrapf = func(format string, args ...any) {...}
//
// The LHS may also be a struct field x.wrapf or
// an imported var pkg.Wrapf.
//
sig = info.TypeOf(f).(*types.Signature)
curBody = cur.ChildAt(edge.FuncLit_Body, -1)
var lhs ast.Expr
switch ek, idx := cur.ParentEdge(); ek {
case edge.ValueSpec_Values:
curName := cur.Parent().ChildAt(edge.ValueSpec_Names, idx)
lhs = curName.Node().(*ast.Ident)
case edge.AssignStmt_Rhs:
curLhs := cur.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
lhs = curLhs.Node().(ast.Expr)
}
switch lhs := lhs.(type) {
case *ast.Ident:
// variable: wrapf = func(...)
obj = info.ObjectOf(lhs).(*types.Var)
case *ast.SelectorExpr:
if sel, ok := info.Selections[lhs]; ok {
// struct field: x.wrapf = func(...)
obj = sel.Obj().(*types.Var)
} else {
// imported var: pkg.Wrapf = func(...)
obj = info.Uses[lhs.Sel].(*types.Var)
}
}
}
if obj != nil {
format, args := formatArgsParams(sig)
if args != nil {
// obj (the symbol for a function/method, or variable
// assigned to an anonymous function) is a potential
// print or printf wrapper.
//
// Later processing will analyze the graph of potential
// wrappers and their function bodies to pick out the
// ones that are true wrappers.
w := &wrapper{
obj: obj,
curBody: curBody,
format: format, // non-nil => printf
args: args,
}
byObj[w.obj] = w
wrappers = append(wrappers, w)
}
}
// Walk the graph to figure out which are really printf wrappers.
for _, w := range wrappers {
// Scan function for calls that could be to other printf-like functions.
ast.Inspect(w.fdecl.Body, func(n ast.Node) bool {
if w.failed {
return false
}
// Pass 2: scan the body of each wrapper function
// for calls to other printf-like functions.
//
// Also, reject tricky cases where the parameters
// are potentially mutated by AssignStmt or UnaryExpr.
// TODO: Relax these checks; issue 26555.
if assign, ok := n.(*ast.AssignStmt); ok {
for _, lhs := range assign.Lhs {
if match(pass.TypesInfo, lhs, w.format) ||
match(pass.TypesInfo, lhs, w.args) {
// Modifies the format
// string or args in
// some way, so not a
// simple wrapper.
w.failed = true
return false
}
}
}
if un, ok := n.(*ast.UnaryExpr); ok && un.Op == token.AND {
if match(pass.TypesInfo, un.X, w.format) ||
match(pass.TypesInfo, un.X, w.args) {
// Taking the address of the
// format string or args,
// so not a simple wrapper.
w.failed = true
return false
for _, w := range wrappers {
scan:
for cur := range w.curBody.Preorder(
(*ast.AssignStmt)(nil),
(*ast.UnaryExpr)(nil),
(*ast.CallExpr)(nil),
) {
switch n := cur.Node().(type) {
case *ast.AssignStmt:
// If the wrapper updates format or args
// it is not a simple wrapper.
for _, lhs := range n.Lhs {
if w.format != nil && match(info, lhs, w.format) ||
match(info, lhs, w.args) {
break scan
}
}
call, ok := n.(*ast.CallExpr)
if !ok || len(call.Args) == 0 || !match(pass.TypesInfo, call.Args[len(call.Args)-1], w.args) {
return true
case *ast.UnaryExpr:
// If the wrapper computes &format or &args,
// it is not a simple wrapper.
if n.Op == token.AND &&
(w.format != nil && match(info, n.X, w.format) ||
match(info, n.X, w.args)) {
break scan
}
fn, kind := printfNameAndKind(pass, call)
if kind != 0 {
checkPrintfFwd(pass, w, call, kind, res)
return true
case *ast.CallExpr:
if len(n.Args) > 0 && match(info, n.Args[len(n.Args)-1], w.args) {
if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
// Call from one wrapper candidate to another?
// Record the edge so that if callee is found to be
// a true wrapper, w will be too.
if w2, ok := byObj[callee]; ok {
w2.callers = append(w2.callers, printfCaller{w, n})
}
// If the call is to another function in this package,
// maybe we will find out it is printf-like later.
// Remember this call for later checking.
if fn != nil && fn.Pkg() == pass.Pkg && byObj[fn] != nil {
callee := byObj[fn]
callee.callers = append(callee.callers, printfCaller{w, call})
// Is the candidate a true wrapper, because it calls
// a known print{,f}-like function from the allowlist
// or an imported fact, or another wrapper found
// to be a true wrapper?
// If so, convert all w's callers to kind.
kind := callKind(pass, callee, res)
if kind != KindNone {
checkForward(pass, w, n, kind, res)
}
}
}
}
}
return true
})
}
return nil, nil
}
func match(info *types.Info, arg ast.Expr, param *types.Var) bool {
@ -261,9 +323,9 @@ func match(info *types.Info, arg ast.Expr, param *types.Var) bool {
return ok && info.ObjectOf(id) == param
}
// checkPrintfFwd checks that a printf-forwarding wrapper is forwarding correctly.
// checkForward checks that a forwarding wrapper is forwarding correctly.
// It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...).
func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, kind Kind, res *Result) {
func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) {
matched := kind == KindPrint ||
kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format)
if !matched {
@ -292,18 +354,39 @@ func checkPrintfFwd(pass *analysis.Pass, w *printfWrapper, call *ast.CallExpr, k
pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc)
return
}
fn := w.obj
var fact isWrapper
if !pass.ImportObjectFact(fn, &fact) {
fact.Kind = kind
pass.ExportObjectFact(fn, &fact)
res.funcs[fn] = kind
// If the candidate's print{,f} status becomes known,
// propagate it back to all its so-far known callers.
if res.funcs[w.obj] != kind {
res.funcs[w.obj] = kind
// Export a fact.
// (This is a no-op for local symbols.)
// We can't export facts on a symbol of another package,
// but we can treat the symbol as a wrapper within
// the current analysis unit.
if w.obj.Pkg() == pass.Pkg {
// Facts are associated with origins.
pass.ExportObjectFact(origin(w.obj), &isWrapper{Kind: kind})
}
// Propagate kind back to known callers.
for _, caller := range w.callers {
checkPrintfFwd(pass, caller.w, caller.call, kind, res)
checkForward(pass, caller.w, caller.call, kind, res)
}
}
}
func origin(obj types.Object) types.Object {
switch obj := obj.(type) {
case *types.Func:
return obj.Origin()
case *types.Var:
return obj.Origin()
}
return obj
}
// isPrint records the print functions.
// If a key ends in 'f' then it is assumed to be a formatted print.
//
@ -412,7 +495,7 @@ func stringConstantExpr(pass *analysis.Pass, expr ast.Expr) (string, bool) {
// checkCalls triggers the print-specific checks for calls that invoke a print
// function.
func checkCalls(pass *analysis.Pass) {
func checkCalls(pass *analysis.Pass, res *Result) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.File)(nil),
@ -426,48 +509,60 @@ func checkCalls(pass *analysis.Pass) {
fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
case *ast.CallExpr:
fn, kind := printfNameAndKind(pass, n)
if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
kind := callKind(pass, callee, res)
switch kind {
case KindPrintf, KindErrorf:
checkPrintf(pass, fileVersion, kind, n, fn.FullName())
checkPrintf(pass, fileVersion, kind, n, fullname(callee))
case KindPrint:
checkPrint(pass, n, fn.FullName())
checkPrint(pass, n, fullname(callee))
}
}
}
})
}
func printfNameAndKind(pass *analysis.Pass, call *ast.CallExpr) (fn *types.Func, kind Kind) {
fn, _ = typeutil.Callee(pass.TypesInfo, call).(*types.Func)
if fn == nil {
return nil, 0
func fullname(obj types.Object) string {
if fn, ok := obj.(*types.Func); ok {
return fn.FullName()
}
return obj.Name()
}
// Facts are associated with generic declarations, not instantiations.
fn = fn.Origin()
_, ok := isPrint[fn.FullName()]
// callKind returns the symbol of the called function
// and its print/printf kind, if any.
// (The symbol may be a var for an anonymous function.)
// The result is memoized in res.funcs.
func callKind(pass *analysis.Pass, obj types.Object, res *Result) Kind {
kind, ok := res.funcs[obj]
if !ok {
// cache miss
_, ok := isPrint[fullname(obj)]
if !ok {
// Next look up just "printf", for use with -printf.funcs.
_, ok = isPrint[strings.ToLower(fn.Name())]
_, ok = isPrint[strings.ToLower(obj.Name())]
}
if ok {
if fn.FullName() == "fmt.Errorf" {
// well-known printf functions
if fullname(obj) == "fmt.Errorf" {
kind = KindErrorf
} else if strings.HasSuffix(fn.Name(), "f") {
} else if strings.HasSuffix(obj.Name(), "f") {
kind = KindPrintf
} else {
kind = KindPrint
}
return fn, kind
}
} else {
// imported wrappers
// Facts are associated with generic declarations, not instantiations.
obj = origin(obj)
var fact isWrapper
if pass.ImportObjectFact(fn, &fact) {
return fn, fact.Kind
if pass.ImportObjectFact(obj, &fact) {
kind = fact.Kind
}
return fn, KindNone
}
res.funcs[obj] = kind // cache
}
return kind
}
// isFormatter reports whether t could satisfy fmt.Formatter.
@ -490,7 +585,7 @@ func isFormatter(typ types.Type) bool {
sig := fn.Type().(*types.Signature)
return sig.Params().Len() == 2 &&
sig.Results().Len() == 0 &&
analysisinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") &&
typesinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") &&
types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
}
@ -729,7 +824,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma
if reason != "" {
details = " (" + reason + ")"
}
pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, analysisinternal.Format(pass.Fset, arg), details)
pass.ReportRangef(rng, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, astutil.Format(pass.Fset, arg), details)
return false
}
}
@ -756,7 +851,7 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma
}
arg := call.Args[verbArgIndex]
if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, analysisinternal.Format(pass.Fset, arg))
pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, astutil.Format(pass.Fset, arg))
return false
}
if reason, ok := matchArgType(pass, v.typ, arg); !ok {
@ -768,14 +863,14 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma
if reason != "" {
details = " (" + reason + ")"
}
pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, analysisinternal.Format(pass.Fset, arg), typeString, details)
pass.ReportRangef(rng, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, astutil.Format(pass.Fset, arg), typeString, details)
return false
}
// Detect recursive formatting via value's String/Error methods.
// The '#' flag suppresses the methods, except with %x, %X, and %q.
if v.typ&argString != 0 && v.verb != 'T' && (!strings.Contains(operation.Flags, "#") || strings.ContainsRune("qxX", v.verb)) {
if methodName, ok := recursiveStringer(pass, arg); ok {
pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, analysisinternal.Format(pass.Fset, arg), methodName)
pass.ReportRangef(rng, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, astutil.Format(pass.Fset, arg), methodName)
return false
}
}
@ -927,7 +1022,7 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
if x, ok := sel.X.(*ast.Ident); ok {
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, analysisinternal.Format(pass.Fset, call.Args[0]))
pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, astutil.Format(pass.Fset, call.Args[0]))
}
}
}
@ -961,10 +1056,10 @@ func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
}
for _, arg := range args {
if isFunctionValue(pass, arg) {
pass.ReportRangef(call, "%s arg %s is a func value, not called", name, analysisinternal.Format(pass.Fset, arg))
pass.ReportRangef(call, "%s arg %s is a func value, not called", name, astutil.Format(pass.Fset, arg))
}
if methodName, ok := recursiveStringer(pass, arg); ok {
pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, analysisinternal.Format(pass.Fset, arg), methodName)
pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, astutil.Format(pass.Fset, arg), methodName)
}
}
}

View file

@ -20,7 +20,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
)
@ -123,7 +123,7 @@ func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) {
}
}
if amt >= minSize {
ident := analysisinternal.Format(pass.Fset, x)
ident := astutil.Format(pass.Fset, x)
qualifier := ""
if len(sizes) > 1 {
qualifier = "may be "

View file

@ -18,9 +18,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -29,14 +29,14 @@ var doc string
// Analyzer describes sigchanyzer analysis function detector.
var Analyzer = &analysis.Analyzer{
Name: "sigchanyzer",
Doc: analysisutil.MustExtractDoc(doc, "sigchanyzer"),
Doc: analysisinternal.MustExtractDoc(doc, "sigchanyzer"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !analysisinternal.Imports(pass.Pkg, "os/signal") {
if !typesinternal.Imports(pass.Pkg, "os/signal") {
return nil, nil // doesn't directly import signal
}

View file

@ -17,10 +17,10 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -29,7 +29,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "slog",
Doc: analysisutil.MustExtractDoc(doc, "slog"),
Doc: analysisinternal.MustExtractDoc(doc, "slog"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -115,10 +115,10 @@ func run(pass *analysis.Pass) (any, error) {
default:
if unknownArg == nil {
pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)",
shortName(fn), analysisinternal.Format(pass.Fset, arg))
shortName(fn), astutil.Format(pass.Fset, arg))
} else {
pass.ReportRangef(arg, "%s arg %q should probably be a string or a slog.Attr (previous arg %q cannot be a key)",
shortName(fn), analysisinternal.Format(pass.Fset, arg), analysisinternal.Format(pass.Fset, unknownArg))
shortName(fn), astutil.Format(pass.Fset, arg), astutil.Format(pass.Fset, unknownArg))
}
// Stop here so we report at most one missing key per call.
return
@ -158,7 +158,7 @@ func run(pass *analysis.Pass) (any, error) {
}
func isAttr(t types.Type) bool {
return analysisinternal.IsTypeNamed(t, "log/slog", "Attr")
return typesinternal.IsTypeNamed(t, "log/slog", "Attr")
}
// shortName returns a name for the function that is shorter than FullName.

View file

@ -12,8 +12,8 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
@ -21,7 +21,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "stdmethods",
Doc: analysisutil.MustExtractDoc(doc, "stdmethods"),
Doc: analysisinternal.MustExtractDoc(doc, "stdmethods"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -13,9 +13,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
)
@ -25,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "stringintconv",
Doc: analysisutil.MustExtractDoc(doc, "stringintconv"),
Doc: analysisinternal.MustExtractDoc(doc, "stringintconv"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -198,7 +198,7 @@ func run(pass *analysis.Pass) (any, error) {
// the type has methods, as some {String,GoString,Format}
// may change the behavior of fmt.Sprint.
if len(ttypes) == 1 && len(vtypes) == 1 && types.NewMethodSet(V0).Len() == 0 {
_, prefix, importEdits := analysisinternal.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos())
prefix, importEdits := refactor.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos())
if types.Identical(T0, types.Typ[types.String]) {
// string(x) -> fmt.Sprint(x)
addFix("Format the number as a decimal", append(importEdits,

View file

@ -13,7 +13,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
@ -31,7 +30,7 @@ func init() {
var Analyzer = &analysis.Analyzer{
Name: "testinggoroutine",
Doc: analysisutil.MustExtractDoc(doc, "testinggoroutine"),
Doc: analysisinternal.MustExtractDoc(doc, "testinggoroutine"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -40,7 +39,7 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
if !analysisinternal.Imports(pass.Pkg, "testing") {
if !typesinternal.Imports(pass.Pkg, "testing") {
return nil, nil
}

View file

@ -15,8 +15,8 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "tests",
Doc: analysisutil.MustExtractDoc(doc, "tests"),
Doc: analysisinternal.MustExtractDoc(doc, "tests"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests",
Run: run,
}
@ -258,7 +258,7 @@ func isTestingType(typ types.Type, testingType string) bool {
if !ok {
return false
}
return analysisinternal.IsTypeNamed(ptr.Elem(), "testing", testingType)
return typesinternal.IsTypeNamed(ptr.Elem(), "testing", testingType)
}
// Validate that fuzz target function's arguments are of accepted types.

View file

@ -16,10 +16,10 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
const badFormat = "2006-02-01"
@ -30,7 +30,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "timeformat",
Doc: analysisutil.MustExtractDoc(doc, "timeformat"),
Doc: analysisinternal.MustExtractDoc(doc, "timeformat"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -39,7 +39,7 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (any, error) {
// Note: (time.Time).Format is a method and can be a typeutil.Callee
// without directly importing "time". So we cannot just skip this package
// when !analysisutil.Imports(pass.Pkg, "time").
// when !analysisinternal.Imports(pass.Pkg, "time").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
@ -50,8 +50,8 @@ func run(pass *analysis.Pass) (any, error) {
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if !analysisinternal.IsMethodNamed(obj, "time", "Time", "Format") &&
!analysisinternal.IsFunctionNamed(obj, "time", "Parse") {
if !typesinternal.IsMethodNamed(obj, "time", "Time", "Format") &&
!typesinternal.IsFunctionNamed(obj, "time", "Parse") {
return
}
if len(call.Args) > 0 {

View file

@ -11,9 +11,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
@ -22,7 +22,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unmarshal",
Doc: analysisutil.MustExtractDoc(doc, "unmarshal"),
Doc: analysisinternal.MustExtractDoc(doc, "unmarshal"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -39,7 +39,7 @@ func run(pass *analysis.Pass) (any, error) {
// Note: (*"encoding/json".Decoder).Decode, (* "encoding/gob".Decoder).Decode
// and (* "encoding/xml".Decoder).Decode are methods and can be a typeutil.Callee
// without directly importing their packages. So we cannot just skip this package
// when !analysisutil.Imports(pass.Pkg, "encoding/...").
// when !analysisinternal.Imports(pass.Pkg, "encoding/...").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

View file

@ -14,8 +14,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/refactor"
)
//go:embed doc.go
@ -23,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unreachable",
Doc: analysisutil.MustExtractDoc(doc, "unreachable"),
Doc: analysisinternal.MustExtractDoc(doc, "unreachable"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,
@ -188,6 +189,11 @@ func (d *deadState) findDead(stmt ast.Stmt) {
case *ast.EmptyStmt:
// do not warn about unreachable empty statements
default:
var (
inspect = d.pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
curStmt, _ = inspect.Root().FindNode(stmt)
tokFile = d.pass.Fset.File(stmt.Pos())
)
// (This call to pass.Report is a frequent source
// of diagnostics beyond EOF in a truncated file;
// see #71659.)
@ -197,10 +203,7 @@ func (d *deadState) findDead(stmt ast.Stmt) {
Message: "unreachable code",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove",
TextEdits: []analysis.TextEdit{{
Pos: stmt.Pos(),
End: stmt.End(),
}},
TextEdits: refactor.DeleteStmt(tokFile, curStmt),
}},
})
d.reachable = true // silence error about next statement

View file

@ -14,9 +14,9 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unsafeptr",
Doc: analysisutil.MustExtractDoc(doc, "unsafeptr"),
Doc: analysisinternal.MustExtractDoc(doc, "unsafeptr"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -105,7 +105,7 @@ func isSafeUintptr(info *types.Info, x ast.Expr) bool {
}
switch sel.Sel.Name {
case "Pointer", "UnsafeAddr":
if analysisinternal.IsTypeNamed(info.Types[sel.X].Type, "reflect", "Value") {
if typesinternal.IsTypeNamed(info.Types[sel.X].Type, "reflect", "Value") {
return true
}
}
@ -153,5 +153,5 @@ func hasBasicType(info *types.Info, x ast.Expr, kind types.BasicKind) bool {
// isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader.
func isReflectHeader(t types.Type) bool {
return analysisinternal.IsTypeNamed(t, "reflect", "SliceHeader", "StringHeader")
return typesinternal.IsTypeNamed(t, "reflect", "SliceHeader", "StringHeader")
}

View file

@ -23,7 +23,6 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
@ -34,7 +33,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unusedresult",
Doc: analysisutil.MustExtractDoc(doc, "unusedresult"),
Doc: analysisinternal.MustExtractDoc(doc, "unusedresult"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -13,10 +13,10 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
//go:embed doc.go
@ -24,14 +24,14 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "waitgroup",
Doc: analysisutil.MustExtractDoc(doc, "waitgroup"),
Doc: analysisinternal.MustExtractDoc(doc, "waitgroup"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (any, error) {
if !analysisinternal.Imports(pass.Pkg, "sync") {
if !typesinternal.Imports(pass.Pkg, "sync") {
return nil, nil // doesn't directly import sync
}
@ -44,7 +44,7 @@ func run(pass *analysis.Pass) (any, error) {
if push {
call := n.(*ast.CallExpr)
obj := typeutil.Callee(pass.TypesInfo, call)
if analysisinternal.IsMethodNamed(obj, "sync", "WaitGroup", "Add") &&
if typesinternal.IsMethodNamed(obj, "sync", "WaitGroup", "Add") &&
hasSuffix(stack, wantSuffix) &&
backindex(stack, 1) == backindex(stack, 2).(*ast.BlockStmt).List[0] { // ExprStmt must be Block's first stmt

View file

@ -75,7 +75,6 @@ type Config struct {
VetxOutput string // where to write file of fact information
Stdout string // write stdout (e.g. JSON, unified diff) to this file
SucceedOnTypecheckFailure bool // obsolete awful hack; see #18395 and below
WarnDiagnostics bool // printing diagnostics should not cause a non-zero exit
}
// Main is the main function of a vet-like analysis tool that must be
@ -87,18 +86,9 @@ type Config struct {
// -V=full describe executable for build caching
// foo.cfg perform separate modular analyze on the single
// unit described by a JSON config file foo.cfg.
//
// Also, subject to approval of proposal #71859:
//
// -fix don't print each diagnostic, apply its first fix
// -diff don't apply a fix, print the diff (requires -fix)
//
// Additionally, the environment variable GOVET has the value "vet" or
// "fix" depending on whether the command is being invoked by "go vet",
// to report diagnostics, or "go fix", to apply fixes. This is
// necessary so that callers of Main can select their analyzer suite
// before flag parsing. (Vet analyzers must report real code problems,
// whereas Fix analyzers may fix non-problems such as style issues.)
// -json print diagnostics and fixes in JSON form
func Main(analyzers ...*analysis.Analyzer) {
progname := filepath.Base(os.Args[0])
log.SetFlags(0)
@ -163,7 +153,7 @@ func Run(configFile string, analyzers []*analysis.Analyzer) {
// In VetxOnly mode, the analysis is run only for facts.
if !cfg.VetxOnly {
code = processResults(fset, cfg.ID, results, cfg.WarnDiagnostics)
code = processResults(fset, cfg.ID, results)
}
os.Exit(code)
@ -187,7 +177,7 @@ func readConfig(filename string) (*Config, error) {
return cfg, nil
}
func processResults(fset *token.FileSet, id string, results []result, warnDiagnostics bool) (exit int) {
func processResults(fset *token.FileSet, id string, results []result) (exit int) {
if analysisflags.Fix {
// Don't print the diagnostics,
// but apply all fixes from the root actions.
@ -236,12 +226,10 @@ func processResults(fset *token.FileSet, id string, results []result, warnDiagno
for _, res := range results {
for _, diag := range res.diagnostics {
analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
if !warnDiagnostics {
exit = 1
}
}
}
}
return
}

View file

@ -2,166 +2,39 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package analysisinternal provides gopls' internal analyses with a
// number of helper functions that operate on typed syntax trees.
// Package analysisinternal provides helper functions for use in both
// the analysis drivers in go/analysis and gopls, and in various
// analyzers.
//
// TODO(adonovan): this is not ideal as it may lead to unnecessary
// dependencies between drivers and analyzers. Split into analyzerlib
// and driverlib?
package analysisinternal
import (
"bytes"
"cmp"
"fmt"
"go/ast"
"go/printer"
"go/scanner"
"go/token"
"go/types"
"iter"
pathpkg "path"
"os"
"slices"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/typesinternal"
)
// Deprecated: this heuristic is ill-defined.
// TODO(adonovan): move to sole use in gopls/internal/cache.
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
// Get the end position for the type error.
file := fset.File(start)
if file == nil {
return start
// ReadFile reads a file and adds it to the FileSet in pass
// so that we can report errors against it using lineStart.
func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {
readFile := pass.ReadFile
if readFile == nil {
readFile = os.ReadFile
}
if offset := file.PositionFor(start, false).Offset; offset > len(src) {
return start
} else {
src = src[offset:]
content, err := readFile(filename)
if err != nil {
return nil, nil, err
}
// 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
}
// MatchingIdents finds the names of all identifiers in 'node' that match any of the given types.
// 'pos' represents the position at which the identifiers may be inserted. 'pos' must be within
// the scope of each of identifier we select. Otherwise, we will insert a variable at 'pos' that
// is unrecognized.
func MatchingIdents(typs []types.Type, node ast.Node, pos token.Pos, info *types.Info, pkg *types.Package) map[types.Type][]string {
// Initialize matches to contain the variable types we are searching for.
matches := make(map[types.Type][]string)
for _, typ := range typs {
if typ == nil {
continue // TODO(adonovan): is this reachable?
}
matches[typ] = nil // create entry
}
seen := map[types.Object]struct{}{}
ast.Inspect(node, func(n ast.Node) bool {
if n == nil {
return false
}
// Prevent circular definitions. If 'pos' is within an assignment statement, do not
// allow any identifiers in that assignment statement to be selected. Otherwise,
// we could do the following, where 'x' satisfies the type of 'f0':
//
// x := fakeStruct{f0: x}
//
if assign, ok := n.(*ast.AssignStmt); ok && pos > assign.Pos() && pos <= assign.End() {
return false
}
if n.End() > pos {
return n.Pos() <= pos
}
ident, ok := n.(*ast.Ident)
if !ok || ident.Name == "_" {
return true
}
obj := info.Defs[ident]
if obj == nil || obj.Type() == nil {
return true
}
if _, ok := obj.(*types.TypeName); ok {
return true
}
// Prevent duplicates in matches' values.
if _, ok = seen[obj]; ok {
return true
}
seen[obj] = struct{}{}
// Find the scope for the given position. Then, check whether the object
// exists within the scope.
innerScope := pkg.Scope().Innermost(pos)
if innerScope == nil {
return true
}
_, foundObj := innerScope.LookupParent(ident.Name, pos)
if foundObj != obj {
return true
}
// The object must match one of the types that we are searching for.
// TODO(adonovan): opt: use typeutil.Map?
if names, ok := matches[obj.Type()]; ok {
matches[obj.Type()] = append(names, ident.Name)
} else {
// If the object type does not exactly match
// any of the target types, greedily find the first
// target type that the object type can satisfy.
for typ := range matches {
if equivalentTypes(obj.Type(), typ) {
matches[typ] = append(matches[typ], ident.Name)
}
}
}
return true
})
return matches
}
func equivalentTypes(want, got types.Type) bool {
if types.Identical(want, got) {
return true
}
// Code segment to help check for untyped equality from (golang/go#32146).
if rhs, ok := want.(*types.Basic); ok && rhs.Info()&types.IsUntyped > 0 {
if lhs, ok := got.Underlying().(*types.Basic); ok {
return rhs.Info()&types.IsConstType == lhs.Info()&types.IsConstType
}
}
return types.AssignableTo(want, got)
tf := pass.Fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)
return content, tf, nil
}
// A ReadFileFunc is a function that returns the
@ -193,207 +66,6 @@ func CheckReadable(pass *analysis.Pass, filename string) error {
return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename)
}
// AddImport checks whether this file already imports pkgpath and that
// the import is in scope at pos. If so, it returns the name under
// which it was imported and no edits. Otherwise, it adds a new import
// of pkgpath, using a name derived from the preferred name, and
// returns the chosen name, a prefix to be concatenated with member to
// form a qualified name, and the edit for the new import.
//
// The member argument indicates the name of the desired symbol within
// the imported package. This is needed in the case when the existing
// import is a dot import, because then it is possible that the
// desired symbol is shadowed by other declarations in the current
// package. If member is not shadowed at pos, AddImport returns (".",
// "", nil). (AddImport accepts the caller's implicit claim that the
// imported package declares member.)
//
// Use a preferredName of "_" to request a blank import;
// member is ignored in this case.
//
// It does not mutate its arguments.
func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (name, prefix string, newImport []analysis.TextEdit) {
// Find innermost enclosing lexical block.
scope := info.Scopes[file].Innermost(pos)
if scope == nil {
panic("no enclosing lexical block")
}
// Is there an existing import of this package?
// If so, are we in its scope? (not shadowed)
for _, spec := range file.Imports {
pkgname := info.PkgNameOf(spec)
if pkgname != nil && pkgname.Imported().Path() == pkgpath {
name = pkgname.Name()
if preferredName == "_" {
// Request for blank import; any existing import will do.
return name, "", nil
}
if name == "." {
// The scope of ident must be the file scope.
if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] {
return name, "", nil
}
} else if _, obj := scope.LookupParent(name, pos); obj == pkgname {
return name, name + ".", nil
}
}
}
// We must add a new import.
// Ensure we have a fresh name.
newName := preferredName
if preferredName != "_" {
newName = FreshName(scope, pos, preferredName)
}
// Create a new import declaration either before the first existing
// declaration (which must exist), including its comments; or
// inside the declaration, if it is an import group.
//
// Use a renaming import whenever the preferred name is not
// available, or the chosen name does not match the last
// segment of its path.
newText := fmt.Sprintf("%q", pkgpath)
if newName != preferredName || newName != pathpkg.Base(pkgpath) {
newText = fmt.Sprintf("%s %q", newName, pkgpath)
}
decl0 := file.Decls[0]
var before ast.Node = decl0
switch decl0 := decl0.(type) {
case *ast.GenDecl:
if decl0.Doc != nil {
before = decl0.Doc
}
case *ast.FuncDecl:
if decl0.Doc != nil {
before = decl0.Doc
}
}
if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() {
// Have existing grouped import ( ... ) decl.
if IsStdPackage(pkgpath) && len(gd.Specs) > 0 {
// Add spec for a std package before
// first existing spec, followed by
// a blank line if the next one is non-std.
first := gd.Specs[0].(*ast.ImportSpec)
pos = first.Pos()
if !IsStdPackage(first.Path.Value) {
newText += "\n"
}
newText += "\n\t"
} else {
// Add spec at end of group.
pos = gd.Rparen
newText = "\t" + newText + "\n"
}
} else {
// No import decl, or non-grouped import.
// Add a new import decl before first decl.
// (gofmt will merge multiple import decls.)
pos = before.Pos()
newText = "import " + newText + "\n\n"
}
return newName, newName + ".", []analysis.TextEdit{{
Pos: pos,
End: pos,
NewText: []byte(newText),
}}
}
// FreshName returns the name of an identifier that is undefined
// at the specified position, based on the preferred name.
func FreshName(scope *types.Scope, pos token.Pos, preferred string) string {
newName := preferred
for i := 0; ; i++ {
if _, obj := scope.LookupParent(newName, pos); obj == nil {
break // fresh
}
newName = fmt.Sprintf("%s%d", preferred, i)
}
return newName
}
// Format returns a string representation of the node n.
func Format(fset *token.FileSet, n ast.Node) string {
var buf strings.Builder
printer.Fprint(&buf, fset, n) // ignore errors
return buf.String()
}
// Imports returns true if path is imported by pkg.
func Imports(pkg *types.Package, path string) bool {
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return true
}
}
return false
}
// IsTypeNamed reports whether t is (or is an alias for) a
// package-level defined type with the given package path and one of
// the given names. It returns false if t is nil.
//
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool {
if named, ok := types.Unalias(t).(*types.Named); ok {
tname := named.Obj()
return tname != nil &&
typesinternal.IsPackageLevel(tname) &&
tname.Pkg().Path() == pkgPath &&
slices.Contains(names, tname.Name())
}
return false
}
// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a
// package-level defined type with the given package path and one of the given
// names. It returns false if t is not a pointer type.
func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool {
r := typesinternal.Unpointer(t)
if r == t {
return false
}
return IsTypeNamed(r, pkgPath, names...)
}
// IsFunctionNamed reports whether obj is a package-level function
// defined in the given package and has one of the given names.
// It returns false if obj is nil.
//
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool {
f, ok := obj.(*types.Func)
return ok &&
typesinternal.IsPackageLevel(obj) &&
f.Pkg().Path() == pkgPath &&
f.Type().(*types.Signature).Recv() == nil &&
slices.Contains(names, f.Name())
}
// IsMethodNamed reports whether obj is a method defined on a
// package-level type with the given package and type name, and has
// one of the given names. It returns false if obj is nil.
//
// This function avoids allocating the concatenation of "pkg.TypeName.Name",
// which is important for the performance of syntax matching.
func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool {
if fn, ok := obj.(*types.Func); ok {
if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
_, T := typesinternal.ReceiverNamed(recv)
return T != nil &&
IsTypeNamed(T, pkgPath, typeName) &&
slices.Contains(names, fn.Name())
}
}
return false
}
// ValidateFixes validates the set of fixes for a single diagnostic.
// Any error indicates a bug in the originating analyzer.
//
@ -496,172 +168,6 @@ func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error {
return nil
}
// CanImport reports whether one package is allowed to import another.
//
// TODO(adonovan): allow customization of the accessibility relation
// (e.g. for Bazel).
func CanImport(from, to string) bool {
// TODO(adonovan): better segment hygiene.
if to == "internal" || strings.HasPrefix(to, "internal/") {
// Special case: only std packages may import internal/...
// We can't reliably know whether we're in std, so we
// use a heuristic on the first segment.
first, _, _ := strings.Cut(from, "/")
if strings.Contains(first, ".") {
return false // example.com/foo ∉ std
}
if first == "testdata" {
return false // testdata/foo ∉ std
}
}
if strings.HasSuffix(to, "/internal") {
return strings.HasPrefix(from, to[:len(to)-len("/internal")])
}
if i := strings.LastIndex(to, "/internal/"); i >= 0 {
return strings.HasPrefix(from, to[:i])
}
return true
}
// DeleteStmt returns the edits to remove the [ast.Stmt] identified by
// curStmt, if it is contained within a BlockStmt, CaseClause,
// CommClause, or is the STMT in switch STMT; ... {...}. It returns nil otherwise.
func DeleteStmt(fset *token.FileSet, curStmt inspector.Cursor) []analysis.TextEdit {
stmt := curStmt.Node().(ast.Stmt)
// if the stmt is on a line by itself delete the whole line
// otherwise just delete the statement.
// this logic would be a lot simpler with the file contents, and somewhat simpler
// if the cursors included the comments.
tokFile := fset.File(stmt.Pos())
lineOf := tokFile.Line
stmtStartLine, stmtEndLine := lineOf(stmt.Pos()), lineOf(stmt.End())
var from, to token.Pos
// bounds of adjacent syntax/comments on same line, if any
limits := func(left, right token.Pos) {
if lineOf(left) == stmtStartLine {
from = left
}
if lineOf(right) == stmtEndLine {
to = right
}
}
// TODO(pjw): there are other places a statement might be removed:
// IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
// (removing the blocks requires more rewriting than this routine would do)
// CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
// (removing the stmt requires more rewriting, and it's unclear what the user means)
switch parent := curStmt.Parent().Node().(type) {
case *ast.SwitchStmt:
limits(parent.Switch, parent.Body.Lbrace)
case *ast.TypeSwitchStmt:
limits(parent.Switch, parent.Body.Lbrace)
if parent.Assign == stmt {
return nil // don't let the user break the type switch
}
case *ast.BlockStmt:
limits(parent.Lbrace, parent.Rbrace)
case *ast.CommClause:
limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
if parent.Comm == stmt {
return nil // maybe the user meant to remove the entire CommClause?
}
case *ast.CaseClause:
limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
case *ast.ForStmt:
limits(parent.For, parent.Body.Lbrace)
default:
return nil // not one of ours
}
if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine {
from = prev.Node().End() // preceding statement ends on same line
}
if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine {
to = next.Node().Pos() // following statement begins on same line
}
// and now for the comments
Outer:
for _, cg := range enclosingFile(curStmt).Comments {
for _, co := range cg.List {
if lineOf(co.End()) < stmtStartLine {
continue
} else if lineOf(co.Pos()) > stmtEndLine {
break Outer // no more are possible
}
if lineOf(co.End()) == stmtStartLine && co.End() < stmt.Pos() {
if !from.IsValid() || co.End() > from {
from = co.End()
continue // maybe there are more
}
}
if lineOf(co.Pos()) == stmtEndLine && co.Pos() > stmt.End() {
if !to.IsValid() || co.Pos() < to {
to = co.Pos()
continue // maybe there are more
}
}
}
}
// if either from or to is valid, just remove the statement
// otherwise remove the line
edit := analysis.TextEdit{Pos: stmt.Pos(), End: stmt.End()}
if from.IsValid() || to.IsValid() {
// remove just the statement.
// we can't tell if there is a ; or whitespace right after the statement
// ideally we'd like to remove the former and leave the latter
// (if gofmt has run, there likely won't be a ;)
// In type switches we know there's a semicolon somewhere after the statement,
// but the extra work for this special case is not worth it, as gofmt will fix it.
return []analysis.TextEdit{edit}
}
// remove the whole line
for lineOf(edit.Pos) == stmtStartLine {
edit.Pos--
}
edit.Pos++ // get back tostmtStartLine
for lineOf(edit.End) == stmtEndLine {
edit.End++
}
return []analysis.TextEdit{edit}
}
// Comments returns an iterator over the comments overlapping the specified interval.
func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] {
// TODO(adonovan): optimize use binary O(log n) instead of linear O(n) search.
return func(yield func(*ast.Comment) bool) {
for _, cg := range file.Comments {
for _, co := range cg.List {
if co.Pos() > end {
return
}
if co.End() < start {
continue
}
if !yield(co) {
return
}
}
}
}
}
// IsStdPackage reports whether the specified package path belongs to a
// package in the standard library (including internal dependencies).
func IsStdPackage(path string) bool {
// A standard package has no dot in its first segment.
// (It may yet have a dot, e.g. "vendor/golang.org/x/foo".)
slash := strings.IndexByte(path, '/')
if slash < 0 {
slash = len(path)
}
return !strings.Contains(path[:slash], ".") && path != "testdata"
}
// Range returns an [analysis.Range] for the specified start and end positions.
func Range(pos, end token.Pos) analysis.Range {
return tokenRange{pos, end}
@ -672,9 +178,3 @@ type tokenRange struct{ StartPos, EndPos token.Pos }
func (r tokenRange) Pos() token.Pos { return r.StartPos }
func (r tokenRange) End() token.Pos { return r.EndPos }
// enclosingFile returns the syntax tree for the file enclosing c.
func enclosingFile(c inspector.Cursor) *ast.File {
c, _ = moreiters.First(c.Enclosing((*ast.File)(nil)))
return c.Node().(*ast.File)
}

View file

@ -35,7 +35,7 @@ import (
//
// var Analyzer = &analysis.Analyzer{
// Name: "halting",
// Doc: analysisutil.MustExtractDoc(doc, "halting"),
// Doc: analysisinternal.MustExtractDoc(doc, "halting"),
// ...
// }
func MustExtractDoc(content, name string) string {

View file

@ -7,6 +7,7 @@ package astutil
import (
"go/ast"
"go/token"
"iter"
"strings"
)
@ -111,3 +112,24 @@ func Directives(g *ast.CommentGroup) (res []*Directive) {
}
return
}
// Comments returns an iterator over the comments overlapping the specified interval.
func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] {
// TODO(adonovan): optimize use binary O(log n) instead of linear O(n) search.
return func(yield func(*ast.Comment) bool) {
for _, cg := range file.Comments {
for _, co := range cg.List {
if co.Pos() > end {
return
}
if co.End() < start {
continue
}
if !yield(co) {
return
}
}
}
}
}

View file

@ -26,6 +26,14 @@ func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool {
return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical)
}
// EqualSyntax reports whether x and y are equal.
// Identifiers are considered equal if they are spelled the same.
// Comments are ignored.
func EqualSyntax(x, y ast.Expr) bool {
sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name }
return Equal(x, y, sameName)
}
func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool {
// Ensure types are the same
if x.Type() != y.Type() {

View file

@ -6,7 +6,13 @@ package astutil
import (
"go/ast"
"go/printer"
"go/token"
"strings"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/moreiters"
)
// PreorderStack traverses the tree rooted at root,
@ -67,3 +73,47 @@ func NodeContains(n ast.Node, pos token.Pos) bool {
}
return start <= pos && pos <= end
}
// IsChildOf reports whether cur.ParentEdge is ek.
//
// TODO(adonovan): promote to a method of Cursor.
func IsChildOf(cur inspector.Cursor, ek edge.Kind) bool {
got, _ := cur.ParentEdge()
return got == ek
}
// EnclosingFile returns the syntax tree for the file enclosing c.
//
// TODO(adonovan): promote this to a method of Cursor.
func EnclosingFile(c inspector.Cursor) *ast.File {
c, _ = moreiters.First(c.Enclosing((*ast.File)(nil)))
return c.Node().(*ast.File)
}
// DocComment returns the doc comment for a node, if any.
func DocComment(n ast.Node) *ast.CommentGroup {
switch n := n.(type) {
case *ast.FuncDecl:
return n.Doc
case *ast.GenDecl:
return n.Doc
case *ast.ValueSpec:
return n.Doc
case *ast.TypeSpec:
return n.Doc
case *ast.File:
return n.Doc
case *ast.ImportSpec:
return n.Doc
case *ast.Field:
return n.Doc
}
return nil
}
// Format returns a string representation of the node n.
func Format(fset *token.FileSet, n ast.Node) string {
var buf strings.Builder
printer.Fprint(&buf, fset, n) // ignore errors
return buf.String()
}

View file

@ -378,10 +378,7 @@ func (e *editGraph) twoDone(df, db int) (int, bool) {
return 0, false // diagonals cannot overlap
}
kmin := max(-df, -db+e.delta)
kmax := db + e.delta
if df < kmax {
kmax = df
}
kmax := min(df, db+e.delta)
for k := kmin; k <= kmax; k += 2 {
x := e.vf.get(df, k)
u := e.vb.get(db, k-e.delta)

View file

@ -103,11 +103,3 @@ func commonSuffixLenString(a, b string) int {
}
return i
}
func min(x, y int) int {
if x < y {
return x
} else {
return y
}
}

View file

@ -0,0 +1,49 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package packagepath provides metadata operations on package path
// strings.
package packagepath
// (This package should not depend on go/ast.)
import "strings"
// CanImport reports whether one package is allowed to import another.
//
// TODO(adonovan): allow customization of the accessibility relation
// (e.g. for Bazel).
func CanImport(from, to string) bool {
// TODO(adonovan): better segment hygiene.
if to == "internal" || strings.HasPrefix(to, "internal/") {
// Special case: only std packages may import internal/...
// We can't reliably know whether we're in std, so we
// use a heuristic on the first segment.
first, _, _ := strings.Cut(from, "/")
if strings.Contains(first, ".") {
return false // example.com/foo ∉ std
}
if first == "testdata" {
return false // testdata/foo ∉ std
}
}
if strings.HasSuffix(to, "/internal") {
return strings.HasPrefix(from, to[:len(to)-len("/internal")])
}
if i := strings.LastIndex(to, "/internal/"); i >= 0 {
return strings.HasPrefix(from, to[:i])
}
return true
}
// IsStdPackage reports whether the specified package path belongs to a
// package in the standard library (including internal dependencies).
func IsStdPackage(path string) bool {
// A standard package has no dot in its first segment.
// (It may yet have a dot, e.g. "vendor/golang.org/x/foo".)
slash := strings.IndexByte(path, '/')
if slash < 0 {
slash = len(path)
}
return !strings.Contains(path[:slash], ".") && path != "testdata"
}

View file

@ -0,0 +1,484 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package refactor
// This file defines operations for computing deletion edits.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
// DeleteVar returns edits to delete the declaration of a variable or
// constant whose defining identifier is curId.
//
// It handles variants including:
// - GenDecl > ValueSpec versus AssignStmt;
// - RHS expression has effects, or not;
// - entire statement/declaration may be eliminated;
// and removes associated comments.
//
// If it cannot make the necessary edits, such as for a function
// parameter or result, it returns nil.
func DeleteVar(tokFile *token.File, info *types.Info, curId inspector.Cursor) []analysis.TextEdit {
switch ek, _ := curId.ParentEdge(); ek {
case edge.ValueSpec_Names:
return deleteVarFromValueSpec(tokFile, info, curId)
case edge.AssignStmt_Lhs:
return deleteVarFromAssignStmt(tokFile, info, curId)
}
// e.g. function receiver, parameter, or result,
// or "switch v := expr.(T) {}" (which has no object).
return nil
}
// deleteVarFromValueSpec returns edits to delete the declaration of a
// variable or constant within a ValueSpec.
//
// Precondition: curId is Ident beneath ValueSpec.Names beneath GenDecl.
//
// See also [deleteVarFromAssignStmt], which has parallel structure.
func deleteVarFromValueSpec(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []analysis.TextEdit {
var (
id = curIdent.Node().(*ast.Ident)
curSpec = curIdent.Parent()
spec = curSpec.Node().(*ast.ValueSpec)
)
declaresOtherNames := slices.ContainsFunc(spec.Names, func(name *ast.Ident) bool {
return name != id && name.Name != "_"
})
noRHSEffects := !slices.ContainsFunc(spec.Values, func(rhs ast.Expr) bool {
return !typesinternal.NoEffects(info, rhs)
})
if !declaresOtherNames && noRHSEffects {
// The spec is no longer needed, either to declare
// other variables, or for its side effects.
return DeleteSpec(tokFile, curSpec)
}
// The spec is still needed, either for
// at least one LHS, or for effects on RHS.
// Blank out or delete just one LHS.
_, index := curIdent.ParentEdge() // index of LHS within ValueSpec.Names
// If there is no RHS, we can delete the LHS.
if len(spec.Values) == 0 {
var pos, end token.Pos
if index == len(spec.Names)-1 {
// Delete final name.
//
// var _, lhs1 T
// ------
pos = spec.Names[index-1].End()
end = spec.Names[index].End()
} else {
// Delete non-final name.
//
// var lhs0, _ T
// ------
pos = spec.Names[index].Pos()
end = spec.Names[index+1].Pos()
}
return []analysis.TextEdit{{
Pos: pos,
End: end,
}}
}
// If the assignment is n:n and the RHS has no effects,
// we can delete the LHS and its corresponding RHS.
if len(spec.Names) == len(spec.Values) &&
typesinternal.NoEffects(info, spec.Values[index]) {
if index == len(spec.Names)-1 {
// Delete final items.
//
// var _, lhs1 = rhs0, rhs1
// ------ ------
return []analysis.TextEdit{
{
Pos: spec.Names[index-1].End(),
End: spec.Names[index].End(),
},
{
Pos: spec.Values[index-1].End(),
End: spec.Values[index].End(),
},
}
} else {
// Delete non-final items.
//
// var lhs0, _ = rhs0, rhs1
// ------ ------
return []analysis.TextEdit{
{
Pos: spec.Names[index].Pos(),
End: spec.Names[index+1].Pos(),
},
{
Pos: spec.Values[index].Pos(),
End: spec.Values[index+1].Pos(),
},
}
}
}
// We cannot delete the RHS.
// Blank out the LHS.
return []analysis.TextEdit{{
Pos: id.Pos(),
End: id.End(),
NewText: []byte("_"),
}}
}
// Precondition: curId is Ident beneath AssignStmt.Lhs.
//
// See also [deleteVarFromValueSpec], which has parallel structure.
func deleteVarFromAssignStmt(tokFile *token.File, info *types.Info, curIdent inspector.Cursor) []analysis.TextEdit {
var (
id = curIdent.Node().(*ast.Ident)
curStmt = curIdent.Parent()
assign = curStmt.Node().(*ast.AssignStmt)
)
declaresOtherNames := slices.ContainsFunc(assign.Lhs, func(lhs ast.Expr) bool {
lhsId, ok := lhs.(*ast.Ident)
return ok && lhsId != id && lhsId.Name != "_"
})
noRHSEffects := !slices.ContainsFunc(assign.Rhs, func(rhs ast.Expr) bool {
return !typesinternal.NoEffects(info, rhs)
})
if !declaresOtherNames && noRHSEffects {
// The assignment is no longer needed, either to
// declare other variables, or for its side effects.
if edits := DeleteStmt(tokFile, curStmt); edits != nil {
return edits
}
// Statement could not not be deleted in this context.
// Fall back to conservative deletion.
}
// The assign is still needed, either for
// at least one LHS, or for effects on RHS,
// or because it cannot deleted because of its context.
// Blank out or delete just one LHS.
// If the assignment is 1:1 and the RHS has no effects,
// we can delete the LHS and its corresponding RHS.
_, index := curIdent.ParentEdge()
if len(assign.Lhs) > 1 &&
len(assign.Lhs) == len(assign.Rhs) &&
typesinternal.NoEffects(info, assign.Rhs[index]) {
if index == len(assign.Lhs)-1 {
// Delete final items.
//
// _, lhs1 := rhs0, rhs1
// ------ ------
return []analysis.TextEdit{
{
Pos: assign.Lhs[index-1].End(),
End: assign.Lhs[index].End(),
},
{
Pos: assign.Rhs[index-1].End(),
End: assign.Rhs[index].End(),
},
}
} else {
// Delete non-final items.
//
// lhs0, _ := rhs0, rhs1
// ------ ------
return []analysis.TextEdit{
{
Pos: assign.Lhs[index].Pos(),
End: assign.Lhs[index+1].Pos(),
},
{
Pos: assign.Rhs[index].Pos(),
End: assign.Rhs[index+1].Pos(),
},
}
}
}
// We cannot delete the RHS.
// Blank out the LHS.
edits := []analysis.TextEdit{{
Pos: id.Pos(),
End: id.End(),
NewText: []byte("_"),
}}
// If this eliminates the final variable declared by
// an := statement, we need to turn it into an =
// assignment to avoid a "no new variables on left
// side of :=" error.
if !declaresOtherNames {
edits = append(edits, analysis.TextEdit{
Pos: assign.TokPos,
End: assign.TokPos + token.Pos(len(":=")),
NewText: []byte("="),
})
}
return edits
}
// DeleteSpec returns edits to delete the {Type,Value}Spec identified by curSpec.
//
// TODO(adonovan): add test suite. Test for consts as well.
func DeleteSpec(tokFile *token.File, curSpec inspector.Cursor) []analysis.TextEdit {
var (
spec = curSpec.Node().(ast.Spec)
curDecl = curSpec.Parent()
decl = curDecl.Node().(*ast.GenDecl)
)
// If it is the sole spec in the decl,
// delete the entire decl.
if len(decl.Specs) == 1 {
return DeleteDecl(tokFile, curDecl)
}
// Delete the spec and its comments.
_, index := curSpec.ParentEdge() // index of ValueSpec within GenDecl.Specs
pos, end := spec.Pos(), spec.End()
if doc := astutil.DocComment(spec); doc != nil {
pos = doc.Pos() // leading comment
}
if index == len(decl.Specs)-1 {
// Delete final spec.
if c := eolComment(spec); c != nil {
// var (v int // comment \n)
end = c.End()
}
} else {
// Delete non-final spec.
// var ( a T; b T )
// -----
end = decl.Specs[index+1].Pos()
}
return []analysis.TextEdit{{
Pos: pos,
End: end,
}}
}
// DeleteDecl returns edits to delete the ast.Decl identified by curDecl.
//
// TODO(adonovan): add test suite.
func DeleteDecl(tokFile *token.File, curDecl inspector.Cursor) []analysis.TextEdit {
decl := curDecl.Node().(ast.Decl)
ek, _ := curDecl.ParentEdge()
switch ek {
case edge.DeclStmt_Decl:
return DeleteStmt(tokFile, curDecl.Parent())
case edge.File_Decls:
pos, end := decl.Pos(), decl.End()
if doc := astutil.DocComment(decl); doc != nil {
pos = doc.Pos()
}
// Delete free-floating comments on same line as rparen.
// var (...) // comment
var (
file = curDecl.Parent().Node().(*ast.File)
lineOf = tokFile.Line
declEndLine = lineOf(decl.End())
)
for _, cg := range file.Comments {
for _, c := range cg.List {
if c.Pos() < end {
continue // too early
}
commentEndLine := lineOf(c.End())
if commentEndLine > declEndLine {
break // too late
} else if lineOf(c.Pos()) == declEndLine && commentEndLine == declEndLine {
end = c.End()
}
}
}
return []analysis.TextEdit{{
Pos: pos,
End: end,
}}
default:
panic(fmt.Sprintf("Decl parent is %v, want DeclStmt or File", ek))
}
}
// DeleteStmt returns the edits to remove the [ast.Stmt] identified by
// curStmt, if it is contained within a BlockStmt, CaseClause,
// CommClause, or is the STMT in switch STMT; ... {...}. It returns nil otherwise.
func DeleteStmt(tokFile *token.File, curStmt inspector.Cursor) []analysis.TextEdit {
stmt := curStmt.Node().(ast.Stmt)
// if the stmt is on a line by itself delete the whole line
// otherwise just delete the statement.
// this logic would be a lot simpler with the file contents, and somewhat simpler
// if the cursors included the comments.
lineOf := tokFile.Line
stmtStartLine, stmtEndLine := lineOf(stmt.Pos()), lineOf(stmt.End())
var from, to token.Pos
// bounds of adjacent syntax/comments on same line, if any
limits := func(left, right token.Pos) {
if lineOf(left) == stmtStartLine {
from = left
}
if lineOf(right) == stmtEndLine {
to = right
}
}
// TODO(pjw): there are other places a statement might be removed:
// IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
// (removing the blocks requires more rewriting than this routine would do)
// CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
// (removing the stmt requires more rewriting, and it's unclear what the user means)
switch parent := curStmt.Parent().Node().(type) {
case *ast.SwitchStmt:
limits(parent.Switch, parent.Body.Lbrace)
case *ast.TypeSwitchStmt:
limits(parent.Switch, parent.Body.Lbrace)
if parent.Assign == stmt {
return nil // don't let the user break the type switch
}
case *ast.BlockStmt:
limits(parent.Lbrace, parent.Rbrace)
case *ast.CommClause:
limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
if parent.Comm == stmt {
return nil // maybe the user meant to remove the entire CommClause?
}
case *ast.CaseClause:
limits(parent.Colon, curStmt.Parent().Parent().Node().(*ast.BlockStmt).Rbrace)
case *ast.ForStmt:
limits(parent.For, parent.Body.Lbrace)
default:
return nil // not one of ours
}
if prev, found := curStmt.PrevSibling(); found && lineOf(prev.Node().End()) == stmtStartLine {
from = prev.Node().End() // preceding statement ends on same line
}
if next, found := curStmt.NextSibling(); found && lineOf(next.Node().Pos()) == stmtEndLine {
to = next.Node().Pos() // following statement begins on same line
}
// and now for the comments
Outer:
for _, cg := range astutil.EnclosingFile(curStmt).Comments {
for _, co := range cg.List {
if lineOf(co.End()) < stmtStartLine {
continue
} else if lineOf(co.Pos()) > stmtEndLine {
break Outer // no more are possible
}
if lineOf(co.End()) == stmtStartLine && co.End() < stmt.Pos() {
if !from.IsValid() || co.End() > from {
from = co.End()
continue // maybe there are more
}
}
if lineOf(co.Pos()) == stmtEndLine && co.Pos() > stmt.End() {
if !to.IsValid() || co.Pos() < to {
to = co.Pos()
continue // maybe there are more
}
}
}
}
// if either from or to is valid, just remove the statement
// otherwise remove the line
edit := analysis.TextEdit{Pos: stmt.Pos(), End: stmt.End()}
if from.IsValid() || to.IsValid() {
// remove just the statement.
// we can't tell if there is a ; or whitespace right after the statement
// ideally we'd like to remove the former and leave the latter
// (if gofmt has run, there likely won't be a ;)
// In type switches we know there's a semicolon somewhere after the statement,
// but the extra work for this special case is not worth it, as gofmt will fix it.
return []analysis.TextEdit{edit}
}
// remove the whole line
for lineOf(edit.Pos) == stmtStartLine {
edit.Pos--
}
edit.Pos++ // get back tostmtStartLine
for lineOf(edit.End) == stmtEndLine {
edit.End++
}
return []analysis.TextEdit{edit}
}
// DeleteUnusedVars computes the edits required to delete the
// declarations of any local variables whose last uses are in the
// curDelend subtree, which is about to be deleted.
func DeleteUnusedVars(index *typeindex.Index, info *types.Info, tokFile *token.File, curDelend inspector.Cursor) []analysis.TextEdit {
// TODO(adonovan): we might want to generalize this by
// splitting the two phases below, so that we can gather
// across a whole sequence of deletions then finally compute the
// set of variables that are no longer wanted.
// Count number of deletions of each var.
delcount := make(map[*types.Var]int)
for curId := range curDelend.Preorder((*ast.Ident)(nil)) {
id := curId.Node().(*ast.Ident)
if v, ok := info.Uses[id].(*types.Var); ok &&
typesinternal.GetVarKind(v) == typesinternal.LocalVar { // always false before go1.25
delcount[v]++
}
}
// Delete declaration of each var that became unused.
var edits []analysis.TextEdit
for v, count := range delcount {
if len(slices.Collect(index.Uses(v))) == count {
if curDefId, ok := index.Def(v); ok {
edits = append(edits, DeleteVar(tokFile, info, curDefId)...)
}
}
}
return edits
}
func eolComment(n ast.Node) *ast.CommentGroup {
// TODO(adonovan): support:
// func f() {...} // comment
switch n := n.(type) {
case *ast.GenDecl:
if !n.TokPos.IsValid() && len(n.Specs) == 1 {
return eolComment(n.Specs[0])
}
case *ast.ValueSpec:
return n.Comment
case *ast.TypeSpec:
return n.Comment
}
return nil
}

View file

@ -0,0 +1,127 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package refactor
// This file defines operations for computing edits to imports.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
pathpkg "path"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/packagepath"
)
// AddImport returns the prefix (either "pkg." or "") that should be
// used to qualify references to the desired symbol (member) imported
// from the specified package, plus any necessary edits to the file's
// import declaration to add a new import.
//
// If the import already exists, and is accessible at pos, AddImport
// returns the existing name and no edits. (If the existing import is
// a dot import, the prefix is "".)
//
// Otherwise, it adds a new import, using a local name derived from
// the preferred name. To request a blank import, use a preferredName
// of "_", and discard the prefix result; member is ignored in this
// case.
//
// AddImport accepts the caller's implicit claim that the imported
// package declares member.
//
// AddImport does not mutate its arguments.
func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (prefix string, edits []analysis.TextEdit) {
// Find innermost enclosing lexical block.
scope := info.Scopes[file].Innermost(pos)
if scope == nil {
panic("no enclosing lexical block")
}
// Is there an existing import of this package?
// If so, are we in its scope? (not shadowed)
for _, spec := range file.Imports {
pkgname := info.PkgNameOf(spec)
if pkgname != nil && pkgname.Imported().Path() == pkgpath {
name := pkgname.Name()
if preferredName == "_" {
// Request for blank import; any existing import will do.
return "", nil
}
if name == "." {
// The scope of ident must be the file scope.
if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] {
return "", nil
}
} else if _, obj := scope.LookupParent(name, pos); obj == pkgname {
return name + ".", nil
}
}
}
// We must add a new import.
// Ensure we have a fresh name.
newName := preferredName
if preferredName != "_" {
newName = FreshName(scope, pos, preferredName)
}
// Create a new import declaration either before the first existing
// declaration (which must exist), including its comments; or
// inside the declaration, if it is an import group.
//
// Use a renaming import whenever the preferred name is not
// available, or the chosen name does not match the last
// segment of its path.
newText := fmt.Sprintf("%q", pkgpath)
if newName != preferredName || newName != pathpkg.Base(pkgpath) {
newText = fmt.Sprintf("%s %q", newName, pkgpath)
}
decl0 := file.Decls[0]
var before ast.Node = decl0
switch decl0 := decl0.(type) {
case *ast.GenDecl:
if decl0.Doc != nil {
before = decl0.Doc
}
case *ast.FuncDecl:
if decl0.Doc != nil {
before = decl0.Doc
}
}
if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() {
// Have existing grouped import ( ... ) decl.
if packagepath.IsStdPackage(pkgpath) && len(gd.Specs) > 0 {
// Add spec for a std package before
// first existing spec, followed by
// a blank line if the next one is non-std.
first := gd.Specs[0].(*ast.ImportSpec)
pos = first.Pos()
if !packagepath.IsStdPackage(first.Path.Value) {
newText += "\n"
}
newText += "\n\t"
} else {
// Add spec at end of group.
pos = gd.Rparen
newText = "\t" + newText + "\n"
}
} else {
// No import decl, or non-grouped import.
// Add a new import decl before first decl.
// (gofmt will merge multiple import decls.)
pos = before.Pos()
newText = "import " + newText + "\n\n"
}
return newName + ".", []analysis.TextEdit{{
Pos: pos,
End: pos,
NewText: []byte(newText),
}}
}

View file

@ -0,0 +1,29 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package refactor provides operators to compute common textual edits
// for refactoring tools.
//
// This package should not use features of the analysis API
// other than [analysis.TextEdit].
package refactor
import (
"fmt"
"go/token"
"go/types"
)
// FreshName returns the name of an identifier that is undefined
// at the specified position, based on the preferred name.
func FreshName(scope *types.Scope, pos token.Pos, preferred string) string {
newName := preferred
for i := 0; ; i++ {
if _, obj := scope.LookupParent(newName, pos); obj == nil {
break // fresh
}
newName = fmt.Sprintf("%s%d", preferred, i)
}
return newName
}

View file

@ -0,0 +1,88 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typesinternal
import (
"go/ast"
"go/token"
"go/types"
)
// NoEffects reports whether the expression has no side effects, i.e., it
// does not modify the memory state. This function is conservative: it may
// return false even when the expression has no effect.
func NoEffects(info *types.Info, expr ast.Expr) bool {
noEffects := true
ast.Inspect(expr, func(n ast.Node) bool {
switch v := n.(type) {
case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr,
*ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr,
*ast.StarExpr, *ast.CompositeLit,
// non-expressions that may appear within expressions
*ast.KeyValueExpr,
*ast.FieldList,
*ast.Field,
*ast.Ellipsis,
*ast.IndexListExpr:
// No effect.
case *ast.ArrayType,
*ast.StructType,
*ast.ChanType,
*ast.FuncType,
*ast.MapType,
*ast.InterfaceType:
// Type syntax: no effects, recursively.
// Prune descent.
return false
case *ast.UnaryExpr:
// Channel send <-ch has effects.
if v.Op == token.ARROW {
noEffects = false
}
case *ast.CallExpr:
// Type conversion has no effects.
if !info.Types[v.Fun].IsType() {
if CallsPureBuiltin(info, v) {
// A call such as len(e) has no effects of its
// own, though the subexpression e might.
} else {
noEffects = false
}
}
case *ast.FuncLit:
// A FuncLit has no effects, but do not descend into it.
return false
default:
// All other expressions have effects
noEffects = false
}
return noEffects
})
return noEffects
}
// CallsPureBuiltin reports whether call is a call of a built-in
// function that is a pure computation over its operands (analogous to
// a + operator). Because it does not depend on program state, it may
// be evaluated at any point--though not necessarily at multiple
// points (consider new, make).
func CallsPureBuiltin(info *types.Info, call *ast.CallExpr) bool {
if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok {
if b, ok := info.ObjectOf(id).(*types.Builtin); ok {
switch b.Name() {
case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min":
return true
}
// Not: append clear close copy delete panic print println recover
}
}
return false
}

View file

@ -0,0 +1,71 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typesinternal
import (
"go/types"
"slices"
)
// IsTypeNamed reports whether t is (or is an alias for) a
// package-level defined type with the given package path and one of
// the given names. It returns false if t is nil.
//
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool {
if named, ok := types.Unalias(t).(*types.Named); ok {
tname := named.Obj()
return tname != nil &&
IsPackageLevel(tname) &&
tname.Pkg().Path() == pkgPath &&
slices.Contains(names, tname.Name())
}
return false
}
// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a
// package-level defined type with the given package path and one of the given
// names. It returns false if t is not a pointer type.
func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool {
r := Unpointer(t)
if r == t {
return false
}
return IsTypeNamed(r, pkgPath, names...)
}
// IsFunctionNamed reports whether obj is a package-level function
// defined in the given package and has one of the given names.
// It returns false if obj is nil.
//
// This function avoids allocating the concatenation of "pkg.Name",
// which is important for the performance of syntax matching.
func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool {
f, ok := obj.(*types.Func)
return ok &&
IsPackageLevel(obj) &&
f.Pkg().Path() == pkgPath &&
f.Type().(*types.Signature).Recv() == nil &&
slices.Contains(names, f.Name())
}
// IsMethodNamed reports whether obj is a method defined on a
// package-level type with the given package and type name, and has
// one of the given names. It returns false if obj is nil.
//
// This function avoids allocating the concatenation of "pkg.TypeName.Name",
// which is important for the performance of syntax matching.
func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool {
if fn, ok := obj.(*types.Func); ok {
if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
_, T := ReceiverNamed(recv)
return T != nil &&
IsTypeNamed(T, pkgPath, typeName) &&
slices.Contains(names, fn.Name())
}
}
return false
}

View file

@ -2,8 +2,20 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package typesinternal provides access to internal go/types APIs that are not
// yet exported.
// Package typesinternal provides helpful operators for dealing with
// go/types:
//
// - operators for querying typed syntax trees (e.g. [Imports], [IsFunctionNamed]);
// - functions for converting types to strings or syntax (e.g. [TypeExpr], FileQualifier]);
// - helpers for working with the [go/types] API (e.g. [NewTypesInfo]);
// - access to internal go/types APIs that are not yet
// exported (e.g. [SetUsesCgo], [ErrorCodeStartEnd], [VarKind]); and
// - common algorithms related to types (e.g. [TooNewStdSymbols]).
//
// See also:
// - [golang.org/x/tools/internal/astutil], for operations on untyped syntax;
// - [golang.org/x/tools/internal/analysisinernal], for helpers for analyzers;
// - [golang.org/x/tools/internal/refactor], for operators to compute text edits.
package typesinternal
import (
@ -13,6 +25,7 @@ import (
"reflect"
"unsafe"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/aliases"
)
@ -60,6 +73,9 @@ func ErrorCodeStartEnd(err types.Error) (code ErrorCode, start, end token.Pos, o
// which is often excessive.)
//
// If pkg is nil, it is equivalent to [*types.Package.Name].
//
// TODO(adonovan): all uses of this with TypeString should be
// eliminated when https://go.dev/issues/75604 is resolved.
func NameRelativeTo(pkg *types.Package) types.Qualifier {
return func(other *types.Package) string {
if pkg != nil && pkg == other {
@ -153,3 +169,31 @@ func NewTypesInfo() *types.Info {
FileVersions: map[*ast.File]string{},
}
}
// EnclosingScope returns the innermost block logically enclosing the cursor.
func EnclosingScope(info *types.Info, cur inspector.Cursor) *types.Scope {
for cur := range cur.Enclosing() {
n := cur.Node()
// A function's Scope is associated with its FuncType.
switch f := n.(type) {
case *ast.FuncDecl:
n = f.Type
case *ast.FuncLit:
n = f.Type
}
if b := info.Scopes[n]; b != nil {
return b
}
}
panic("no Scope for *ast.File")
}
// Imports reports whether path is imported by pkg.
func Imports(pkg *types.Package, path string) bool {
for _, imp := range pkg.Imports() {
if imp.Path() == path {
return true
}
}
return false
}

View file

@ -2,39 +2,22 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typesinternal
//go:build go1.25
// TODO(adonovan): when CL 645115 lands, define the go1.25 version of
// this API that actually does something.
package typesinternal
import "go/types"
type VarKind uint8
type VarKind = types.VarKind
const (
_ VarKind = iota // (not meaningful)
PackageVar // a package-level variable
LocalVar // a local variable
RecvVar // a method receiver variable
ParamVar // a function parameter variable
ResultVar // a function result variable
FieldVar // a struct field
PackageVar = types.PackageVar
LocalVar = types.LocalVar
RecvVar = types.RecvVar
ParamVar = types.ParamVar
ResultVar = types.ResultVar
FieldVar = types.FieldVar
)
func (kind VarKind) String() string {
return [...]string{
0: "VarKind(0)",
PackageVar: "PackageVar",
LocalVar: "LocalVar",
RecvVar: "RecvVar",
ParamVar: "ParamVar",
ResultVar: "ResultVar",
FieldVar: "FieldVar",
}[kind]
}
// GetVarKind returns an invalid VarKind.
func GetVarKind(v *types.Var) VarKind { return 0 }
// SetVarKind has no effect.
func SetVarKind(v *types.Var, kind VarKind) {}
func GetVarKind(v *types.Var) VarKind { return v.Kind() }
func SetVarKind(v *types.Var, kind VarKind) { v.SetKind(kind) }

View file

@ -0,0 +1,39 @@
// 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.25
package typesinternal
import "go/types"
type VarKind uint8
const (
_ VarKind = iota // (not meaningful)
PackageVar // a package-level variable
LocalVar // a local variable
RecvVar // a method receiver variable
ParamVar // a function parameter variable
ResultVar // a function result variable
FieldVar // a struct field
)
func (kind VarKind) String() string {
return [...]string{
0: "VarKind(0)",
PackageVar: "PackageVar",
LocalVar: "LocalVar",
RecvVar: "RecvVar",
ParamVar: "ParamVar",
ResultVar: "ResultVar",
FieldVar: "FieldVar",
}[kind]
}
// GetVarKind returns an invalid VarKind.
func GetVarKind(v *types.Var) VarKind { return 0 }
// SetVarKind has no effect.
func SetVarKind(v *types.Var, kind VarKind) {}

View file

@ -204,23 +204,12 @@ func ZeroExpr(t types.Type, qual types.Qualifier) (_ ast.Expr, isValid bool) {
}
}
// IsZeroExpr uses simple syntactic heuristics to report whether expr
// is a obvious zero value, such as 0, "", nil, or false.
// It cannot do better without type information.
func IsZeroExpr(expr ast.Expr) bool {
switch e := expr.(type) {
case *ast.BasicLit:
return e.Value == "0" || e.Value == `""`
case *ast.Ident:
return e.Name == "nil" || e.Name == "false"
default:
return false
}
}
// TypeExpr returns syntax for the specified type. References to named types
// are qualified by an appropriate (optional) qualifier function.
// It may panic for types such as Tuple or Union.
//
// See also https://go.dev/issues/75604, which will provide a robust
// Type-to-valid-Go-syntax formatter.
func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr {
switch t := t.(type) {
case *types.Basic:

View file

@ -28,7 +28,7 @@ golang.org/x/arch/x86/x86asm
# golang.org/x/build v0.0.0-20250806225920-b7c66c047964
## explicit; go 1.23.0
golang.org/x/build/relnote
# golang.org/x/mod v0.28.0
# golang.org/x/mod v0.29.0
## explicit; go 1.24.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
@ -43,12 +43,12 @@ golang.org/x/mod/zip
## explicit; go 1.24.0
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
# golang.org/x/sys v0.36.0
# golang.org/x/sys v0.37.0
## explicit; go 1.24.0
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
# golang.org/x/telemetry v0.0.0-20250908211612-aef8a434d053
# golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8
## explicit; go 1.24.0
golang.org/x/telemetry
golang.org/x/telemetry/counter
@ -63,7 +63,7 @@ golang.org/x/telemetry/internal/upload
# golang.org/x/term v0.34.0
## explicit; go 1.23.0
golang.org/x/term
# golang.org/x/text v0.29.0
# golang.org/x/text v0.30.0
## explicit; go 1.24.0
golang.org/x/text/cases
golang.org/x/text/internal
@ -73,7 +73,7 @@ golang.org/x/text/internal/tag
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
# golang.org/x/tools v0.37.1-0.20250924232827-4df13e317ce4
# golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5
## explicit; go 1.24.0
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover
@ -97,7 +97,6 @@ golang.org/x/tools/go/analysis/passes/hostport
golang.org/x/tools/go/analysis/passes/httpresponse
golang.org/x/tools/go/analysis/passes/ifaceassert
golang.org/x/tools/go/analysis/passes/inspect
golang.org/x/tools/go/analysis/passes/internal/analysisutil
golang.org/x/tools/go/analysis/passes/loopclosure
golang.org/x/tools/go/analysis/passes/lostcancel
golang.org/x/tools/go/analysis/passes/nilfunc
@ -133,6 +132,8 @@ golang.org/x/tools/internal/diff/lcs
golang.org/x/tools/internal/facts
golang.org/x/tools/internal/fmtstr
golang.org/x/tools/internal/moreiters
golang.org/x/tools/internal/packagepath
golang.org/x/tools/internal/refactor
golang.org/x/tools/internal/stdlib
golang.org/x/tools/internal/typeparams
golang.org/x/tools/internal/typesinternal

View file

@ -3,11 +3,11 @@ module std
go 1.26
require (
golang.org/x/crypto v0.42.0
golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f
golang.org/x/crypto v0.43.0
golang.org/x/net v0.46.0
)
require (
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.30.0 // indirect
)

View file

@ -1,8 +1,8 @@
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f h1:vNklv+oJQSYNGsWXHoCPi2MHMcpj9/Q7aBhvvfnJvGg=
golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=

View file

@ -11695,6 +11695,12 @@ func (ws *http2priorityWriteSchedulerRFC9218) AdjustStream(streamID uint32, prio
q.prev.next = q
q.next.prev = q
}
// Update the metadata.
ws.streams[streamID] = http2streamMetadata{
location: q,
priority: priority,
}
}
func (ws *http2priorityWriteSchedulerRFC9218) Push(wr http2FrameWriteRequest) {

View file

@ -427,13 +427,6 @@ type isolatingRunSequence struct {
func (i *isolatingRunSequence) Len() int { return len(i.indexes) }
func maxLevel(a, b level) level {
if a > b {
return a
}
return b
}
// Rule X10, second bullet: Determine the start-of-sequence (sos) and end-of-sequence (eos) types,
// either L or R, for each isolating run sequence.
func (p *paragraph) isolatingRunSequence(indexes []int) *isolatingRunSequence {
@ -474,8 +467,8 @@ func (p *paragraph) isolatingRunSequence(indexes []int) *isolatingRunSequence {
indexes: indexes,
types: types,
level: level,
sos: typeForLevel(maxLevel(prevLevel, level)),
eos: typeForLevel(maxLevel(succLevel, level)),
sos: typeForLevel(max(prevLevel, level)),
eos: typeForLevel(max(succLevel, level)),
}
}

View file

@ -1,4 +1,4 @@
# golang.org/x/crypto v0.42.0
# golang.org/x/crypto v0.43.0
## explicit; go 1.24.0
golang.org/x/crypto/chacha20
golang.org/x/crypto/chacha20poly1305
@ -6,7 +6,7 @@ golang.org/x/crypto/cryptobyte
golang.org/x/crypto/cryptobyte/asn1
golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305
# golang.org/x/net v0.44.1-0.20251002015445-edb764c2296f
# golang.org/x/net v0.46.0
## explicit; go 1.24.0
golang.org/x/net/dns/dnsmessage
golang.org/x/net/http/httpguts
@ -15,10 +15,10 @@ golang.org/x/net/http2/hpack
golang.org/x/net/idna
golang.org/x/net/lif
golang.org/x/net/nettest
# golang.org/x/sys v0.36.0
# golang.org/x/sys v0.37.0
## explicit; go 1.24.0
golang.org/x/sys/cpu
# golang.org/x/text v0.29.0
# golang.org/x/text v0.30.0
## explicit; go 1.24.0
golang.org/x/text/secure/bidirule
golang.org/x/text/transform