]> Cypherpunks repositories - gostls13.git/commitdiff
cmd: update to x/tools@7d9453cc
authorAlan Donovan <adonovan@google.com>
Fri, 26 Sep 2025 17:03:12 +0000 (13:03 -0400)
committerGopher Robot <gobot@golang.org>
Thu, 16 Oct 2025 11:23:16 +0000 (04:23 -0700)
 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>
70 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/golang.org/x/sys/unix/affinity_linux.go
src/cmd/vendor/golang.org/x/sys/unix/fdset.go
src/cmd/vendor/golang.org/x/sys/unix/ifreq_linux.go
src/cmd/vendor/golang.org/x/sys/unix/mkall.sh
src/cmd/vendor/golang.org/x/sys/unix/syscall_linux.go
src/cmd/vendor/golang.org/x/sys/unix/syscall_netbsd.go
src/cmd/vendor/golang.org/x/sys/windows/syscall_windows.go
src/cmd/vendor/golang.org/x/sys/windows/types_windows.go
src/cmd/vendor/golang.org/x/sys/windows/zsyscall_windows.go
src/cmd/vendor/golang.org/x/telemetry/internal/crashmonitor/monitor.go
src/cmd/vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/appends/appends.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/assign/assign.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/atomic/atomic.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/buildtag/buildtag.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/cgocall/cgocall.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/copylock/copylock.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/defers/defers.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/directive/directive.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/framepointer/framepointer.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/httpresponse/httpresponse.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/ifaceassert/ifaceassert.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go [deleted file]
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/loopclosure/loopclosure.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/lostcancel/lostcancel.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/nilfunc/nilfunc.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/shift/shift.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/sigchanyzer/sigchanyzer.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/slog/slog.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdmethods/stdmethods.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stringintconv/string.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/testinggoroutine/testinggoroutine.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/tests/tests.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/timeformat/timeformat.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unmarshal/unmarshal.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unreachable/unreachable.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unsafeptr/unsafeptr.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/unusedresult/unusedresult.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/waitgroup/waitgroup.go
src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/analysis.go
src/cmd/vendor/golang.org/x/tools/internal/analysisinternal/extractdoc.go
src/cmd/vendor/golang.org/x/tools/internal/astutil/comment.go
src/cmd/vendor/golang.org/x/tools/internal/astutil/equal.go
src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/old.go
src/cmd/vendor/golang.org/x/tools/internal/diff/lcs/sequence.go
src/cmd/vendor/golang.org/x/tools/internal/packagepath/packagepath.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/fx.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/types.go
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/varkind.go
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/varkind_go124.go [new file with mode: 0644]
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go
src/cmd/vendor/modules.txt
src/go.mod
src/go.sum
src/net/http/h2_bundle.go
src/vendor/golang.org/x/text/unicode/bidi/core.go
src/vendor/modules.txt

index 017883a7870cd236caacc50a822a2d16ff64c720..1fc256ae6f2ce715b3d907949a6b86e07d2e030a 100644 (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
 )
index 0906ffcc605854a620f3e5e9b247091d9b6e6031..eb7af1615520203b3ef99bfda18dcd688c193214 100644 (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=
index 3c7a6d6e2f1d2fcc6291f6b51bda779ffad5bfd3..3ea470387bcf0832a407c80af2b3b2c02c4c2d4d 100644 (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
 }
index 9e83d18cd0421525a28e1bdbe1a24890ebb33602..62ed12645f48dd0e218906a06897d2ae2f800a93 100644 (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[:])
 }
index 848840ae4c75866920d139a896f17270c19464aa..309f5a2b0c76ae861d126b14a2176da2d9ee5a03 100644 (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
index e6f31d374df52cc72d798a871ec05b95d4f7e4f2..d0ed611912929614c971e6bd6bda89f225bd00fc 100644 (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
index 4958a657085bcde240ca296bc15e7f6404095ead..9439af961d98b62926e50d2610c8e8169ab48850 100644 (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
 }
index 88162099af5444085cad78759a3ca5a7bf26de44..34a4676973042f12e266cfe2e7c2ea0768fab50d 100644 (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
  */
index 640f6b153f00453334ebebc0b3e9ff0019725e14..bd51337306019a3e32bab25efa113a35ba71342f 100644 (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
index 993a2297dbe1a520a811d4864041287205359b61..358be3c7f5eece5238c208c0e100a3153b3f5f18 100644 (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
index 641a5f4b775aa1f9a1b7a586dcb61a46d4c7141b..426151a0193d355f90726fe3d19bd241946a8abf 100644 (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 {
index 53966ad2bcb3628952999a13c305d05e0383b5ad..ac22f68c34ccf1ed4da053037e2233a224429469 100644 (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
-       }
-}
index ce92892c817690f5139f5b00f02355500e66b497..0ca27316e6236b0c472464bfcb8e4d99c5efc3cf 100644 (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 {
index e554c3cc90301efc957e6c9c3c2eb8ca82ffca75..b4e91edce3b88abdee77a0660788e9e20d0bbd8b 100644 (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,
index efbf05d596a6d2af83402a5435ed66b491215f2a..e9c0879844938680646cd91d141dda5f0e908bc0 100644 (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 == "" {
index 1914bb476168bac45125359854b0dcc9672c3dec..8080aed020e5f9daffe3bf3df087abf8a2bf0833 100644 (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
 }
index 82d5439ce571e9eeedd783c58fb3f89356a80b14..9faa3f67c1deaefeb36fff181d5460d3075dd6a3 100644 (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)
index e1cf9f9b7ade101bd66f2ccb2e8114e657893428..574fafaa95dc98fbed079449d86bd54074503aea 100644 (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 {
index 6e32f298dc25e3dd56ec868eb2ddffa12236ce2b..7dd4f249e258cf9901fd5456d902bdff33bad1e9 100644 (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
        }
index d9189b5b69608f4024167e273a43638db60006b6..bf1202b92b73d4d083e78685fa809d5d406c88e4 100644 (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
        }
 
index d35b85f03a721b1b42fa84bebc04e6f70e15378b..4190cc5900f682dec651cc124952176ab5a94d22 100644 (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()}
        }
 
index e11957f2d099d113078e560a3152e112c5efec80..3069ee9fecd8478735c97c4d388dbbdb280270f7 100644 (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:
index bebec891408fefb4e0d3605e1ec0d170b7aa110b..c84d25842e3018f6fc18725d12733ade1f3982e4 100644 (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
        }
index b8d29d019db0b0d7dcd6b79994d2a851dfc1a8d5..b3df99929dc6fc33aff066a70104923fce006003 100644 (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
-       }
-
-       inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+       var (
+               index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+               info  = pass.TypesInfo
+       )
 
-       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 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()
+)
index ff9c8b4f818b104174eb98e905542dfb93104eb5..809095d40a54560b1aa6237a87baa3c2975f7c5c 100644 (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
                        }
index e9acd96547e1a786255029b66f118326757e17f4..37ecb6523bd1ca67fb887d69fe50bbe6d0facf29 100644 (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
index 4022dbe7c22cce9013393fb79dc8d2b2f5cfce8c..a6dcf1cf8e8e1de6d032df1dcb259c5fb74d1849 100644 (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,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/internal/analysisutil/util.go
deleted file mode 100644 (file)
index d3df898..0000000
+++ /dev/null
@@ -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
index 2580a0ac21f1f969921fb5fbdc99c841d96a27e7..868226328fc5e11bd78fad7df113b6cef96c7469 100644 (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)
 }
index c0746789e9cb2ae7934a64797168e259b7fcae16..dfaecf51e25a2b7735496ff6d77661baf0496d94 100644 (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
        }
 
index fa1883b0c3402cae99b6e73c9ce0f461f75293a3..2b5a7c80378828025c4d9c7427d315d9fa73b371 100644 (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,
index eebf40208d18165ca14719730a77baa6ae8648ff..f04e44143412a4df50d9d6315c3217d033b98e94 100644 (file)
 //             ...
 //     }
 //
+// 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
index f008eca36fe3c1872f9e8a371ddb8c5ca3fe592a..910ffe70d7e33ae7eef27bbc4cbabc47f619c370 100644 (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
        }
+
+       return format, args
 }
 
-// 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
+// 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)
                        }
-                       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
+               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)
                        }
 
-                       // 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
-                                       }
+                       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 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
+               }
+               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)
                        }
+               }
+       }
 
-                       call, ok := n.(*ast.CallExpr)
-                       if !ok || len(call.Args) == 0 || !match(pass.TypesInfo, call.Args[len(call.Args)-1], w.args) {
-                               return true
-                       }
+       // 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.
+       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
+                                       }
+                               }
 
-                       fn, kind := printfNameAndKind(pass, call)
-                       if kind != 0 {
-                               checkPrintfFwd(pass, w, call, kind, res)
-                               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
+                               }
 
-                       // 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})
+                       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})
+                                               }
+
+                                               // 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)
-                       switch kind {
-                       case KindPrintf, KindErrorf:
-                               checkPrintf(pass, fileVersion, kind, n, fn.FullName())
-                       case KindPrint:
-                               checkPrint(pass, n, fn.FullName())
+                       if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil {
+                               kind := callKind(pass, callee, res)
+                               switch kind {
+                               case KindPrintf, KindErrorf:
+                                       checkPrintf(pass, fileVersion, kind, n, fullname(callee))
+                               case KindPrint:
+                                       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 {
-               // Next look up just "printf", for use with -printf.funcs.
-               _, ok = isPrint[strings.ToLower(fn.Name())]
-       }
-       if ok {
-               if fn.FullName() == "fmt.Errorf" {
-                       kind = KindErrorf
-               } else if strings.HasSuffix(fn.Name(), "f") {
-                       kind = KindPrintf
+               // cache miss
+               _, ok := isPrint[fullname(obj)]
+               if !ok {
+                       // Next look up just "printf", for use with -printf.funcs.
+                       _, ok = isPrint[strings.ToLower(obj.Name())]
+               }
+               if ok {
+                       // well-known printf functions
+                       if fullname(obj) == "fmt.Errorf" {
+                               kind = KindErrorf
+                       } else if strings.HasSuffix(obj.Name(), "f") {
+                               kind = KindPrintf
+                       } else {
+                               kind = KindPrint
+                       }
                } else {
-                       kind = KindPrint
+                       // imported wrappers
+                       // Facts are associated with generic declarations, not instantiations.
+                       obj = origin(obj)
+                       var fact isWrapper
+                       if pass.ImportObjectFact(obj, &fact) {
+                               kind = fact.Kind
+                       }
                }
-               return fn, kind
+               res.funcs[obj] = kind // cache
        }
-
-       var fact isWrapper
-       if pass.ImportObjectFact(fn, &fact) {
-               return fn, fact.Kind
-       }
-
-       return fn, KindNone
+       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)
                }
        }
 }
index 57987b3d203a75e17673dd29e46d961bec4680ce..366927326fcede2941a0c62a0a2c85a92ad53ede 100644 (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 "
index 78a2fa5ea3bd346396f476e06387b5a85e51b74f..934f3913c2763bbfc7625523459fa4ef40b3099c 100644 (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
        }
 
index c1ac960435d41fe79323d79e5a523e621e556431..2cb91c732993740a6203a6a296f6d039030b97bf 100644 (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.
index a0bdf001abd602f7eaead854bbb562e2a8f24246..ca303ae5c15c6d783f2e3df9ab967b90bca80cda 100644 (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,
index 7dbff1e4d8d4ee5fdd21921d1eccb86888ace045..19c72d2cf938ae442cba3b5bec3ee64f1416a2f5 100644 (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,
index 360ba0e74d89d34b17ddb8669dc7cadf09f2ecce..eba4e56bb0532b89fa1e7b21ffb7a6083a0bb844 100644 (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
        }
 
index d4e9b025324a1843d6e12b87b0845cd430ad1bd6..a0ed5ab14e8e14edb2bca4e2a4c7482007723b0a 100644 (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.
index 4fdbb2b5415eff7da5c12692f9ad92e4dd650328..45b6822c17606ea0c51e162ae22da209f8b46b71 100644 (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 {
index 26e894bd4000eb9e92a9a6c6b01092d9c6f1d8df..4de48c83930648e7b17a7ead7ab6c26812a0a190 100644 (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)
index 317f034992bb5d0025875a7e1ca949bb5bd2d474..668a33529989d1df1004aa85a89400cf877cb0e1 100644 (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.)
@@ -196,11 +202,8 @@ func (d *deadState) findDead(stmt ast.Stmt) {
                                End:     stmt.End(),
                                Message: "unreachable code",
                                SuggestedFixes: []analysis.SuggestedFix{{
-                                       Message: "Remove",
-                                       TextEdits: []analysis.TextEdit{{
-                                               Pos: stmt.Pos(),
-                                               End: stmt.End(),
-                                       }},
+                                       Message:   "Remove",
+                                       TextEdits: refactor.DeleteStmt(tokFile, curStmt),
                                }},
                        })
                        d.reachable = true // silence error about next statement
index 57c6da64ff30e3a0ff1fb9f0b80129f73ee211b8..24ff723390f59739c2e577eb97becacd74bb579f 100644 (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")
 }
index ed4cf7ae0be35b99a5229aec7e408e901f64644d..57ad4f0769924c93c5dc5c6da3a9952e4a1ec057 100644 (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,
index 14c6986eabab3b607892b182a25d33e56904f8d8..88e4cc8677691e5b70b803d3fb50942bc7295bd7 100644 (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
 
index 7b805b882bf2df162771bad29973a214a4e36cfd..b407bc7791547fce35358841571a3e768e756ff1 100644 (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,9 +226,7 @@ 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
-                               }
+                               exit = 1
                        }
                }
        }
index cea89d34dac45bae4f404600156997d83e6d0ee1..970d7507f02d0adde602b16d934692410dbf9d17 100644 (file)
 // 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
-       }
-       if offset := file.PositionFor(start, false).Offset; offset > len(src) {
-               return start
-       } else {
-               src = src[offset:]
-       }
-
-       // Attempt to find a reasonable end position for the type error.
-       //
-       // TODO(rfindley): the heuristic implemented here is unclear. It looks like
-       // it seeks the end of the primary operand starting at start, but that is not
-       // quite implemented (for example, given a func literal this heuristic will
-       // return the range of the func keyword).
-       //
-       // We should formalize this heuristic, or deprecate it by finally proposing
-       // to add end position to all type checker errors.
-       //
-       // Nevertheless, ensure that the end position at least spans the current
-       // token at the cursor (this was golang/go#69505).
-       end := start
-       {
-               var s scanner.Scanner
-               fset := token.NewFileSet()
-               f := fset.AddFile("", fset.Base(), len(src))
-               s.Init(f, src, nil /* no error handler */, scanner.ScanComments)
-               pos, tok, lit := s.Scan()
-               if tok != token.SEMICOLON && token.Pos(f.Base()) <= pos && pos <= token.Pos(f.Base()+f.Size()) {
-                       off := file.Offset(pos) + len(lit)
-                       src = src[off:]
-                       end += token.Pos(off)
-               }
+// 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
        }
-
-       // 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)
+       content, err := readFile(filename)
+       if err != nil {
+               return nil, nil, err
        }
-       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)
-}
index bfb5900f1b3195e8e1120bbbd3d6f789a21256e3..c6cdf5997e2194eabc47a6db8d17ab592df49a48 100644 (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 {
index c3a256c987cbc354a14579dff1b2abac66cabb83..7e52aeaaac590abec2b5044722c3bd9176fb2e5c 100644 (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
+                               }
+                       }
+               }
+       }
+}
index c945de02d4a13096c09bfe95febef27ae23bd91d..210f392387b426dafb394af1eb39f7831672967d 100644 (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() {
index 14189155e4e895a8cf16b9c6e26b3809cf987ff0..a1c09835041bf0defcfe02e68c9a9005ff303317 100644 (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()
+}
index 4c346706a7566c08665113334106ec1bac9e67e9..7b7c5cc677beb4cbdb98af3d020728c89bf8b23a 100644 (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)
index 2d72d2630435b8b62a2aa5681f7ab3c15705332d..811bb216ea2f247c0d2ad89f493c15f9e67c57a8 100644 (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
-       }
-}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/packagepath/packagepath.go b/src/cmd/vendor/golang.org/x/tools/internal/packagepath/packagepath.go
new file mode 100644 (file)
index 0000000..fa39a13
--- /dev/null
@@ -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"
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go
new file mode 100644 (file)
index 0000000..6df01d8
--- /dev/null
@@ -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
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/imports.go
new file mode 100644 (file)
index 0000000..b5440d8
--- /dev/null
@@ -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),
+       }}
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/refactor.go
new file mode 100644 (file)
index 0000000..27b9750
--- /dev/null
@@ -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
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/fx.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/fx.go
new file mode 100644 (file)
index 0000000..c846a53
--- /dev/null
@@ -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
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/isnamed.go
new file mode 100644 (file)
index 0000000..f2affec
--- /dev/null
@@ -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
+}
index a5cd7e8dbfcb9d9f7f46475dc04067ccec634fa9..fef74a785604804141c192da4cf5da98419faa5d 100644 (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
+}
index e5da0495111ba1b08ad7debfe4e5181faea20e62..26499cdd2e70fb993db65456ccca4ff9344e5435 100644 (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) }
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/varkind_go124.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/varkind_go124.go
new file mode 100644 (file)
index 0000000..17b1804
--- /dev/null
@@ -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) {}
index d272949c17718996d7c09589b45284636181dd86..453bba2ad5e8402f3ddf4104e4e040ae2180f43a 100644 (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:
index 133271355f2cafc4257075ea8eed6d8cada4f0ad..f4c60cc3281b78dc7cd772d0edf60c70702540ed 100644 (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
index f134f0c7b571da9e33ec213b3e96406309641fef..c5e901b9ef22e8c2e53d18c39329a9560e172132 100644 (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
 )
index f24bea029a223937910d50ed8f321c7c73018a3d..4a52682161fc237ee3640df3b97cedc6ed7b0f2d 100644 (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=
index f09e102efb7b1ce6649812cb1431c84652985a64..0df276321cf09e7d7bf0ab4ab6e6369c8e7a5728 100644 (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) {
index 9d2ae547b5ed4d91ba20783bd00eec10fc2d0247..fb8273236dde6e8cfa4d0ddd02437906be1603d3 100644 (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)),
        }
 }
 
index a2a0c0b3e85f23b7bb2ed350281e5e07d629bffb..f1e33686ed0626c4599dbc35c40f440ad442e522 100644 (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