mirror of
https://github.com/golang/go.git
synced 2026-06-28 03:40:37 +00:00
all: update to x/tools@b38156a7
This rolls up to include CL 780780, preparing us for the freeze. Change-Id: I69561591332e377fddac20af99d85cf92f8bc2cf Reviewed-on: https://go-review.googlesource.com/c/go/+/780801 TryBot-Bypass: Mark Freeman <markfreeman@google.com> Reviewed-by: Neal Patel <nealpatel@google.com>
This commit is contained in:
parent
f571fc93b0
commit
bbf60f3bbd
34 changed files with 1429 additions and 587 deletions
|
|
@ -9,9 +9,9 @@ require (
|
|||
golang.org/x/mod v0.36.1-0.20260513122029-343ee60345a1
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.44.0
|
||||
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6
|
||||
golang.org/x/term v0.39.0
|
||||
golang.org/x/tools v0.44.1-0.20260414062052-55fb96ff894f
|
||||
golang.org/x/tools v0.45.1-0.20260520205638-b38156a7a9f5
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
|||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4=
|
||||
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE=
|
||||
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/tools v0.44.1-0.20260414062052-55fb96ff894f h1:OsDhJTPRMdqueEUhZ6K1sdC07K6rj9i4RYTQGF6zSHA=
|
||||
golang.org/x/tools v0.44.1-0.20260414062052-55fb96ff894f/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
golang.org/x/tools v0.45.1-0.20260520205638-b38156a7a9f5 h1:jqdNq3qAaJT9zQL5Cbq/TRYEdoLZmystI2hoCyAsAuw=
|
||||
golang.org/x/tools v0.45.1-0.20260520205638-b38156a7a9f5/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
|
||||
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=
|
||||
|
|
|
|||
144
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go
generated
vendored
144
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/composite/composite.go
generated
vendored
|
|
@ -10,6 +10,7 @@ import (
|
|||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
|
|
@ -49,108 +50,88 @@ func init() {
|
|||
Analyzer.Flags.BoolVar(&whitelist, "whitelist", whitelist, "use composite white list; for testing only")
|
||||
}
|
||||
|
||||
// runUnkeyedLiteral checks if a composite literal is a struct literal with
|
||||
// unkeyed fields.
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
|
||||
nodeFilter := []ast.Node{
|
||||
(*ast.CompositeLit)(nil),
|
||||
}
|
||||
inspect.Preorder(nodeFilter, func(n ast.Node) {
|
||||
cl := n.(*ast.CompositeLit)
|
||||
for curLit := range inspect.Root().Preorder((*ast.CompositeLit)(nil)) {
|
||||
complit := curLit.Node().(*ast.CompositeLit)
|
||||
|
||||
typ := pass.TypesInfo.Types[cl].Type
|
||||
if typ == nil {
|
||||
// cannot determine composite literals' type, skip it
|
||||
return
|
||||
// Skip empty or partly/fully keyed literals.
|
||||
if len(complit.Elts) == 0 ||
|
||||
slices.ContainsFunc(complit.Elts, func(e ast.Expr) bool { return is[*ast.KeyValueExpr](e) }) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find struct type.
|
||||
// (For a type parameter, choose an arbitrary term.)
|
||||
typ := pass.TypesInfo.Types[complit].Type
|
||||
if typ == nil {
|
||||
continue // no type info
|
||||
}
|
||||
terms, err := typeparams.NormalTerms(typ)
|
||||
if err != nil || len(terms) == 0 {
|
||||
continue // invalid or empty type
|
||||
}
|
||||
t := terms[0].Type()
|
||||
strct, ok := typeparams.Deref(t).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
continue // not a struct literal
|
||||
}
|
||||
if isSamePackageType(pass, t) {
|
||||
continue // allow unkeyed literals for structs in same package
|
||||
}
|
||||
|
||||
// Allow whitelisted types.
|
||||
typeName := typ.String()
|
||||
if whitelist && unkeyedLiteral[typeName] {
|
||||
// skip whitelisted types
|
||||
return
|
||||
}
|
||||
var structuralTypes []types.Type
|
||||
switch typ := types.Unalias(typ).(type) {
|
||||
case *types.TypeParam:
|
||||
terms, err := typeparams.StructuralTerms(typ)
|
||||
if err != nil {
|
||||
return // invalid type
|
||||
}
|
||||
for _, term := range terms {
|
||||
structuralTypes = append(structuralTypes, term.Type())
|
||||
}
|
||||
default:
|
||||
structuralTypes = append(structuralTypes, typ)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, typ := range structuralTypes {
|
||||
strct, ok := typeparams.Deref(typ).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
// skip non-struct composite literals
|
||||
continue
|
||||
}
|
||||
if isLocalType(pass, typ) {
|
||||
// allow unkeyed locally defined composite literal
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the struct contains an unkeyed field
|
||||
allKeyValue := true
|
||||
var suggestedFixAvailable = len(cl.Elts) == strct.NumFields()
|
||||
var missingKeys []analysis.TextEdit
|
||||
for i, e := range cl.Elts {
|
||||
if _, ok := e.(*ast.KeyValueExpr); !ok {
|
||||
allKeyValue = false
|
||||
if i >= strct.NumFields() {
|
||||
break
|
||||
}
|
||||
field := strct.Field(i)
|
||||
if !field.Exported() {
|
||||
// Adding unexported field names for structs not defined
|
||||
// locally will not work.
|
||||
suggestedFixAvailable = false
|
||||
break
|
||||
}
|
||||
missingKeys = append(missingKeys, analysis.TextEdit{
|
||||
Pos: e.Pos(),
|
||||
End: e.Pos(),
|
||||
NewText: fmt.Appendf(nil, "%s: ", field.Name()),
|
||||
})
|
||||
// If there is one value per field,
|
||||
// offer to fill in the field names.
|
||||
var fixes []analysis.SuggestedFix
|
||||
if len(complit.Elts) == strct.NumFields() {
|
||||
var edits []analysis.TextEdit
|
||||
for i, elt := range complit.Elts {
|
||||
field := strct.Field(i)
|
||||
// We cannot fill in the name of an
|
||||
// exported field from another package.
|
||||
if !field.Exported() {
|
||||
edits = nil
|
||||
break
|
||||
}
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: elt.Pos(),
|
||||
End: elt.Pos(),
|
||||
NewText: fmt.Appendf(nil, "%s: ", field.Name()),
|
||||
})
|
||||
}
|
||||
if allKeyValue {
|
||||
// all the struct fields are keyed
|
||||
continue
|
||||
}
|
||||
|
||||
diag := analysis.Diagnostic{
|
||||
Pos: cl.Pos(),
|
||||
End: cl.End(),
|
||||
Message: fmt.Sprintf("%s struct literal uses unkeyed fields", typeName),
|
||||
}
|
||||
if suggestedFixAvailable {
|
||||
diag.SuggestedFixes = []analysis.SuggestedFix{{
|
||||
if edits != nil {
|
||||
fixes = []analysis.SuggestedFix{{
|
||||
Message: "Add field names to struct literal",
|
||||
TextEdits: missingKeys,
|
||||
TextEdits: edits,
|
||||
}}
|
||||
}
|
||||
pass.Report(diag)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: complit.Pos(),
|
||||
End: complit.End(),
|
||||
Message: fmt.Sprintf("%s struct literal uses unkeyed fields", typeName),
|
||||
SuggestedFixes: fixes,
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// isLocalType reports whether typ belongs to the same package as pass.
|
||||
// TODO(adonovan): local means "internal to a function"; rename to isSamePackageType.
|
||||
func isLocalType(pass *analysis.Pass, typ types.Type) bool {
|
||||
// isSamePackageType reports whether typ belongs to the same package as pass.
|
||||
func isSamePackageType(pass *analysis.Pass, typ types.Type) bool {
|
||||
switch x := types.Unalias(typ).(type) {
|
||||
case *types.Struct:
|
||||
// struct literals are local types
|
||||
return true
|
||||
case *types.Pointer:
|
||||
return isLocalType(pass, x.Elem())
|
||||
return isSamePackageType(pass, x.Elem())
|
||||
case interface{ Obj() *types.TypeName }: // *Named or *TypeParam (aliases were removed already)
|
||||
// names in package foo are local to foo_test too
|
||||
return x.Obj().Pkg() != nil &&
|
||||
|
|
@ -158,3 +139,8 @@ func isLocalType(pass *analysis.Pass, typ types.Type) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func is[T any](x any) bool {
|
||||
_, ok := x.(T)
|
||||
return ok
|
||||
}
|
||||
|
|
|
|||
11
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go
generated
vendored
11
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/errorsas/errorsas.go
generated
vendored
|
|
@ -2,8 +2,8 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The errorsas package defines an Analyzer that checks that the second argument to
|
||||
// errors.As is a pointer to a type implementing error.
|
||||
// Package errorsas defines an Analyzer that checks that the second argument to
|
||||
// [errors.As] is a pointer to a type implementing error.
|
||||
package errorsas
|
||||
|
||||
import (
|
||||
|
|
@ -19,7 +19,12 @@ import (
|
|||
const Doc = `report passing non-pointer or non-error values to errors.As
|
||||
|
||||
The errorsas analyzer reports calls to errors.As where the type
|
||||
of the second argument is not a pointer to a type implementing error.`
|
||||
of the second argument is not a pointer to a type implementing error.
|
||||
For example:
|
||||
|
||||
var unwrappedErr net.DNSError
|
||||
errors.As(err, unwrappedErr) // should use &unwrappedErr, DNSError.Error has a pointer receiver
|
||||
`
|
||||
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "errorsas",
|
||||
|
|
|
|||
6
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/doc.go
generated
vendored
6
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/doc.go
generated
vendored
|
|
@ -61,6 +61,12 @@ inliner machinery is capable of replacing f by a function literal,
|
|||
func(){...}(). However, the inline analyzer discards all such
|
||||
"literalizations" unconditionally, again on grounds of style.)
|
||||
|
||||
A call to a function F from its dedicated test (TestF) is not inlined,
|
||||
since the purpose of the test is to exercise F itself, even when
|
||||
it's a deprecated function to which other calls should be inlined.
|
||||
This is not true for type aliases; see https://go.dev/issue/79271.
|
||||
See further discussion in https://go.dev/issue/79272.
|
||||
|
||||
## Constants
|
||||
|
||||
Given a constant that is marked for inlining, like this one:
|
||||
|
|
|
|||
30
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
generated
vendored
30
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
generated
vendored
|
|
@ -339,23 +339,19 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
|
|||
expr = curId.Node().(*ast.IndexListExpr)
|
||||
}
|
||||
|
||||
fieldType := curId
|
||||
if fieldType.ParentEdgeKind() == edge.StarExpr_X {
|
||||
fieldType = fieldType.Parent()
|
||||
}
|
||||
if fieldType.ParentEdgeKind() == edge.Field_Type {
|
||||
field := fieldType.Parent().Node().(*ast.Field)
|
||||
if len(field.Names) == 0 {
|
||||
identicalName := false
|
||||
if rhs, ok := alias.Rhs().(*types.Named); ok {
|
||||
identicalName = alias.Obj().Name() == rhs.Obj().Name()
|
||||
}
|
||||
if !identicalName {
|
||||
// Type is embedded, inlining the alias will cause
|
||||
// the field name to be changed, which might break
|
||||
// programs in terms of backwards compatibility.
|
||||
return
|
||||
}
|
||||
// Reject inlining of a type alias used to declare an embedded
|
||||
// struct field if doing so would change the field's name.
|
||||
if v, ok := a.pass.TypesInfo.Defs[id].(*types.Var); ok && v.Embedded() {
|
||||
identicalName := false
|
||||
// TODO(adonovan): should we allow a pointer (type A = *pkg.A)?
|
||||
if rhs, ok := alias.Rhs().(*types.Named); ok {
|
||||
identicalName = alias.Obj().Name() == rhs.Obj().Name()
|
||||
}
|
||||
if !identicalName {
|
||||
// Type is embedded, inlining the alias will cause
|
||||
// the field name to be changed, which might break
|
||||
// programs in terms of backwards compatibility.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
8
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go
generated
vendored
8
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go
generated
vendored
|
|
@ -19,14 +19,13 @@ import (
|
|||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
|
||||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/goplsexport"
|
||||
"golang.org/x/tools/internal/refactor"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
var atomicTypesAnalyzer = &analysis.Analyzer{
|
||||
var AtomicTypesAnalyzer = &analysis.Analyzer{
|
||||
Name: "atomictypes",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "atomictypes"),
|
||||
Requires: []*analysis.Analyzer{
|
||||
|
|
@ -37,11 +36,6 @@ var atomicTypesAnalyzer = &analysis.Analyzer{
|
|||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#atomictypes",
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Export to gopls until this is a published modernizer.
|
||||
goplsexport.AtomicTypesModernizer = atomicTypesAnalyzer
|
||||
}
|
||||
|
||||
// TODO(mkalil): support the Pointer variants.
|
||||
// Consider the following function signatures for pointer loading:
|
||||
// func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
|
||||
|
|
|
|||
41
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
generated
vendored
41
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
generated
vendored
|
|
@ -111,6 +111,31 @@ The any analyzer suggests replacing uses of the empty interface type,
|
|||
`interface{}`, with the `any` alias, which was introduced in Go 1.18.
|
||||
This is a purely stylistic change that makes code more readable.
|
||||
|
||||
# Analyzer embedlit
|
||||
|
||||
embedlit: simplify references to embedded fields in composite literals
|
||||
|
||||
The embedlit analyzer suggests removing redundant embedded field type specifiers
|
||||
from composite literals. Go1.27 introduced the ability to directly initialize
|
||||
fields promoted from embedded struct types without a nested literal. For
|
||||
example, given the following structs:
|
||||
|
||||
type T struct {
|
||||
U
|
||||
}
|
||||
|
||||
type U struct {
|
||||
x int
|
||||
}
|
||||
|
||||
A composite literal such as
|
||||
|
||||
t := T{U: U{x: 1}}
|
||||
|
||||
would become
|
||||
|
||||
t := T{x: 1}
|
||||
|
||||
# Analyzer errorsastype
|
||||
|
||||
errorsastype: replace errors.As with errors.AsType[T]
|
||||
|
|
@ -142,6 +167,9 @@ The fmtappendf analyzer suggests replacing `[]byte(fmt.Sprintf(...))` with
|
|||
by Sprintf, making the code more efficient. The suggestion also applies to
|
||||
fmt.Sprint and fmt.Sprintln.
|
||||
|
||||
Since its fix is not a Pareto improvement, fmtappendf is disabled by default in
|
||||
the `go fix` analyzer suite; see golang/go#77581.
|
||||
|
||||
# Analyzer forvar
|
||||
|
||||
forvar: remove redundant re-declaration of loop variables
|
||||
|
|
@ -424,6 +452,16 @@ It also handles variants using [strings.IndexByte] instead of Index, or the byte
|
|||
|
||||
Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use.
|
||||
|
||||
It also replaces [strings.SplitN](s, sep, 2)[0] and [strings.Split](s, sep)[0] with the "before" result of strings.Cut, when sep is a non-empty string constant:
|
||||
|
||||
x := strings.SplitN(s, sep, 2)[0]
|
||||
|
||||
is replaced by:
|
||||
|
||||
x, _, _ := strings.Cut(s, sep)
|
||||
|
||||
The fix is only offered when sep is a non-empty string literal. When sep is a variable or the empty string, the semantics differ (strings.Split(s, "")[0] returns the first character of s, but strings.Cut(s, "").before is ""), so no fix is suggested.
|
||||
|
||||
# Analyzer stringscutprefix
|
||||
|
||||
stringscutprefix: replace HasPrefix/TrimPrefix with CutPrefix
|
||||
|
|
@ -506,6 +544,9 @@ is replaced by:
|
|||
|
||||
This avoids quadratic memory allocation and improves performance.
|
||||
|
||||
No diagnostics are issued in tests, where data sizes are often
|
||||
small and asymptotic performance is not a security concern.
|
||||
|
||||
The analyzer requires that all references to s before the final uses
|
||||
are += operations. To avoid warning about trivial cases, at least one
|
||||
must appear within a loop. The variable s must be a local
|
||||
|
|
|
|||
406
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/embedlit.go
generated
vendored
Normal file
406
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/embedlit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
// Copyright 2026 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 modernize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
|
||||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/moreiters"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
var EmbedLitAnalyzer = &analysis.Analyzer{
|
||||
Name: "embedlit",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "embedlit"),
|
||||
Requires: []*analysis.Analyzer{
|
||||
inspect.Analyzer,
|
||||
typeindexanalyzer.Analyzer,
|
||||
},
|
||||
Run: runEmbedLit,
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#embedlit",
|
||||
}
|
||||
|
||||
// Go1.27 introduced the ability to directly access embedded struct fields.
|
||||
// The embedlit modernizer suggests two types of fixes that use this feature:
|
||||
// 1. Removing redundant field type specifiers in embedded struct fields.
|
||||
// 2. Moving embedded struct field assignments inside of the struct literal
|
||||
// initialization.
|
||||
func runEmbedLit(pass *analysis.Pass) (any, error) {
|
||||
var (
|
||||
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
|
||||
info = pass.TypesInfo
|
||||
)
|
||||
for curLit := range inspect.Root().Preorder((*ast.CompositeLit)(nil)) {
|
||||
if curLit.ParentEdgeKind() != edge.KeyValueExpr_Value { // non-nested comp lit
|
||||
// TODO(mkalil): Figure out how to handle addition/removal of commas in
|
||||
// the comp lit when we observe code where both patterns apply. (This will
|
||||
// likely require a significant amount of work). For now, only apply edits
|
||||
// from one pattern at a time.
|
||||
if !embedlitUnnest(pass, info, curLit) {
|
||||
err := embedlitCombine(pass, index, info, curLit) // calls pass.ReadFile
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Pattern A: removing unneeded embedded field type specifier from the struct
|
||||
// literal.
|
||||
// T{U: U{f: v, ...}} => T{f: v, ...}
|
||||
// It returns true if it reported a diagnostic with edits.
|
||||
func embedlitUnnest(pass *analysis.Pass, info *types.Info, curLit inspector.Cursor) bool {
|
||||
var (
|
||||
edits []analysis.TextEdit
|
||||
names []string // names of the embedded field types that can be removed
|
||||
lit = curLit.Node().(*ast.CompositeLit)
|
||||
compLitType = info.TypeOf(lit)
|
||||
)
|
||||
|
||||
// checkLit determines whether any of the fields in the given struct literal can
|
||||
// be promoted, and calculates the corresponding edits.
|
||||
var checkLit func(lit *ast.CompositeLit)
|
||||
checkLit = func(lit *ast.CompositeLit) {
|
||||
for _, elt := range lit.Elts {
|
||||
// Can't promote an unkeyed field; would result in a syntax error.
|
||||
if kv, ok := elt.(*ast.KeyValueExpr); ok {
|
||||
if innerLit := isEmbeddedFieldLit(info, compLitType, kv); innerLit != nil {
|
||||
// Emit edits to delete the unnecessary embedded field type specifier
|
||||
// and its closing brace.
|
||||
closingPos := innerLit.Rbrace
|
||||
if len(innerLit.Elts) > 0 {
|
||||
// Delete any inner trailing commas or white space. Extra trailing commas
|
||||
// would result in invalid code.
|
||||
closingPos = innerLit.Elts[len(innerLit.Elts)-1].End()
|
||||
}
|
||||
file := astutil.EnclosingFile(curLit)
|
||||
// Enable modernizer only for Go1.27.
|
||||
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_27) {
|
||||
return
|
||||
}
|
||||
// If any comments overlap with the range to delete, don't suggest a fix.
|
||||
if !moreiters.Empty(astutil.Comments(file, kv.Pos(), innerLit.Lbrace+1)) ||
|
||||
!moreiters.Empty(astutil.Comments(file, closingPos, innerLit.Rbrace+1)) {
|
||||
continue
|
||||
}
|
||||
edits = append(edits, []analysis.TextEdit{
|
||||
// T{U: U{f: v, ...}}
|
||||
// ----- -
|
||||
{
|
||||
// Delete the key and the opening brace of the inner struct literal.
|
||||
Pos: kv.Pos(),
|
||||
End: innerLit.Lbrace + 1,
|
||||
},
|
||||
{
|
||||
// Delete the corresponding closing brace, including preceding
|
||||
// white space or commas. Failing to delete trailing commas may
|
||||
// result in invalid code.
|
||||
Pos: closingPos,
|
||||
End: innerLit.Rbrace + 1,
|
||||
},
|
||||
}...)
|
||||
names = append(names, kv.Key.(*ast.Ident).Name)
|
||||
checkLit(innerLit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checkLit(lit)
|
||||
if len(edits) > 0 {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: curLit.Node().Pos(),
|
||||
End: curLit.Node().End(),
|
||||
Message: "embedded field type can be removed from struct literal",
|
||||
SuggestedFixes: []analysis.SuggestedFix{
|
||||
{
|
||||
Message: fmt.Sprintf("Remove embedded field type%s %s", cond(len(names) == 1, "", "s"), strings.Join(names, ", ")),
|
||||
TextEdits: edits,
|
||||
},
|
||||
},
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Pattern B: moving embedded field assignments inside the struct literal
|
||||
// initialization.
|
||||
// t := T{...}; t.x = x => t := T{..., x: x}
|
||||
// (or var t = ...)
|
||||
func embedlitCombine(pass *analysis.Pass, index *typeindex.Index, info *types.Info, curLit inspector.Cursor) error {
|
||||
compLit := curLit.Node().(*ast.CompositeLit)
|
||||
if !moreiters.Every(slices.Values(compLit.Elts), func(e ast.Expr) bool {
|
||||
return is[*ast.KeyValueExpr](e)
|
||||
}) {
|
||||
// Promoting additional embedded fields would result in mixing keyed and
|
||||
// unkeyed fields, which isn't allowed.
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
// Ident for "t" in the assignment.
|
||||
lhs *ast.Ident
|
||||
// The cursor representing the statement that initializes the comp lit "t".
|
||||
// We use its siblings to search for field assignments and verify that there
|
||||
// are no intervening statements, in case those statements observe "t".
|
||||
curStmt inspector.Cursor
|
||||
)
|
||||
switch curLit.ParentEdgeKind() {
|
||||
case edge.AssignStmt_Rhs:
|
||||
assign := curLit.Parent().Node().(*ast.AssignStmt)
|
||||
// TODO(mkalil): Handle lhs forms that aren't idents, i.e. x.y[i] = T{...}.
|
||||
if id, ok := assign.Lhs[curLit.ParentEdgeIndex()].(*ast.Ident); ok {
|
||||
lhs = id
|
||||
curStmt = curLit.Parent()
|
||||
}
|
||||
case edge.ValueSpec_Values:
|
||||
spec := curLit.Parent().Node().(*ast.ValueSpec)
|
||||
lhs = spec.Names[curLit.ParentEdgeIndex()]
|
||||
if decl, ok := moreiters.First(curLit.Enclosing((*ast.DeclStmt)(nil))); ok {
|
||||
curStmt = decl
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if lhs == nil || !curStmt.Valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
tObj = info.ObjectOf(lhs)
|
||||
// Marks the contiguous block of embedded field assign statements that will
|
||||
// be moved into the struct initialization.
|
||||
firstStmt, lastStmt inspector.Cursor
|
||||
)
|
||||
stmtloop:
|
||||
for {
|
||||
var ok bool
|
||||
curStmt, ok = curStmt.NextSibling()
|
||||
if !ok {
|
||||
break // end of (e.g.) block
|
||||
}
|
||||
// All embedded field value assignments must immediately follow the struct
|
||||
// initialization.
|
||||
assign, ok := curStmt.Node().(*ast.AssignStmt)
|
||||
if !ok || len(assign.Lhs) != 1 || !(assign.Tok == token.ASSIGN || assign.Tok == token.DEFINE) {
|
||||
// TODO(mkalil): handle multi-assignments like t.x, t.y = 1, 2
|
||||
break
|
||||
}
|
||||
expr := assign.Lhs[0]
|
||||
sel, ok := expr.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
// Verify that sel.X refers to the same object as "t"
|
||||
selXId, ok := sel.X.(*ast.Ident)
|
||||
if !ok {
|
||||
// TODO(mkalil): handle deeply nested expressions like t.B.x
|
||||
break
|
||||
}
|
||||
obj := info.ObjectOf(selXId)
|
||||
if obj != tObj {
|
||||
break
|
||||
}
|
||||
rhsCur := curStmt.ChildAt(edge.AssignStmt_Rhs, 0)
|
||||
if uses(index, rhsCur, tObj) {
|
||||
break
|
||||
}
|
||||
for c := range rhsCur.Preorder((*ast.Ident)(nil)) {
|
||||
id := c.Node().(*ast.Ident)
|
||||
// If the rhs uses a value of t (e.g. t.x = t.y), don't suggest a fix because
|
||||
// we can't evaluate t.y when constructing the new literal.
|
||||
if info.ObjectOf(id) == tObj {
|
||||
break stmtloop
|
||||
}
|
||||
// Note: we don't need to worry about expressions with side effects
|
||||
// changing the behavior when moved inside the comp lit. The order of
|
||||
// effects will be preserved because we preserve the order of the key
|
||||
// value pairs inside the comp lit.
|
||||
}
|
||||
if !firstStmt.Valid() {
|
||||
firstStmt = curStmt
|
||||
}
|
||||
lastStmt = curStmt
|
||||
}
|
||||
|
||||
if !firstStmt.Valid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := astutil.EnclosingFile(curLit)
|
||||
// Enable modernizer only for Go1.27.
|
||||
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_27) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read file content to determine if the struct lit has a trailing comma
|
||||
// after its last element.
|
||||
tokFile := pass.Fset.File(compLit.Rbrace)
|
||||
filename := tokFile.Name()
|
||||
src, err := pass.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasTrailingComma := false
|
||||
if len(compLit.Elts) > 0 {
|
||||
lastElt := compLit.Elts[len(compLit.Elts)-1]
|
||||
lastEltOffset := tokFile.Offset(lastElt.End())
|
||||
rbraceOffset := tokFile.Offset(compLit.Rbrace)
|
||||
hasTrailingComma = bytes.Contains(src[lastEltOffset:rbraceOffset], []byte(","))
|
||||
}
|
||||
var edits []analysis.TextEdit
|
||||
// Emit edits to move the field assignment into the struct lit while
|
||||
// removing it from its current place.
|
||||
// t := T{...}; t.x = v
|
||||
// ----- --- -
|
||||
// t := T{..., x: v}
|
||||
|
||||
// Add a trailing comma before the closing brace of compLit if one doesn't
|
||||
// exist, and delete the closing brace itself.
|
||||
// t := T{...}; t.x = v
|
||||
// -
|
||||
// t := T{..., t.x = v
|
||||
if len(compLit.Elts) > 0 && !hasTrailingComma {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: compLit.Rbrace,
|
||||
End: compLit.Rbrace + 1,
|
||||
NewText: []byte(","),
|
||||
})
|
||||
} else {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: compLit.Rbrace,
|
||||
End: compLit.Rbrace + 1,
|
||||
})
|
||||
}
|
||||
|
||||
// For each assignment:
|
||||
// t.x = v
|
||||
// -- ---
|
||||
// x : v
|
||||
curStmt = firstStmt
|
||||
var prevStmt inspector.Cursor
|
||||
for {
|
||||
assign := curStmt.Node().(*ast.AssignStmt)
|
||||
expr := assign.Lhs[0]
|
||||
sel := expr.(*ast.SelectorExpr)
|
||||
// Delete "t."
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: assign.Pos(),
|
||||
End: sel.Sel.Pos(),
|
||||
})
|
||||
// Replace "=" with ":"
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: expr.End(),
|
||||
End: assign.TokPos + 1,
|
||||
NewText: []byte(":"),
|
||||
})
|
||||
|
||||
// Add a comma after the previous assignment if this is not the first one.
|
||||
if prevStmt.Valid() {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: prevStmt.Node().End(),
|
||||
NewText: []byte(","),
|
||||
})
|
||||
}
|
||||
|
||||
// For the last assignment, add the closing brace of the struct lit.
|
||||
if curStmt == lastStmt {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: assign.End(),
|
||||
NewText: []byte("}"),
|
||||
})
|
||||
break
|
||||
}
|
||||
prevStmt = curStmt
|
||||
curStmt, _ = curStmt.NextSibling() // can't fail because we break out of the loop when we hit lastStmt
|
||||
}
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: curLit.Node().Pos(),
|
||||
End: curLit.Node().End(),
|
||||
Message: "embedded field assignment can be moved to struct literal",
|
||||
SuggestedFixes: []analysis.SuggestedFix{
|
||||
{
|
||||
Message: "Move embedded field assignment to struct literal",
|
||||
TextEdits: edits,
|
||||
},
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// isEmbeddedFieldLit determines whether elt is a KeyValueExpr "T: T{...}" for
|
||||
// an embedded field for which we can safely remove its type.
|
||||
// If so, it returns the corresponding CompositeLit.
|
||||
// If elt contains an unkeyed field or ambiguous type, it returns nil.
|
||||
func isEmbeddedFieldLit(info *types.Info, topLevelType types.Type, kv *ast.KeyValueExpr) *ast.CompositeLit {
|
||||
obj := keyedField(info, kv)
|
||||
if obj == nil || !obj.Embedded() {
|
||||
return nil
|
||||
}
|
||||
lit, ok := kv.Value.(*ast.CompositeLit)
|
||||
if !ok || len(lit.Elts) == 0 {
|
||||
// Skip if the struct literal is empty.
|
||||
return nil
|
||||
}
|
||||
// We cannot remove this type if any of its nested composite elements have
|
||||
// unkeyed fields or are ambiguous, so we check for those conditions before
|
||||
// returning.
|
||||
for _, elt := range lit.Elts {
|
||||
kv, ok := elt.(*ast.KeyValueExpr)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
obj := keyedField(info, kv)
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
k := kv.Key.(*ast.Ident) // can't fail
|
||||
// Cannot promote an ambiguous type, for example:
|
||||
// type T struct { A; B }
|
||||
// type A struct { x int }
|
||||
// type B struct { x int }
|
||||
// _ = T{A: A{x: 1}}
|
||||
// cannot be simplified to T{x: 1} because T has two different embedded fields called "x".
|
||||
// We also reject composite literals with slice elements, as parentObj will be nil.
|
||||
parentObj, _, _ := types.LookupFieldOrMethod(topLevelType, true, obj.Pkg(), k.Name)
|
||||
if parentObj != obj {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return lit
|
||||
}
|
||||
|
||||
// keyedField reports whether the key of kv is an embedded field type. If so, it
|
||||
// returns the type of the embedded field, otherwise it returns nil.
|
||||
func keyedField(info *types.Info, kv *ast.KeyValueExpr) *types.Var {
|
||||
k, ok := kv.Key.(*ast.Ident)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
obj, ok := info.ObjectOf(k).(*types.Var)
|
||||
if !ok || !obj.IsField() {
|
||||
return nil
|
||||
}
|
||||
return obj
|
||||
}
|
||||
132
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/errorsastype.go
generated
vendored
132
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/errorsastype.go
generated
vendored
|
|
@ -17,14 +17,14 @@ import (
|
|||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
|
||||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/goplsexport"
|
||||
"golang.org/x/tools/internal/moreiters"
|
||||
"golang.org/x/tools/internal/refactor"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
var errorsastypeAnalyzer = &analysis.Analyzer{
|
||||
var ErrorsAsTypeAnalyzer = &analysis.Analyzer{
|
||||
Name: "errorsastype",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "errorsastype"),
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
|
||||
|
|
@ -32,11 +32,6 @@ var errorsastypeAnalyzer = &analysis.Analyzer{
|
|||
Run: errorsastype,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Export to gopls until this is a published modernizer.
|
||||
goplsexport.ErrorsAsTypeModernizer = errorsastypeAnalyzer
|
||||
}
|
||||
|
||||
// errorsastype offers a fix to replace error.As with the newer
|
||||
// errors.AsType[T] following this pattern:
|
||||
//
|
||||
|
|
@ -60,22 +55,15 @@ func init() {
|
|||
//
|
||||
// because the transformation in that case would be ungainly.
|
||||
//
|
||||
// For the negated case (!errors.As), we use !ok instead.
|
||||
//
|
||||
// Note that the cmd/vet suite includes the "errorsas" analyzer, which
|
||||
// detects actual mistakes in the use of errors.As. This logic does
|
||||
// not belong in errorsas because the problems it fixes are merely
|
||||
// stylistic.
|
||||
//
|
||||
// TODO(adonovan): support more cases:
|
||||
//
|
||||
// - Negative cases
|
||||
// var myerr E
|
||||
// if !errors.As(err, &myerr) { ... }
|
||||
// =>
|
||||
// myerr, ok := errors.AsType[E](err)
|
||||
// if !ok { ... }
|
||||
//
|
||||
// - if myerr := new(E); errors.As(err, myerr); { ... }
|
||||
//
|
||||
// - if errors.As(err, myerr) && othercond { ... }
|
||||
func errorsastype(pass *analysis.Pass) (any, error) {
|
||||
var (
|
||||
|
|
@ -89,7 +77,7 @@ func errorsastype(pass *analysis.Pass) (any, error) {
|
|||
continue // spread call: errors.As(pair())
|
||||
}
|
||||
|
||||
v, curDeclStmt := canUseErrorsAsType(info, index, curCall)
|
||||
v, curDeclStmt, curIfStmt := canUseErrorsAsType(info, index, curCall)
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
|
@ -127,64 +115,82 @@ func errorsastype(pass *analysis.Pass) (any, error) {
|
|||
// Choose a name for the "ok" variable.
|
||||
// We generate a new name only if 'ok' is already declared at
|
||||
// curCall and it also used within the if-statement.
|
||||
curIf := curCall.Parent()
|
||||
ifScope := info.Scopes[curIf.Node().(*ast.IfStmt)]
|
||||
okName := freshName(info, index, ifScope, v.Pos(), curCall, curIf, token.NoPos, "ok")
|
||||
ifScope := info.Scopes[curIfStmt.Node().(*ast.IfStmt)]
|
||||
negated := curCall.ParentEdgeKind() == edge.UnaryExpr_X // bool => Tok==NOT
|
||||
okName := freshName(info, index, ifScope, v.Pos(), curCall, curIfStmt, token.NoPos, "ok")
|
||||
// Because we reject any use of v outside the if statement, any use besides
|
||||
// the argument in errors.As must lie inside the if statement.
|
||||
usesV := moreiters.Len(index.Uses(v)) > 1
|
||||
|
||||
edits := append(
|
||||
// delete "var myerr *MyErr"
|
||||
refactor.DeleteStmt(pass.Fset.File(call.Fun.Pos()), curDeclStmt),
|
||||
// if errors.As (err, &myerr) { ... }
|
||||
// ------------- -------------- -------- ----
|
||||
// if myerr, ok := errors.AsType[*MyErr](err ); ok { ... }
|
||||
analysis.TextEdit{
|
||||
// Insert "myerr, ok := " if myerr is used inside the if statement.
|
||||
// Otherwise insert "_, ok := ".
|
||||
Pos: call.Pos(),
|
||||
End: call.Pos(),
|
||||
NewText: fmt.Appendf(nil, "%s, %s := ", cond(usesV, v.Name(), "_"), okName),
|
||||
},
|
||||
analysis.TextEdit{
|
||||
// replace As with AsType[T]
|
||||
Pos: asIdent.Pos(),
|
||||
End: asIdent.End(),
|
||||
NewText: fmt.Appendf(nil, "AsType[%s]", errtype),
|
||||
},
|
||||
analysis.TextEdit{
|
||||
// delete ", &myerr"
|
||||
Pos: call.Args[0].End(),
|
||||
End: call.Args[1].End(),
|
||||
},
|
||||
analysis.TextEdit{
|
||||
// insert "; ok" for errors.AsType or "; !ok" for !errors.AsType
|
||||
Pos: call.End(),
|
||||
End: call.End(),
|
||||
NewText: fmt.Appendf(nil, "; %s%s", cond(negated, "!", ""), okName),
|
||||
},
|
||||
)
|
||||
if negated {
|
||||
unaryExpr := curCall.Parent().Node().(*ast.UnaryExpr)
|
||||
// delete "!"
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: unaryExpr.OpPos,
|
||||
End: unaryExpr.X.Pos(),
|
||||
})
|
||||
}
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: call.Fun.Pos(),
|
||||
End: call.Fun.End(),
|
||||
Message: fmt.Sprintf("errors.As can be simplified using AsType[%s]", errtype),
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Replace errors.As with AsType[%s]", errtype),
|
||||
TextEdits: append(
|
||||
// delete "var myerr *MyErr"
|
||||
refactor.DeleteStmt(pass.Fset.File(call.Fun.Pos()), curDeclStmt),
|
||||
// if errors.As (err, &myerr) { ... }
|
||||
// ------------- -------------- -------- ----
|
||||
// if myerr, ok := errors.AsType[*MyErr](err ); ok { ... }
|
||||
analysis.TextEdit{
|
||||
// insert "myerr, ok := "
|
||||
Pos: call.Pos(),
|
||||
End: call.Pos(),
|
||||
NewText: fmt.Appendf(nil, "%s, %s := ", v.Name(), okName),
|
||||
},
|
||||
analysis.TextEdit{
|
||||
// replace As with AsType[T]
|
||||
Pos: asIdent.Pos(),
|
||||
End: asIdent.End(),
|
||||
NewText: fmt.Appendf(nil, "AsType[%s]", errtype),
|
||||
},
|
||||
analysis.TextEdit{
|
||||
// delete ", &myerr"
|
||||
Pos: call.Args[0].End(),
|
||||
End: call.Args[1].End(),
|
||||
},
|
||||
analysis.TextEdit{
|
||||
// insert "; ok"
|
||||
Pos: call.End(),
|
||||
End: call.End(),
|
||||
NewText: fmt.Appendf(nil, "; %s", okName),
|
||||
},
|
||||
),
|
||||
Message: fmt.Sprintf("Replace errors.As with AsType[%s]", errtype),
|
||||
TextEdits: edits,
|
||||
}},
|
||||
})
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// canUseErrorsAsType reports whether curCall is a call to
|
||||
// errors.As beneath an if statement, preceded by a
|
||||
// declaration of the typed error var. The var must not be
|
||||
// used outside the if statement.
|
||||
func canUseErrorsAsType(info *types.Info, index *typeindex.Index, curCall inspector.Cursor) (_ *types.Var, _ inspector.Cursor) {
|
||||
if curCall.ParentEdgeKind() != edge.IfStmt_Cond {
|
||||
return // not beneath if statement
|
||||
// canUseErrorsAsType reports whether curCall is a call to errors.As beneath an
|
||||
// if statement, preceded by a declaration of the typed error var. The var must
|
||||
// not be used outside the if statement.
|
||||
// If the conditions are met, it returns the error var, the cursor for its
|
||||
// DeclStmt, and the cursor for the IfStmt that contains the call to errors.As.
|
||||
// Otherwise it returns a nil error var.
|
||||
func canUseErrorsAsType(info *types.Info, index *typeindex.Index, curCall inspector.Cursor) (_ *types.Var, curDeclStmt, curIfStmt inspector.Cursor) {
|
||||
curCond := curCall
|
||||
if curCond.ParentEdgeKind() == edge.UnaryExpr_X { // if !errors.As(err, &v)
|
||||
curCond = curCond.Parent()
|
||||
}
|
||||
var (
|
||||
curIfStmt = curCall.Parent()
|
||||
ifStmt = curIfStmt.Node().(*ast.IfStmt)
|
||||
)
|
||||
if curCond.ParentEdgeKind() != edge.IfStmt_Cond {
|
||||
return // not beneath if or unaryexpr
|
||||
}
|
||||
curIfStmt = curCond.Parent()
|
||||
ifStmt := curIfStmt.Node().(*ast.IfStmt)
|
||||
if ifStmt.Init != nil {
|
||||
return // if statement already has an init part
|
||||
}
|
||||
|
|
@ -228,5 +234,5 @@ func canUseErrorsAsType(info *types.Info, index *typeindex.Index, curCall inspec
|
|||
// ...
|
||||
// if errors.As(err, &v) { ... }
|
||||
// with no uses of v outside the IfStmt.
|
||||
return v, curDecl.Parent() // DeclStmt
|
||||
return v, curDecl.Parent(), curIfStmt // curDecl.Parent() is a DeclStmt
|
||||
}
|
||||
|
|
|
|||
35
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
generated
vendored
35
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
generated
vendored
|
|
@ -19,6 +19,7 @@ import (
|
|||
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
|
||||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
|
@ -230,6 +231,7 @@ func minmax(pass *analysis.Pass) (any, error) {
|
|||
if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
|
||||
ifStmt.Init == nil &&
|
||||
isInequality(compare.Op) != 0 &&
|
||||
typesinternal.NoEffects(info, compare) &&
|
||||
isAssignBlock(ifStmt.Body) {
|
||||
// a blank var has no type.
|
||||
if tLHS := info.TypeOf(ifStmt.Body.List[0].(*ast.AssignStmt).Lhs[0]); tLHS != nil && !maybeNaN(tLHS) {
|
||||
|
|
@ -361,18 +363,18 @@ func canUseBuiltinMinMax(fn *types.Func, body *ast.BlockStmt) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
return hasMinMaxLogic(body, fn.Name())
|
||||
return hasMinMaxLogic(body, fn.Name(), sig.Params().At(0).Name(), sig.Params().At(1).Name())
|
||||
}
|
||||
|
||||
// hasMinMaxLogic checks if the function body implements simple min/max logic.
|
||||
func hasMinMaxLogic(body *ast.BlockStmt, funcName string) bool {
|
||||
func hasMinMaxLogic(body *ast.BlockStmt, funcName, param0, param1 string) bool {
|
||||
// Pattern 1: Single if/else statement
|
||||
if len(body.List) == 1 {
|
||||
if ifStmt, ok := body.List[0].(*ast.IfStmt); ok {
|
||||
// Get the "false" result from the else block
|
||||
if elseBlock, ok := ifStmt.Else.(*ast.BlockStmt); ok && len(elseBlock.List) == 1 {
|
||||
if elseRet, ok := elseBlock.List[0].(*ast.ReturnStmt); ok && len(elseRet.Results) == 1 {
|
||||
return checkMinMaxPattern(ifStmt, elseRet.Results[0], funcName)
|
||||
return checkMinMaxPattern(ifStmt, elseRet.Results[0], funcName, param0, param1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -382,7 +384,7 @@ func hasMinMaxLogic(body *ast.BlockStmt, funcName string) bool {
|
|||
if len(body.List) == 2 {
|
||||
if ifStmt, ok := body.List[0].(*ast.IfStmt); ok && ifStmt.Else == nil {
|
||||
if retStmt, ok := body.List[1].(*ast.ReturnStmt); ok && len(retStmt.Results) == 1 {
|
||||
return checkMinMaxPattern(ifStmt, retStmt.Results[0], funcName)
|
||||
return checkMinMaxPattern(ifStmt, retStmt.Results[0], funcName, param0, param1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -394,7 +396,8 @@ func hasMinMaxLogic(body *ast.BlockStmt, funcName string) bool {
|
|||
// ifStmt: the if statement to check
|
||||
// falseResult: the expression returned when the condition is false
|
||||
// funcName: "min" or "max"
|
||||
func checkMinMaxPattern(ifStmt *ast.IfStmt, falseResult ast.Expr, funcName string) bool {
|
||||
// param0, param1: the two parameter names for the function.
|
||||
func checkMinMaxPattern(ifStmt *ast.IfStmt, falseResult ast.Expr, funcName, param0, param1 string) bool {
|
||||
// Must have condition with comparison
|
||||
cmp, ok := ifStmt.Cond.(*ast.BinaryExpr)
|
||||
if !ok {
|
||||
|
|
@ -417,10 +420,24 @@ func checkMinMaxPattern(ifStmt *ast.IfStmt, falseResult ast.Expr, funcName strin
|
|||
return false // Not a comparison operator
|
||||
}
|
||||
|
||||
t := thenRet.Results[0] // "true" result
|
||||
f := falseResult // "false" result
|
||||
x := cmp.X // left operand
|
||||
y := cmp.Y // right operand
|
||||
t := thenRet.Results[0] // "true" result
|
||||
f := falseResult // "false" result
|
||||
x, ok := cmp.X.(*ast.Ident) // left operand
|
||||
if !ok {
|
||||
return false // Not a basic min/max comparison
|
||||
}
|
||||
y, ok := cmp.Y.(*ast.Ident) // right operand
|
||||
if !ok {
|
||||
return false // Not a basic min/max comparison
|
||||
}
|
||||
|
||||
// Check that the min max algorithm uses the function's params
|
||||
// Which param corresponds to which part of the operation doesn't matter,
|
||||
// so we have to try both.
|
||||
if !(param0 == x.Name && param1 == y.Name ||
|
||||
param0 == y.Name && param1 == x.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check operand order and adjust sign accordingly
|
||||
if astutil.EqualSyntax(t, x) && astutil.EqualSyntax(f, y) {
|
||||
|
|
|
|||
24
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
generated
vendored
24
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
generated
vendored
|
|
@ -17,7 +17,6 @@ import (
|
|||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
"golang.org/x/tools/internal/refactor"
|
||||
|
|
@ -35,24 +34,26 @@ var doc string
|
|||
// Suite lists all modernize analyzers.
|
||||
var Suite = []*analysis.Analyzer{
|
||||
AnyAnalyzer,
|
||||
atomicTypesAnalyzer,
|
||||
AtomicTypesAnalyzer,
|
||||
// AppendClippedAnalyzer, // not nil-preserving!
|
||||
// BLoopAnalyzer, // may skew benchmark results, see golang/go#74967
|
||||
FmtAppendfAnalyzer,
|
||||
EmbedLitAnalyzer,
|
||||
ErrorsAsTypeAnalyzer,
|
||||
// FmtAppendfAnalyzer, // makes code less clear, see golang/go#77581
|
||||
ForVarAnalyzer,
|
||||
MapsLoopAnalyzer,
|
||||
MinMaxAnalyzer,
|
||||
NewExprAnalyzer,
|
||||
OmitZeroAnalyzer,
|
||||
plusBuildAnalyzer,
|
||||
PlusBuildAnalyzer,
|
||||
RangeIntAnalyzer,
|
||||
ReflectTypeForAnalyzer,
|
||||
slicesbackwardAnalyzer,
|
||||
slicesBackwardAnalyzer,
|
||||
SlicesContainsAnalyzer,
|
||||
// SlicesDeleteAnalyzer, // not nil-preserving!
|
||||
SlicesSortAnalyzer,
|
||||
stditeratorsAnalyzer,
|
||||
stringscutAnalyzer,
|
||||
StdIteratorsAnalyzer,
|
||||
StringsCutAnalyzer,
|
||||
StringsCutPrefixAnalyzer,
|
||||
StringsSeqAnalyzer,
|
||||
StringsBuilderAnalyzer,
|
||||
|
|
@ -121,15 +122,6 @@ func within(pass *analysis.Pass, pkgs ...string) bool {
|
|||
moreiters.Contains(stdlib.Dependencies(pkgs...), path)
|
||||
}
|
||||
|
||||
// unparenEnclosing removes enclosing parens from cur in
|
||||
// preparation for a call to [Cursor.ParentEdge].
|
||||
func unparenEnclosing(cur inspector.Cursor) inspector.Cursor {
|
||||
for cur.ParentEdgeKind() == edge.ParenExpr_X {
|
||||
cur = cur.Parent()
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
var (
|
||||
builtinAny = types.Universe.Lookup("any")
|
||||
builtinAppend = types.Universe.Lookup("append")
|
||||
|
|
|
|||
8
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/plusbuild.go
generated
vendored
8
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/plusbuild.go
generated
vendored
|
|
@ -11,22 +11,16 @@ import (
|
|||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
"golang.org/x/tools/internal/goplsexport"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
var plusBuildAnalyzer = &analysis.Analyzer{
|
||||
var PlusBuildAnalyzer = &analysis.Analyzer{
|
||||
Name: "plusbuild",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "plusbuild"),
|
||||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#plusbuild",
|
||||
Run: plusbuild,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Export to gopls until this is a published modernizer.
|
||||
goplsexport.PlusBuildModernizer = plusBuildAnalyzer
|
||||
}
|
||||
|
||||
func plusbuild(pass *analysis.Pass) (any, error) {
|
||||
check := func(f *ast.File) {
|
||||
// "//go:build" directives were added in go1.17, but
|
||||
|
|
|
|||
11
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
generated
vendored
11
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
generated
vendored
|
|
@ -356,16 +356,9 @@ func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool {
|
|||
// as it is always true for a variable even when that variable is
|
||||
// used only as an r-value. So we must inspect enclosing syntax.
|
||||
|
||||
cur := curId
|
||||
cur := astutil.UnparenEnclosingCursor(curId)
|
||||
|
||||
// Strip enclosing parens.
|
||||
ek := cur.ParentEdgeKind()
|
||||
for ek == edge.ParenExpr_X {
|
||||
cur = cur.Parent()
|
||||
ek = cur.ParentEdgeKind()
|
||||
}
|
||||
|
||||
switch ek {
|
||||
switch cur.ParentEdgeKind() {
|
||||
case edge.AssignStmt_Lhs:
|
||||
assign := cur.Parent().Node().(*ast.AssignStmt)
|
||||
if assign.Tok != token.DEFINE {
|
||||
|
|
|
|||
4
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
generated
vendored
4
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
generated
vendored
|
|
@ -65,9 +65,9 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
|
|||
// Special cases for TypeOf((*T)(nil)).Elem(), and
|
||||
// TypeOf([]T(nil)).Elem(), needed when T is an interface type.
|
||||
if curCall.ParentEdgeKind() == edge.SelectorExpr_X {
|
||||
curSel := unparenEnclosing(curCall).Parent()
|
||||
curSel := astutil.UnparenEnclosingCursor(curCall).Parent()
|
||||
if curSel.ParentEdgeKind() == edge.CallExpr_Fun {
|
||||
call2 := unparenEnclosing(curSel).Parent().Node().(*ast.CallExpr) // potentially .Elem()
|
||||
call2 := astutil.UnparenEnclosingCursor(curSel).Parent().Node().(*ast.CallExpr) // potentially .Elem()
|
||||
obj := typeutil.Callee(info, call2)
|
||||
if typesinternal.IsMethodNamed(obj, "reflect", "Type", "Elem") {
|
||||
// reflect.TypeOf(expr).Elem()
|
||||
|
|
|
|||
104
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go
generated
vendored
104
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go
generated
vendored
|
|
@ -9,6 +9,7 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
|
|
@ -23,7 +24,7 @@ import (
|
|||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
var slicesbackwardAnalyzer = &analysis.Analyzer{
|
||||
var slicesBackwardAnalyzer = &analysis.Analyzer{
|
||||
Name: "slicesbackward",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "slicesbackward"),
|
||||
Requires: []*analysis.Analyzer{
|
||||
|
|
@ -36,7 +37,7 @@ var slicesbackwardAnalyzer = &analysis.Analyzer{
|
|||
|
||||
func init() {
|
||||
// Export to gopls until this is a published modernizer.
|
||||
goplsexport.SlicesBackwardModernizer = slicesbackwardAnalyzer
|
||||
goplsexport.SlicesBackwardModernizer = slicesBackwardAnalyzer
|
||||
}
|
||||
|
||||
// slicesbackward offers a fix to replace a manually-written backward loop:
|
||||
|
|
@ -147,18 +148,47 @@ func slicesbackward(pass *analysis.Pass) (any, error) {
|
|||
// s[i] — pure element accesses that can be replaced by the value var
|
||||
// other — index used for non-indexing purposes
|
||||
var (
|
||||
sliceIndexes []*ast.IndexExpr
|
||||
otherUses int
|
||||
// First assignment in the loop body of the form "name := s[i]"; or nil.
|
||||
firstSliceIdxAssign *ast.AssignStmt
|
||||
// List of s[i] expressions to replace by the value var (excludes firstSliceIdxAssign, which will be entirely removed).
|
||||
sliceIdxsReplace []*ast.IndexExpr
|
||||
// Total count of s[i] usages.
|
||||
sliceIdxs int
|
||||
// Non-indexing uses of i.
|
||||
otherUses int
|
||||
)
|
||||
for curUse := range index.Uses(indexObj) {
|
||||
if !bodyCur.Contains(curUse) {
|
||||
continue
|
||||
}
|
||||
// Is i in the Index position of an s[i] expression?
|
||||
// If so, we also need to check whether s[i] is an lvalue. If we're
|
||||
// mutating the slice or taking an element's address, a fix will not
|
||||
// be offered.
|
||||
if curUse.ParentEdgeKind() == edge.IndexExpr_Index {
|
||||
idxExpr := curUse.Parent().Node().(*ast.IndexExpr)
|
||||
if isScalarLvalue(pass.TypesInfo, curUse.Parent()) {
|
||||
continue nextLoop
|
||||
}
|
||||
idxCur := curUse.Parent()
|
||||
idxExpr := idxCur.Node().(*ast.IndexExpr)
|
||||
if astutil.EqualSyntax(idxExpr.X, sliceExpr) {
|
||||
sliceIndexes = append(sliceIndexes, idxExpr)
|
||||
sliceIdxs++
|
||||
// If the current statement is the first in the body of the form
|
||||
// "name := s[i]", save it so we can use "name" as the value
|
||||
// variable in slices.Backward. We can also remove the entire assign
|
||||
// statement.
|
||||
if firstSliceIdxAssign == nil && idxCur.ParentEdgeKind() == edge.AssignStmt_Rhs {
|
||||
assignStmt := idxCur.Parent().Node().(*ast.AssignStmt)
|
||||
if len(assignStmt.Lhs) == 1 && assignStmt.Tok == token.DEFINE {
|
||||
// The condition above implies that assignStmt.Lhs[0] is a valid
|
||||
// identifier.
|
||||
firstSliceIdxAssign = assignStmt
|
||||
// We don't need to replace the index expr with the value variable
|
||||
// name if we are going to remove the entire assignment.
|
||||
continue
|
||||
}
|
||||
}
|
||||
sliceIdxsReplace = append(sliceIdxsReplace, idxExpr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
@ -167,15 +197,18 @@ func slicesbackward(pass *analysis.Pass) (any, error) {
|
|||
|
||||
// Build the suggested fix.
|
||||
//
|
||||
// for i := len(s) - 1; i >= 0; i-- { ... s[i] ... }
|
||||
// ---------------------------- ----
|
||||
// _, v := range slices.Backward(s) v
|
||||
// for i := len(s) - 1; i >= 0; i-- { ... s[i] ... }
|
||||
// -------------------------------- ----
|
||||
// for _, v := range slices.Backward(s) { ... v ... }
|
||||
sliceStr := astutil.Format(pass.Fset, sliceExpr)
|
||||
prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Backward", loop.Pos())
|
||||
elemName := freshName(info, index, info.Scopes[loop], loop.Pos(), bodyCur, bodyCur, token.NoPos, "v")
|
||||
elemName := chooseValueName(firstSliceIdxAssign, sliceStr)
|
||||
elemName = freshName(info, index, info.Scopes[loop], loop.Pos(), bodyCur, bodyCur, token.NoPos, elemName)
|
||||
|
||||
// Replace each s[i] with elemName.
|
||||
for _, sx := range sliceIndexes {
|
||||
// Replace each s[i] with elemName (except for in the statement of the
|
||||
// form "name := s[i]" where we might have gotten elemName from - we will
|
||||
// delete this entire statement instead).
|
||||
for _, sx := range sliceIdxsReplace {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: sx.Pos(),
|
||||
End: sx.End(),
|
||||
|
|
@ -183,17 +216,27 @@ func slicesbackward(pass *analysis.Pass) (any, error) {
|
|||
})
|
||||
}
|
||||
|
||||
// Replace the loop header with a range over slices.Backward.
|
||||
var header string
|
||||
if otherUses == 0 && len(sliceIndexes) > 0 {
|
||||
// All uses of i are s[i]; drop the index variable.
|
||||
header = fmt.Sprintf("_, %s := range %sBackward(%s)",
|
||||
elemName, prefix, sliceStr)
|
||||
} else {
|
||||
// i is used for other purposes; keep both index and value.
|
||||
header = fmt.Sprintf("%s, %s := range %sBackward(%s)",
|
||||
indexIdent.Name, elemName, prefix, sliceStr)
|
||||
if firstSliceIdxAssign != nil {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: firstSliceIdxAssign.Pos(),
|
||||
End: firstSliceIdxAssign.End(),
|
||||
})
|
||||
}
|
||||
|
||||
// Replace the loop header with a range over slices.Backward. In
|
||||
// well-typed code, at least one of the index or value variables must be
|
||||
// referenced inside the loop body (otherUses + sliceIndexes > 0).
|
||||
var vars string
|
||||
if otherUses == 0 { // sliceIdxs > 0
|
||||
// All uses of i are s[i]; drop the index variable.
|
||||
vars = fmt.Sprintf("_, %s", elemName)
|
||||
} else if sliceIdxs == 0 { // otherUses > 0
|
||||
// Index i is not used in any s[i] expressions; drop the value variable.
|
||||
vars = indexIdent.Name
|
||||
} else { // otherUses > 0 && sliceIdxs > 0, keep both variables.
|
||||
vars = fmt.Sprintf("%s, %s", indexIdent.Name, elemName)
|
||||
}
|
||||
header := fmt.Sprintf("%s := range %sBackward(%s)", vars, prefix, sliceStr)
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: loop.Init.Pos(),
|
||||
End: loop.Post.End(),
|
||||
|
|
@ -213,3 +256,20 @@ func slicesbackward(pass *analysis.Pass) (any, error) {
|
|||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// chooseValueName uses a heuristic to generate a name for the value variable in
|
||||
// the call to slices.Backward.
|
||||
func chooseValueName(assign *ast.AssignStmt, sliceStr string) string {
|
||||
if assign != nil {
|
||||
return assign.Lhs[0].(*ast.Ident).Name
|
||||
}
|
||||
// Heuristic: remove plural s suffix from slice var
|
||||
// if present, otherwise use first letter.
|
||||
if token.IsIdentifier(sliceStr) && len(sliceStr) > 1 {
|
||||
if single, ok := strings.CutSuffix(sliceStr, "s"); ok {
|
||||
return single
|
||||
}
|
||||
return sliceStr[:1] // first letter (assuming ASCII)
|
||||
}
|
||||
return "v"
|
||||
}
|
||||
|
|
|
|||
14
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
generated
vendored
14
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
generated
vendored
|
|
@ -19,6 +19,7 @@ import (
|
|||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/refactor"
|
||||
"golang.org/x/tools/internal/typeparams"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
|
@ -58,12 +59,8 @@ var SlicesContainsAnalyzer = &analysis.Analyzer{
|
|||
// statement is "found = false" (or vice versa), the
|
||||
// loop becomes "found = [!]slices.Contains(...)".
|
||||
//
|
||||
// It may change cardinality of effects of the "needle" expression.
|
||||
// (Mostly this appears to be a desirable optimization, avoiding
|
||||
// redundantly repeated evaluation.)
|
||||
//
|
||||
// TODO(adonovan): Add a check that needle/predicate expression from
|
||||
// if-statement has no effects. Now the program behavior may change.
|
||||
// It rejects candidates whose needle/predicate expression from the if-statement
|
||||
// has side effects to avoid changes in program behavior.
|
||||
func slicescontains(pass *analysis.Pass) (any, error) {
|
||||
// Skip the analyzer in packages where its
|
||||
// fixes would create an import cycle.
|
||||
|
|
@ -174,6 +171,11 @@ func slicescontains(pass *analysis.Pass) (any, error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Reject if needle/predicate expression has side effects.
|
||||
if !typesinternal.NoEffects(info, arg2) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reject if the body, needle or predicate references either range variable.
|
||||
usesRangeVar := func(n ast.Node) bool {
|
||||
cur, ok := curRange.FindNode(n)
|
||||
|
|
|
|||
8
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
generated
vendored
8
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
generated
vendored
|
|
@ -17,12 +17,11 @@ import (
|
|||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
|
||||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/goplsexport"
|
||||
"golang.org/x/tools/internal/stdlib"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
)
|
||||
|
||||
var stditeratorsAnalyzer = &analysis.Analyzer{
|
||||
var StdIteratorsAnalyzer = &analysis.Analyzer{
|
||||
Name: "stditerators",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "stditerators"),
|
||||
Requires: []*analysis.Analyzer{
|
||||
|
|
@ -32,11 +31,6 @@ var stditeratorsAnalyzer = &analysis.Analyzer{
|
|||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stditerators",
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Export to gopls until this is a published modernizer.
|
||||
goplsexport.StdIteratorsModernizer = stditeratorsAnalyzer
|
||||
}
|
||||
|
||||
// stditeratorsTable records std types that have legacy T.{Len,At}
|
||||
// iteration methods as well as a newer T.All method that returns an
|
||||
// iter.Seq.
|
||||
|
|
|
|||
53
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
generated
vendored
53
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
generated
vendored
|
|
@ -13,6 +13,7 @@ import (
|
|||
"go/types"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
|
|
@ -47,6 +48,7 @@ func stringsbuilder(pass *analysis.Pass) (any, error) {
|
|||
var (
|
||||
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
||||
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
|
||||
info = pass.TypesInfo
|
||||
)
|
||||
|
||||
// Gather all local string variables that appear on the
|
||||
|
|
@ -55,7 +57,7 @@ func stringsbuilder(pass *analysis.Pass) (any, error) {
|
|||
for curAssign := range inspect.Root().Preorder((*ast.AssignStmt)(nil)) {
|
||||
assign := curAssign.Node().(*ast.AssignStmt)
|
||||
if assign.Tok == token.ADD_ASSIGN && is[*ast.Ident](assign.Lhs[0]) {
|
||||
if v, ok := pass.TypesInfo.Uses[assign.Lhs[0].(*ast.Ident)].(*types.Var); ok &&
|
||||
if v, ok := info.Uses[assign.Lhs[0].(*ast.Ident)].(*types.Var); ok &&
|
||||
v.Kind() == types.LocalVar &&
|
||||
types.Identical(v.Type(), builtinString.Type()) {
|
||||
candidates[v] = true
|
||||
|
|
@ -75,7 +77,7 @@ func stringsbuilder(pass *analysis.Pass) (any, error) {
|
|||
// Now check each candidate variable's decl and uses.
|
||||
nextcand:
|
||||
for _, v := range slices.SortedFunc(maps.Keys(candidates), lexicalOrder) {
|
||||
var edits []analysis.TextEdit
|
||||
var edits, postEdits []analysis.TextEdit // postEdits are emitted last
|
||||
|
||||
// Check declaration of s has one of these forms:
|
||||
//
|
||||
|
|
@ -100,6 +102,13 @@ nextcand:
|
|||
if file == lastEditFile && v.Pos() < lastEditEnd {
|
||||
continue
|
||||
}
|
||||
filename := pass.Fset.File(file.FileStart).Name()
|
||||
// Suppress diagnostics in test files, where suggested fixes may increase
|
||||
// verbosity, and performance doesn't matter as much.
|
||||
// See https://go.dev/issue/78613
|
||||
if strings.HasSuffix(filename, "_test.go") {
|
||||
continue
|
||||
}
|
||||
|
||||
ek := def.ParentEdgeKind()
|
||||
if ek == edge.AssignStmt_Lhs &&
|
||||
|
|
@ -121,10 +130,10 @@ nextcand:
|
|||
|
||||
// Add strings import.
|
||||
prefix, importEdits := refactor.AddImport(
|
||||
pass.TypesInfo, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
|
||||
info, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
|
||||
edits = append(edits, importEdits...)
|
||||
|
||||
if isEmptyString(pass.TypesInfo, assign.Rhs[0]) {
|
||||
if isEmptyString(info, assign.Rhs[0]) {
|
||||
// s := ""
|
||||
// ---------------------
|
||||
// var s strings.Builder
|
||||
|
|
@ -171,7 +180,7 @@ nextcand:
|
|||
|
||||
// Add strings import.
|
||||
prefix, importEdits := refactor.AddImport(
|
||||
pass.TypesInfo, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
|
||||
info, astutil.EnclosingFile(def), "strings", "strings", "Builder", v.Pos())
|
||||
edits = append(edits, importEdits...)
|
||||
|
||||
spec := def.Parent().Node().(*ast.ValueSpec)
|
||||
|
|
@ -193,7 +202,7 @@ nextcand:
|
|||
NewText: fmt.Appendf(nil, " %sBuilder", prefix),
|
||||
})
|
||||
|
||||
if len(spec.Values) > 0 && !isEmptyString(pass.TypesInfo, spec.Values[0]) {
|
||||
if len(spec.Values) > 0 && !isEmptyString(info, spec.Values[0]) {
|
||||
if decl.Rparen.IsValid() {
|
||||
// var decl with explicit parens:
|
||||
//
|
||||
|
|
@ -273,11 +282,8 @@ nextcand:
|
|||
)
|
||||
for curUse := range index.Uses(v) {
|
||||
// Strip enclosing parens around Ident.
|
||||
curUse = astutil.UnparenEnclosingCursor(curUse)
|
||||
ek := curUse.ParentEdgeKind()
|
||||
for ek == edge.ParenExpr_X {
|
||||
curUse = curUse.Parent()
|
||||
ek = curUse.ParentEdgeKind()
|
||||
}
|
||||
|
||||
// intervening reports whether cur has an ancestor of
|
||||
// one of the given types that is within the scope of v.
|
||||
|
|
@ -315,20 +321,21 @@ nextcand:
|
|||
// s += expr
|
||||
// ------------- -
|
||||
// s.WriteString(expr)
|
||||
edits = append(edits, []analysis.TextEdit{
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
// replace " += " with ".WriteString("
|
||||
{
|
||||
Pos: assign.Lhs[0].End(),
|
||||
End: assign.Rhs[0].Pos(),
|
||||
NewText: []byte(".WriteString("),
|
||||
},
|
||||
Pos: assign.Lhs[0].End(),
|
||||
End: assign.Rhs[0].Pos(),
|
||||
NewText: []byte(".WriteString("),
|
||||
})
|
||||
|
||||
// Delay inserting the closing parenthesis, in case it overlaps with a
|
||||
// .String() edit, since it would need to come after.
|
||||
postEdits = append(postEdits, analysis.TextEdit{
|
||||
// insert ")"
|
||||
{
|
||||
Pos: assign.End(),
|
||||
End: assign.End(),
|
||||
NewText: []byte(")"),
|
||||
},
|
||||
}...)
|
||||
Pos: assign.End(),
|
||||
End: assign.End(),
|
||||
NewText: []byte(")"),
|
||||
})
|
||||
|
||||
} else if ek == edge.UnaryExpr_X &&
|
||||
curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
|
||||
|
|
@ -357,6 +364,8 @@ nextcand:
|
|||
continue nextcand // no += in a loop; reject
|
||||
}
|
||||
|
||||
edits = append(edits, postEdits...)
|
||||
|
||||
lastEditFile = file
|
||||
lastEditEnd = edits[len(edits)-1].End
|
||||
|
||||
|
|
|
|||
157
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
generated
vendored
157
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
generated
vendored
|
|
@ -21,14 +21,13 @@ import (
|
|||
"golang.org/x/tools/internal/analysis/analyzerutil"
|
||||
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
|
||||
"golang.org/x/tools/internal/astutil"
|
||||
"golang.org/x/tools/internal/goplsexport"
|
||||
"golang.org/x/tools/internal/moreiters"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
"golang.org/x/tools/internal/typesinternal/typeindex"
|
||||
"golang.org/x/tools/internal/versions"
|
||||
)
|
||||
|
||||
var stringscutAnalyzer = &analysis.Analyzer{
|
||||
var StringsCutAnalyzer = &analysis.Analyzer{
|
||||
Name: "stringscut",
|
||||
Doc: analyzerutil.MustExtractDoc(doc, "stringscut"),
|
||||
Requires: []*analysis.Analyzer{
|
||||
|
|
@ -39,11 +38,6 @@ var stringscutAnalyzer = &analysis.Analyzer{
|
|||
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringscut",
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Export to gopls until this is a published modernizer.
|
||||
goplsexport.StringsCutModernizer = stringscutAnalyzer
|
||||
}
|
||||
|
||||
// stringscut offers a fix to replace an occurrence of strings.Index{,Byte} with
|
||||
// strings.{Cut,Contains}, and similar fixes for functions in the bytes package.
|
||||
// Consider some candidate for replacement i := strings.Index(s, substr).
|
||||
|
|
@ -92,7 +86,7 @@ func init() {
|
|||
// }
|
||||
//
|
||||
// If the condition involving `i` is equivalent to i >= 0, then we replace it with
|
||||
// `if ok“.
|
||||
// `if ok`.
|
||||
// If the condition is negated (e.g. equivalent to `i < 0`), we use `if !ok` instead.
|
||||
// If the slices of `s` match `s[:i]` or `s[i+len(substr):]` or their variants listed above,
|
||||
// then we replace them with before and after.
|
||||
|
|
@ -124,6 +118,8 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
|||
bytesIndexByte = index.Object("bytes", "IndexByte")
|
||||
)
|
||||
|
||||
stringsplitCut(pass, index)
|
||||
|
||||
scopeFixCount := make(map[*types.Scope]int) // the number of times we have offered a fix within a given scope in the current pass
|
||||
|
||||
for _, obj := range []types.Object{
|
||||
|
|
@ -149,11 +145,20 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
|||
switch ek, idx := curCall.ParentEdge(); ek {
|
||||
case edge.ValueSpec_Values:
|
||||
// Have: var i = strings.Index(...)
|
||||
// If the call occurs in a multi-value declaration or assignment, don't suggest a fix because it would produce invalid code (See golang/go#78643).
|
||||
spec := curCall.Parent().Node().(*ast.ValueSpec)
|
||||
if len(spec.Names) != 1 {
|
||||
continue
|
||||
}
|
||||
curName := curCall.Parent().ChildAt(edge.ValueSpec_Names, idx)
|
||||
iIdent = curName.Node().(*ast.Ident)
|
||||
case edge.AssignStmt_Rhs:
|
||||
// Have: i := strings.Index(...)
|
||||
// (Must be i's definition.)
|
||||
assign := curCall.Parent().Node().(*ast.AssignStmt)
|
||||
if len(assign.Lhs) != 1 {
|
||||
continue
|
||||
}
|
||||
curLhs := curCall.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
|
||||
iIdent, _ = curLhs.Node().(*ast.Ident) // may be nil
|
||||
}
|
||||
|
|
@ -367,6 +372,129 @@ func stringscut(pass *analysis.Pass) (any, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// stringsplitCut reports patterns where strings.Split or strings.SplitN with
|
||||
// n=2 is immediately indexed at [0], which can be simplified to strings.Cut,
|
||||
// when sep is a non-empty string constant. The transformation is
|
||||
// semantics-preserving only for non-empty sep: strings.Split(s, "")[0]
|
||||
// returns the first character of s, but strings.Cut(s, "").before is "".
|
||||
// For variable sep the value is unknown at analysis time, so we conservatively
|
||||
// skip those cases too.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// x := strings.SplitN(s, ",", 2)[0]
|
||||
// ------ --
|
||||
// x, _, _ := strings.Cut(s, ",")
|
||||
//
|
||||
// Requires Go 1.18 (when strings.Cut was added).
|
||||
func stringsplitCut(pass *analysis.Pass, index *typeindex.Index) {
|
||||
info := pass.TypesInfo
|
||||
|
||||
stringsSplit := index.Object("strings", "Split")
|
||||
stringsSplitN := index.Object("strings", "SplitN")
|
||||
|
||||
for _, obj := range []types.Object{stringsSplit, stringsSplitN} {
|
||||
for curCall := range index.Calls(obj) {
|
||||
callExpr := curCall.Node().(*ast.CallExpr)
|
||||
|
||||
// For SplitN, the third argument must be the integer constant 2.
|
||||
if obj.Name() == "SplitN" && !isIntLiteral(info, callExpr.Args[2], 2) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Sep must be a non-empty constant string.
|
||||
// strings.Split(s, "")[0] returns the first character of s, but
|
||||
// strings.Cut(s, "").before is "", so the semantics differ for
|
||||
// an empty sep. For a variable sep we cannot rule out "" at
|
||||
// analysis time, so we conservatively skip those cases too.
|
||||
sepTV := info.Types[callExpr.Args[1]]
|
||||
if sepTV.Value == nil || constant.StringVal(sepTV.Value) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// The call must be the X of an IndexExpr.
|
||||
if curCall.ParentEdgeKind() != edge.IndexExpr_X {
|
||||
continue
|
||||
}
|
||||
parent := curCall.Parent()
|
||||
indexExpr := parent.Node().(*ast.IndexExpr)
|
||||
|
||||
// The index must be the integer constant 0.
|
||||
if !isZeroIntConst(info, indexExpr.Index) {
|
||||
continue
|
||||
}
|
||||
|
||||
// The IndexExpr must be the sole RHS of an assignment statement.
|
||||
if parent.ParentEdgeKind() != edge.AssignStmt_Rhs {
|
||||
continue
|
||||
}
|
||||
assign := parent.Parent().Node().(*ast.AssignStmt)
|
||||
if assign.Tok != token.DEFINE || len(assign.Lhs) != 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// The LHS must be a single non-blank identifier.
|
||||
lhsIdent, ok := assign.Lhs[0].(*ast.Ident)
|
||||
if !ok || lhsIdent.Name == "_" {
|
||||
continue
|
||||
}
|
||||
|
||||
// strings.Cut requires Go 1.18.
|
||||
if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_18) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the fix.
|
||||
//
|
||||
// x := strings.SplitN(s, sep, 2)[0]
|
||||
// --- ------ ---
|
||||
// x, _, _ := strings.Cut(s, sep)
|
||||
callFunIdent := typesinternal.UsedIdent(info, callExpr.Fun)
|
||||
|
||||
var edits []analysis.TextEdit
|
||||
|
||||
// LHS: insert ", _, _" after x
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: lhsIdent.End(),
|
||||
End: lhsIdent.End(),
|
||||
NewText: []byte(", _, _"),
|
||||
})
|
||||
|
||||
// Function name: Split/SplitN → Cut
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: callFunIdent.Pos(),
|
||||
End: callFunIdent.End(),
|
||||
NewText: []byte("Cut"),
|
||||
})
|
||||
|
||||
// For SplitN: remove the ", 2" third argument.
|
||||
if obj.Name() == "SplitN" {
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: callExpr.Args[1].End(), // after sep
|
||||
End: callExpr.Rparen, // before )
|
||||
})
|
||||
}
|
||||
|
||||
// Remove the "[0]" index expression.
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: indexExpr.Lbrack,
|
||||
End: indexExpr.End(),
|
||||
})
|
||||
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: callExpr.Fun.Pos(),
|
||||
End: callExpr.Fun.End(),
|
||||
Message: fmt.Sprintf("strings.%s call can be simplified using strings.Cut", obj.Name()),
|
||||
Category: "stringscut",
|
||||
SuggestedFixes: []analysis.SuggestedFix{{
|
||||
Message: fmt.Sprintf("Simplify strings.%s call using strings.Cut", obj.Name()),
|
||||
TextEdits: edits,
|
||||
}},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// indexArgValid reports whether expr is a valid strings.Index(_, _) arg
|
||||
// for the transformation. An arg is valid iff it is:
|
||||
// - constant;
|
||||
|
|
@ -387,10 +515,10 @@ func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afte
|
|||
case *ast.Ident:
|
||||
sObj := info.Uses[expr]
|
||||
sUses := index.Uses(sObj)
|
||||
return !hasModifyingUses(info, sUses, afterPos)
|
||||
return !hasModifyingUses(sUses, afterPos)
|
||||
default:
|
||||
// For now, skip instances where s or substr are not
|
||||
// identifers, basic lits, or call expressions of the form
|
||||
// identifiers, basic lits, or call expressions of the form
|
||||
// []byte(s).
|
||||
// TODO(mkalil): Handle s and substr being expressions like ptr.field[i].
|
||||
// From adonovan: We'd need to analyze s and substr to see
|
||||
|
|
@ -487,18 +615,15 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a
|
|||
// hasModifyingUses reports whether any of the uses involve potential
|
||||
// modifications. Uses involving assignments before the "afterPos" won't be
|
||||
// considered.
|
||||
func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPos token.Pos) bool {
|
||||
func hasModifyingUses(uses iter.Seq[inspector.Cursor], afterPos token.Pos) bool {
|
||||
for curUse := range uses {
|
||||
ek := curUse.ParentEdgeKind()
|
||||
if ek == edge.AssignStmt_Lhs {
|
||||
if curUse.Node().Pos() <= afterPos {
|
||||
continue
|
||||
}
|
||||
assign := curUse.Parent().Node().(*ast.AssignStmt)
|
||||
if sameObject(info, assign.Lhs[0], curUse.Node().(*ast.Ident)) {
|
||||
// Modifying use because we are reassigning the value of the object.
|
||||
return true
|
||||
}
|
||||
// Any use on the LHS is a modifying use.
|
||||
return true
|
||||
} else if ek == edge.UnaryExpr_X &&
|
||||
curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
|
||||
// Modifying use because we might be passing the object by reference (an explicit &).
|
||||
|
|
|
|||
2
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
generated
vendored
2
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
generated
vendored
|
|
@ -416,7 +416,7 @@ func match(info *types.Info, arg ast.Expr, param *types.Var) bool {
|
|||
}
|
||||
|
||||
// propagate propagates changes in wrapper (non-None) kind information backwards
|
||||
// through through the wrapper.callers graph of well-formed forwarding calls.
|
||||
// through the wrapper.callers graph of well-formed forwarding calls.
|
||||
func propagate(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) {
|
||||
// Check correct call forwarding.
|
||||
//
|
||||
|
|
|
|||
24
src/cmd/vendor/golang.org/x/tools/go/ast/edge/edge.go
generated
vendored
24
src/cmd/vendor/golang.org/x/tools/go/ast/edge/edge.go
generated
vendored
|
|
@ -12,7 +12,7 @@ import (
|
|||
"reflect"
|
||||
)
|
||||
|
||||
// A Kind describes a field of an ast.Node struct.
|
||||
// A Kind describes a field of an [ast.Node] struct.
|
||||
type Kind uint8
|
||||
|
||||
// String returns a description of the edge kind.
|
||||
|
|
@ -41,21 +41,25 @@ func (k Kind) Get(n ast.Node, idx int) ast.Node {
|
|||
panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n))
|
||||
}
|
||||
v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index)
|
||||
if idx != -1 {
|
||||
v = v.Index(idx) // asserts valid index
|
||||
} else {
|
||||
// (The type assertion below asserts that v is not a slice.)
|
||||
|
||||
if v.Kind() == reflect.Slice {
|
||||
v = v.Index(idx) // asserts valid idx
|
||||
} else if idx != -1 {
|
||||
panic(fmt.Sprintf("%v, Get(%T, %d): cannot index non-slice", v, n, idx))
|
||||
}
|
||||
return v.Interface().(ast.Node) // may be nil
|
||||
|
||||
out, _ := v.Interface().(ast.Node) // may be nil
|
||||
return out
|
||||
}
|
||||
|
||||
// Each [Kind] is named Type_Field, where Type is the
|
||||
// [ast.Node] struct type and Field is the name of the field
|
||||
const (
|
||||
Invalid Kind = iota // for nodes at the root of the traversal
|
||||
|
||||
// Kinds are sorted alphabetically.
|
||||
// Numbering is not stable.
|
||||
// Each is named Type_Field, where Type is the
|
||||
// ast.Node struct type and Field is the name of the field
|
||||
// As of Go1.26 these kinds are sorted alphabetically, but
|
||||
// numbering must be stable, so any new addition of const should
|
||||
// use a new value (be added at the end of the list).
|
||||
|
||||
ArrayType_Elt
|
||||
ArrayType_Len
|
||||
|
|
|
|||
603
src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
generated
vendored
603
src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
generated
vendored
|
|
@ -24,8 +24,10 @@
|
|||
package objectpath
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -124,7 +126,66 @@ func For(obj types.Object) (Path, error) {
|
|||
// An Encoder amortizes the cost of encoding the paths of multiple objects.
|
||||
// The zero value of an Encoder is ready to use.
|
||||
type Encoder struct {
|
||||
scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects
|
||||
pkgIndex map[*types.Package]*pkgIndex
|
||||
}
|
||||
|
||||
// A traversal encapsulates the state of a single traversal of the object/type graph.
|
||||
type traversal struct {
|
||||
pkg *types.Package
|
||||
ix *pkgIndex // non-nil if we are building the index
|
||||
|
||||
target types.Object // the sought symbol (if ix == nil)
|
||||
found Path // the found path (if ix == nil)
|
||||
|
||||
// These maps are used to short circuit cycles through
|
||||
// interface methods, such as occur in the following example:
|
||||
//
|
||||
// type I interface { f() interface{I} }
|
||||
//
|
||||
// See golang/go#68046 for details.
|
||||
seenTParamNames map[*types.TypeName]bool // global cycle breaking through type parameters
|
||||
seenMethods map[*types.Func]bool // global cycle breaking through recursive interfaces
|
||||
}
|
||||
|
||||
// A pkgIndex holds a compressed index of objectpaths of all symbols
|
||||
// (fields, methods, params) requiring search for an entire package.
|
||||
//
|
||||
// The first time a search for a given package is requested, we simply
|
||||
// traverse the type graph for the target object, maintaining the
|
||||
// current object path as a stack. If we find the target object, we
|
||||
// save the path and terminate the main loop (but it's not worth
|
||||
// breaking out of the current recursion).
|
||||
//
|
||||
// On the second search (a pkgIndex exists but its data is nil), we
|
||||
// build an index of the traversal, which we use for all subsequent
|
||||
// searches.
|
||||
//
|
||||
// The traversal index is encoded in the data field as a list of records,
|
||||
// one per node, in preorder. Records are of two types:
|
||||
//
|
||||
// - A record for a package-level object consists of a pair
|
||||
// (parent, nameIndex uvarint), where parent is zero and
|
||||
// nameIndex is the index of the object's name in the sorted
|
||||
// pkg.Scope().Names() slice.
|
||||
//
|
||||
// - A record for a nested node (a segment of an object path)
|
||||
// consists of (parent uvarint, op byte, index uvarint), where
|
||||
// parent is the index of the record for the parent node,
|
||||
// op is the destructuring operator, and index (if op = [AFMTr])
|
||||
// is its integer operand.
|
||||
//
|
||||
// Since data[0] = 0 all nodes have positive offsets. In effect the
|
||||
// encoding is a trie in which each node stores one path segment
|
||||
// and points to the node for its prefix.
|
||||
//
|
||||
// TODO(adonovan): opt: evaluate an only 2-level tree with nodes for
|
||||
// package-level objects and the-rest-of-the-path. One calculation
|
||||
// suggested that it might be similar speed but 30% more compact.
|
||||
type pkgIndex struct {
|
||||
pkg *types.Package
|
||||
data []byte // encoding of traversal; nil if not yet constructed
|
||||
scopeNames []string // memo of pkg.Scope().Names() to avoid O(n) alloc/sort at lookup
|
||||
offsets map[types.Object]uint32 // each object's node offset within encoded traversal data
|
||||
}
|
||||
|
||||
// For returns the path to an object relative to its package,
|
||||
|
|
@ -211,10 +272,9 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
|
|||
if pkg == nil {
|
||||
return "", fmt.Errorf("predeclared %s has no path", obj)
|
||||
}
|
||||
scope := pkg.Scope()
|
||||
|
||||
// 2. package-level object?
|
||||
if scope.Lookup(obj.Name()) == obj {
|
||||
if pkg.Scope().Lookup(obj.Name()) == obj {
|
||||
// Only exported objects (and non-exported types) have a path.
|
||||
// Non-exported types may be referenced by other objects.
|
||||
if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() {
|
||||
|
|
@ -232,19 +292,18 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
|
|||
// have a path.
|
||||
return "", fmt.Errorf("no path for %v", obj)
|
||||
}
|
||||
|
||||
case *types.Const, // Only package-level constants have a path.
|
||||
*types.Label, // Labels are function-local.
|
||||
*types.PkgName: // PkgNames are file-local.
|
||||
return "", fmt.Errorf("no path for %v", obj)
|
||||
|
||||
case *types.Var:
|
||||
// Could be:
|
||||
// - a field (obj.IsField())
|
||||
// - a func parameter or result
|
||||
// - a local var.
|
||||
// Sadly there is no way to distinguish
|
||||
// a param/result from a local
|
||||
// so we must proceed to the find.
|
||||
// A var, if not package-level, must be a
|
||||
// parameter (incl. receiver) or result, or a struct field.
|
||||
if obj.Kind() == types.LocalVar {
|
||||
return "", fmt.Errorf("no path for local %v", obj)
|
||||
}
|
||||
|
||||
case *types.Func:
|
||||
// A func, if not package-level, must be a method.
|
||||
|
|
@ -261,89 +320,311 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
|
|||
panic(obj)
|
||||
}
|
||||
|
||||
// 4. Search the API for the path to the var (field/param/result) or method.
|
||||
// 4. Search the object/type graph for the path to
|
||||
// the var (field/param/result) or method.
|
||||
ix, ok := enc.pkgIndex[pkg]
|
||||
if !ok {
|
||||
// First search: don't build an index, just traverse.
|
||||
// This avoids allocation in [For], whose Encoder
|
||||
// lives for a single call.
|
||||
ix = &pkgIndex{pkg: pkg}
|
||||
|
||||
// First inspect package-level named types.
|
||||
// In the presence of path aliases, these give
|
||||
// the best paths because non-types may
|
||||
// refer to types, but not the reverse.
|
||||
empty := make([]byte, 0, 48) // initial space
|
||||
objs := enc.scopeObjects(scope)
|
||||
for _, o := range objs {
|
||||
tname, ok := o.(*types.TypeName)
|
||||
if !ok {
|
||||
continue // handle non-types in second pass
|
||||
if enc.pkgIndex == nil {
|
||||
enc.pkgIndex = make(map[*types.Package]*pkgIndex)
|
||||
}
|
||||
enc.pkgIndex[pkg] = ix // build the index next time
|
||||
|
||||
f := traversal{pkg: pkg, target: obj}
|
||||
f.traverse()
|
||||
|
||||
if f.found != "" {
|
||||
return f.found, nil
|
||||
}
|
||||
} else {
|
||||
// Second search: build an index while traversing.
|
||||
if ix.data == nil {
|
||||
ix.offsets = make(map[types.Object]uint32)
|
||||
ix.data = []byte{0} // offset 0 is sentinel
|
||||
(&traversal{pkg: pkg, ix: ix}).traverse()
|
||||
}
|
||||
|
||||
path := append(empty, o.Name()...)
|
||||
path = append(path, opType)
|
||||
|
||||
T := o.Type()
|
||||
if alias, ok := T.(*types.Alias); ok {
|
||||
if r := findTypeParam(obj, alias.TypeParams(), path, opTypeParam); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
if r := find(obj, alias.Rhs(), append(path, opRhs)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
|
||||
} else if tname.IsAlias() {
|
||||
// legacy alias
|
||||
if r := find(obj, T, path); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
|
||||
} else if named, ok := T.(*types.Named); ok {
|
||||
// defined (named) type
|
||||
if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
if r := find(obj, named.Underlying(), append(path, opUnderlying)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Then inspect everything else:
|
||||
// non-types, and declared methods of defined types.
|
||||
for _, o := range objs {
|
||||
path := append(empty, o.Name()...)
|
||||
if _, ok := o.(*types.TypeName); !ok {
|
||||
if o.Exported() {
|
||||
// exported non-type (const, var, func)
|
||||
if r := find(obj, o.Type(), append(path, opType)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Inspect declared methods of defined types.
|
||||
if T, ok := types.Unalias(o.Type()).(*types.Named); ok {
|
||||
path = append(path, opType)
|
||||
// The method index here is always with respect
|
||||
// to the underlying go/types data structures,
|
||||
// which ultimately derives from source order
|
||||
// and must be preserved by export data.
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
path2 := appendOpArg(path, opMethod, i)
|
||||
if m == obj {
|
||||
return Path(path2), nil // found declared method
|
||||
}
|
||||
if r := find(obj, m.Type(), append(path2, opType)); r != nil {
|
||||
return Path(r), nil
|
||||
}
|
||||
}
|
||||
// Second and later searches: consult the index.
|
||||
if offset, ok := ix.offsets[obj]; ok {
|
||||
return ix.path(offset), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path())
|
||||
}
|
||||
|
||||
func appendOpArg(path []byte, op byte, arg int) []byte {
|
||||
// traverse performs a complete traversal of all symbols reachable from the package.
|
||||
func (tr *traversal) traverse() {
|
||||
scope := tr.pkg.Scope()
|
||||
names := scope.Names()
|
||||
if tr.ix != nil {
|
||||
tr.ix.scopeNames = names
|
||||
}
|
||||
|
||||
empty := make([]byte, 0, 48) // initial space for stack (ix == nil)
|
||||
|
||||
// First inspect package-level type names.
|
||||
// In the presence of path aliases, these give
|
||||
// the best paths because non-types may
|
||||
// refer to types, but not the reverse.
|
||||
for i, name := range names {
|
||||
if tr.found != "" {
|
||||
return // found (ix == nil)
|
||||
}
|
||||
|
||||
obj := scope.Lookup(name)
|
||||
if _, ok := obj.(*types.TypeName); !ok {
|
||||
continue // handle non-types in second pass
|
||||
}
|
||||
|
||||
// emit (name, opType)
|
||||
var path []byte
|
||||
var offset uint32
|
||||
if tr.ix == nil {
|
||||
path = append(empty, name...)
|
||||
path = append(path, opType)
|
||||
} else {
|
||||
offset = tr.ix.emitPackageLevel(i)
|
||||
tr.ix.offsets[obj] = offset
|
||||
offset = tr.ix.emitPathSegment(offset, opType, -1)
|
||||
}
|
||||
|
||||
// A TypeName (for Named or Alias) may have type parameters.
|
||||
switch t := obj.Type().(type) {
|
||||
case *types.Alias:
|
||||
tr.tparams(t.TypeParams(), path, offset, opTypeParam)
|
||||
tr.typ(path, offset, opRhs, -1, t.Rhs())
|
||||
case *types.Named:
|
||||
tr.tparams(t.TypeParams(), path, offset, opTypeParam)
|
||||
tr.typ(path, offset, opUnderlying, -1, t.Underlying())
|
||||
}
|
||||
}
|
||||
|
||||
// Then inspect everything else:
|
||||
// exported non-types, and declared methods of defined types.
|
||||
for i, name := range names {
|
||||
if tr.found != "" {
|
||||
return // found (ix == nil)
|
||||
}
|
||||
|
||||
obj := scope.Lookup(name)
|
||||
|
||||
if tname, ok := obj.(*types.TypeName); !ok {
|
||||
if obj.Exported() {
|
||||
// exported non-type (const, var, func)
|
||||
var path []byte
|
||||
var offset uint32
|
||||
if tr.ix == nil {
|
||||
path = append(empty, name...)
|
||||
} else {
|
||||
offset = tr.ix.emitPackageLevel(i)
|
||||
tr.ix.offsets[obj] = offset
|
||||
}
|
||||
tr.typ(path, offset, opType, -1, obj.Type())
|
||||
}
|
||||
|
||||
} else if T, ok := types.Unalias(tname.Type()).(*types.Named); ok {
|
||||
// defined type
|
||||
var path []byte
|
||||
var offset uint32
|
||||
if tr.ix == nil {
|
||||
path = append(empty, name...)
|
||||
path = append(path, opType)
|
||||
} else {
|
||||
// Inv: map entry for obj was populated in first pass.
|
||||
offset = tr.ix.emitPathSegment(tr.ix.offsets[obj], opType, -1)
|
||||
}
|
||||
|
||||
// Inspect declared methods of defined types.
|
||||
//
|
||||
// The method index here is always with respect
|
||||
// to the underlying go/types data structures,
|
||||
// which ultimately derives from source order
|
||||
// and must be preserved by export data.
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
tr.object(path, offset, opMethod, i, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *traversal) visitType(path []byte, offset uint32, T types.Type) {
|
||||
switch T := T.(type) {
|
||||
case *types.Alias:
|
||||
tr.typ(path, offset, opRhs, -1, T.Rhs())
|
||||
|
||||
case *types.Basic, *types.Named:
|
||||
// Named types belonging to pkg were handled already,
|
||||
// so T must belong to another package. No path.
|
||||
return
|
||||
|
||||
case *types.Pointer, *types.Slice, *types.Array, *types.Chan:
|
||||
type hasElem interface{ Elem() types.Type } // note: includes Map
|
||||
tr.typ(path, offset, opElem, -1, T.(hasElem).Elem())
|
||||
|
||||
case *types.Map:
|
||||
tr.typ(path, offset, opKey, -1, T.Key())
|
||||
tr.typ(path, offset, opElem, -1, T.Elem())
|
||||
|
||||
case *types.Signature:
|
||||
tr.tparams(T.RecvTypeParams(), path, offset, opRecvTypeParam)
|
||||
tr.tparams(T.TypeParams(), path, offset, opTypeParam)
|
||||
tr.typ(path, offset, opParams, -1, T.Params())
|
||||
tr.typ(path, offset, opResults, -1, T.Results())
|
||||
|
||||
case *types.Struct:
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
tr.object(path, offset, opField, i, T.Field(i))
|
||||
}
|
||||
|
||||
case *types.Tuple:
|
||||
for i := 0; i < T.Len(); i++ {
|
||||
tr.object(path, offset, opAt, i, T.At(i))
|
||||
}
|
||||
|
||||
case *types.Interface:
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
if m.Pkg() != nil && m.Pkg() != tr.pkg {
|
||||
continue // embedded method from another package
|
||||
}
|
||||
if !tr.seenMethods[m] {
|
||||
if tr.seenMethods == nil {
|
||||
tr.seenMethods = make(map[*types.Func]bool)
|
||||
}
|
||||
tr.seenMethods[m] = true
|
||||
tr.object(path, offset, opMethod, i, m)
|
||||
}
|
||||
}
|
||||
|
||||
case *types.TypeParam:
|
||||
tname := T.Obj()
|
||||
if tname.Pkg() != nil && tname.Pkg() != tr.pkg {
|
||||
return // type parameter from another package
|
||||
}
|
||||
if !tr.seenTParamNames[tname] {
|
||||
if tr.seenTParamNames == nil {
|
||||
tr.seenTParamNames = make(map[*types.TypeName]bool)
|
||||
}
|
||||
tr.seenTParamNames[tname] = true
|
||||
tr.object(path, offset, opObj, -1, tname)
|
||||
tr.typ(path, offset, opConstraint, -1, T.Constraint())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tr *traversal) tparams(list *types.TypeParamList, path []byte, offset uint32, op byte) {
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
tr.typ(path, offset, op, i, list.At(i))
|
||||
}
|
||||
}
|
||||
|
||||
// typ descends the type graph edge (op, index), then proceeds to traverse type t.
|
||||
func (tr *traversal) typ(path []byte, offset uint32, op byte, index int, t types.Type) {
|
||||
if tr.ix == nil {
|
||||
path = appendOpArg(path, op, index)
|
||||
} else {
|
||||
offset = tr.ix.emitPathSegment(offset, op, index)
|
||||
}
|
||||
tr.visitType(path, offset, t)
|
||||
}
|
||||
|
||||
// object descends the type graph edge (op, index), records object
|
||||
// obj, then proceeds to traverse its type.
|
||||
func (tr *traversal) object(path []byte, offset uint32, op byte, index int, obj types.Object) {
|
||||
if tr.ix == nil {
|
||||
path = appendOpArg(path, op, index)
|
||||
if obj == tr.target && tr.found == "" {
|
||||
tr.found = Path(path)
|
||||
}
|
||||
path = append(path, opType)
|
||||
} else {
|
||||
offset = tr.ix.emitPathSegment(offset, op, index)
|
||||
if _, ok := tr.ix.offsets[obj]; !ok {
|
||||
tr.ix.offsets[obj] = offset
|
||||
}
|
||||
offset = tr.ix.emitPathSegment(offset, opType, -1)
|
||||
}
|
||||
tr.visitType(path, offset, obj.Type())
|
||||
}
|
||||
|
||||
// emitPackageLevel encodes a record for a package-level symbol,
|
||||
// identified by its index in ix.scopeNames.
|
||||
func (p *pkgIndex) emitPackageLevel(index int) uint32 {
|
||||
off := uint32(len(p.data))
|
||||
p.data = append(p.data, 0) // zero varint => no parent
|
||||
p.data = binary.AppendUvarint(p.data, uint64(index))
|
||||
return off
|
||||
}
|
||||
|
||||
// emitPathSegment emits a record for a non-initial object path segment.
|
||||
func (p *pkgIndex) emitPathSegment(parent uint32, op byte, index int) uint32 {
|
||||
off := uint32(len(p.data))
|
||||
p.data = binary.AppendUvarint(p.data, uint64(parent))
|
||||
p.data = append(p.data, op)
|
||||
switch op {
|
||||
case opAt, opField, opMethod, opTypeParam, opRecvTypeParam:
|
||||
p.data = binary.AppendUvarint(p.data, uint64(index))
|
||||
}
|
||||
return off
|
||||
}
|
||||
|
||||
// path returns the Path for the encoded node at the specified offset.
|
||||
func (p *pkgIndex) path(offset uint32) Path {
|
||||
var elems []string // path elements in reverse
|
||||
for {
|
||||
// Read parent index.
|
||||
parent, n := binary.Uvarint(p.data[offset:])
|
||||
offset += uint32(n)
|
||||
|
||||
if parent == 0 {
|
||||
break // root (end of path)
|
||||
}
|
||||
|
||||
op := p.data[offset]
|
||||
offset++
|
||||
|
||||
// The [AFMTr] operators have a numeric operand.
|
||||
switch op {
|
||||
case opAt, opField, opMethod, opTypeParam, opRecvTypeParam:
|
||||
val, n := binary.Uvarint(p.data[offset:])
|
||||
offset += uint32(n)
|
||||
elems = append(elems, strconv.Itoa(int(val)))
|
||||
}
|
||||
|
||||
elems = append(elems, string([]byte{op}))
|
||||
|
||||
offset = uint32(parent)
|
||||
}
|
||||
idx, _ := binary.Uvarint(p.data[offset:])
|
||||
|
||||
// Convert index to Path string.
|
||||
name := p.scopeNames[idx]
|
||||
sz := len(name)
|
||||
for _, elem := range elems {
|
||||
sz += len(elem)
|
||||
}
|
||||
var buf strings.Builder
|
||||
buf.Grow(sz)
|
||||
buf.WriteString(name)
|
||||
for _, elem := range slices.Backward(elems) {
|
||||
buf.WriteString(elem)
|
||||
}
|
||||
return Path(buf.String())
|
||||
}
|
||||
|
||||
// appendOpArg appends (op, index) to the object path.
|
||||
// A negative index is ignored.
|
||||
func appendOpArg(path []byte, op byte, index int) []byte {
|
||||
path = append(path, op)
|
||||
path = strconv.AppendInt(path, int64(arg), 10)
|
||||
if index >= 0 {
|
||||
path = strconv.AppendInt(path, int64(index), 10)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
|
|
@ -442,138 +723,6 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
|
|||
// panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named)))
|
||||
}
|
||||
|
||||
// find finds obj within type T, returning the path to it, or nil if not found.
|
||||
//
|
||||
// The seen map is used to short circuit cycles through type parameters. If
|
||||
// nil, it will be allocated as necessary.
|
||||
//
|
||||
// The seenMethods map is used internally to short circuit cycles through
|
||||
// interface methods, such as occur in the following example:
|
||||
//
|
||||
// type I interface { f() interface{I} }
|
||||
//
|
||||
// See golang/go#68046 for details.
|
||||
func find(obj types.Object, T types.Type, path []byte) []byte {
|
||||
return (&finder{obj: obj}).find(T, path)
|
||||
}
|
||||
|
||||
// finder closes over search state for a call to find.
|
||||
type finder struct {
|
||||
obj types.Object // the sought object
|
||||
seenTParamNames map[*types.TypeName]bool // for cycle breaking through type parameters
|
||||
seenMethods map[*types.Func]bool // for cycle breaking through recursive interfaces
|
||||
}
|
||||
|
||||
func (f *finder) find(T types.Type, path []byte) []byte {
|
||||
switch T := T.(type) {
|
||||
case *types.Alias:
|
||||
return f.find(types.Unalias(T), path)
|
||||
case *types.Basic, *types.Named:
|
||||
// Named types belonging to pkg were handled already,
|
||||
// so T must belong to another package. No path.
|
||||
return nil
|
||||
case *types.Pointer:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Slice:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Array:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Chan:
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Map:
|
||||
if r := f.find(T.Key(), append(path, opKey)); r != nil {
|
||||
return r
|
||||
}
|
||||
return f.find(T.Elem(), append(path, opElem))
|
||||
case *types.Signature:
|
||||
if r := f.findTypeParam(T.RecvTypeParams(), path, opRecvTypeParam); r != nil {
|
||||
return r
|
||||
}
|
||||
if r := f.findTypeParam(T.TypeParams(), path, opTypeParam); r != nil {
|
||||
return r
|
||||
}
|
||||
if r := f.find(T.Params(), append(path, opParams)); r != nil {
|
||||
return r
|
||||
}
|
||||
return f.find(T.Results(), append(path, opResults))
|
||||
case *types.Struct:
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
fld := T.Field(i)
|
||||
path2 := appendOpArg(path, opField, i)
|
||||
if fld == f.obj {
|
||||
return path2 // found field var
|
||||
}
|
||||
if r := f.find(fld.Type(), append(path2, opType)); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *types.Tuple:
|
||||
for i := 0; i < T.Len(); i++ {
|
||||
v := T.At(i)
|
||||
path2 := appendOpArg(path, opAt, i)
|
||||
if v == f.obj {
|
||||
return path2 // found param/result var
|
||||
}
|
||||
if r := f.find(v.Type(), append(path2, opType)); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *types.Interface:
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
m := T.Method(i)
|
||||
if f.seenMethods[m] {
|
||||
continue // break cycles (see TestIssue70418)
|
||||
}
|
||||
path2 := appendOpArg(path, opMethod, i)
|
||||
if m == f.obj {
|
||||
return path2 // found interface method
|
||||
}
|
||||
if f.seenMethods == nil {
|
||||
f.seenMethods = make(map[*types.Func]bool)
|
||||
}
|
||||
f.seenMethods[m] = true
|
||||
if r := f.find(m.Type(), append(path2, opType)); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case *types.TypeParam:
|
||||
name := T.Obj()
|
||||
if f.seenTParamNames[name] {
|
||||
return nil
|
||||
}
|
||||
if name == f.obj {
|
||||
return append(path, opObj)
|
||||
}
|
||||
if f.seenTParamNames == nil {
|
||||
f.seenTParamNames = make(map[*types.TypeName]bool)
|
||||
}
|
||||
f.seenTParamNames[name] = true
|
||||
if r := f.find(T.Constraint(), append(path, opConstraint)); r != nil {
|
||||
return r
|
||||
}
|
||||
return nil
|
||||
}
|
||||
panic(T)
|
||||
}
|
||||
|
||||
func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, op byte) []byte {
|
||||
return (&finder{obj: obj}).findTypeParam(list, path, op)
|
||||
}
|
||||
|
||||
func (f *finder) findTypeParam(list *types.TypeParamList, path []byte, op byte) []byte {
|
||||
for i := 0; i < list.Len(); i++ {
|
||||
tparam := list.At(i)
|
||||
path2 := appendOpArg(path, op, i)
|
||||
if r := f.find(tparam, path2); r != nil {
|
||||
return r
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Object returns the object denoted by path p within the package pkg.
|
||||
func Object(pkg *types.Package, p Path) (types.Object, error) {
|
||||
pathstr := string(p)
|
||||
|
|
@ -708,7 +857,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
|
|||
}
|
||||
tparams := hasTypeParams.TypeParams()
|
||||
if n := tparams.Len(); index >= n {
|
||||
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
|
||||
return nil, fmt.Errorf("type parameter index %d out of range [0-%d)", index, n)
|
||||
}
|
||||
t = tparams.At(index)
|
||||
|
||||
|
|
@ -719,7 +868,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
|
|||
}
|
||||
rtparams := sig.RecvTypeParams()
|
||||
if n := rtparams.Len(); index >= n {
|
||||
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
|
||||
return nil, fmt.Errorf("receiver type parameter index %d out of range [0-%d)", index, n)
|
||||
}
|
||||
t = rtparams.At(index)
|
||||
|
||||
|
|
@ -794,23 +943,3 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
|
|||
|
||||
return obj, nil // success
|
||||
}
|
||||
|
||||
// scopeObjects is a memoization of scope objects.
|
||||
// Callers must not modify the result.
|
||||
func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object {
|
||||
m := enc.scopeMemo
|
||||
if m == nil {
|
||||
m = make(map[*types.Scope][]types.Object)
|
||||
enc.scopeMemo = m
|
||||
}
|
||||
objs, ok := m[scope]
|
||||
if !ok {
|
||||
names := scope.Names() // allocates and sorts
|
||||
objs = make([]types.Object, len(names))
|
||||
for i, name := range names {
|
||||
objs[i] = scope.Lookup(name)
|
||||
}
|
||||
m[scope] = objs
|
||||
}
|
||||
return objs
|
||||
}
|
||||
|
|
|
|||
22
src/cmd/vendor/golang.org/x/tools/internal/astutil/comment.go
generated
vendored
22
src/cmd/vendor/golang.org/x/tools/internal/astutil/comment.go
generated
vendored
|
|
@ -8,6 +8,7 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
"iter"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
|
@ -114,18 +115,25 @@ func Directives(g *ast.CommentGroup) (res []*Directive) {
|
|||
}
|
||||
|
||||
// Comments returns an iterator over the comments overlapping the specified interval.
|
||||
// Comments are sorted by position in the file, so we can use binary search.
|
||||
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 {
|
||||
// Find the first comment group that overlaps the range.
|
||||
i := sort.Search(len(file.Comments), func(i int) bool {
|
||||
return file.Comments[i].End() >= start
|
||||
})
|
||||
for _, cg := range file.Comments[i:] {
|
||||
if cg.Pos() > end {
|
||||
return
|
||||
}
|
||||
// Find the first comment in the group that overlaps the range.
|
||||
j := sort.Search(len(cg.List), func(j int) bool {
|
||||
return cg.List[j].End() >= start
|
||||
})
|
||||
for _, co := range cg.List[j:] {
|
||||
if co.Pos() > end {
|
||||
return
|
||||
}
|
||||
if co.End() < start {
|
||||
continue
|
||||
}
|
||||
|
||||
if !yield(co) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
38
src/cmd/vendor/golang.org/x/tools/internal/astutil/cursor.go
generated
vendored
Normal file
38
src/cmd/vendor/golang.org/x/tools/internal/astutil/cursor.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package astutil
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/ast/edge"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
// UnparenCursor returns the cursor for an expression with any
|
||||
// enclosing parentheses removed, similar to [ast.Unparen].
|
||||
// It is often prudent to call this before switching on the
|
||||
// type of cur.Node().
|
||||
//
|
||||
// See also [UnparenEnclosingCursor].
|
||||
func UnparenCursor(cur inspector.Cursor) inspector.Cursor {
|
||||
for is[*ast.ParenExpr](cur) {
|
||||
cur, _ = cur.FirstChild()
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
||||
// UnparenEnclosingCursor returns the first element of
|
||||
// the [Cursor.Enclosing] sequence that is not itself enclosed
|
||||
// in parens. It is often prudent to call this before switching on
|
||||
// cur.ParentEdge().
|
||||
//
|
||||
// See also [UnparenCursor].
|
||||
func UnparenEnclosingCursor(cur inspector.Cursor) inspector.Cursor {
|
||||
for cur.ParentEdgeKind() == edge.ParenExpr_X {
|
||||
cur = cur.Parent()
|
||||
}
|
||||
return cur
|
||||
}
|
||||
43
src/cmd/vendor/golang.org/x/tools/internal/astutil/purge.go
generated
vendored
43
src/cmd/vendor/golang.org/x/tools/internal/astutil/purge.go
generated
vendored
|
|
@ -12,9 +12,14 @@ import (
|
|||
)
|
||||
|
||||
// PurgeFuncBodies returns a copy of src in which the contents of each
|
||||
// outermost {...} region except struct and interface types have been
|
||||
// deleted. This reduces the amount of work required to parse the
|
||||
// top-level declarations.
|
||||
// outermost {...} region have been deleted, except for struct and
|
||||
// interface type bodies and the bodies of length-elided array
|
||||
// literals ([...]T), whose element count is part of the type. It
|
||||
// includes function bodies, function-literal bodies, and the bodies
|
||||
// of slice, map, and explicitly-sized array composite literals (whose
|
||||
// contents don't affect the type of the enclosing declaration). This
|
||||
// reduces the amount of work required to parse the top-level
|
||||
// declarations.
|
||||
//
|
||||
// PurgeFuncBodies does not preserve newlines or position information.
|
||||
// Also, if the input is invalid, parsing the output of
|
||||
|
|
@ -22,11 +27,12 @@ import (
|
|||
// on parser error recovery.
|
||||
func PurgeFuncBodies(src []byte) []byte {
|
||||
// Destroy the content of any {...}-bracketed regions that are
|
||||
// not immediately preceded by a "struct" or "interface"
|
||||
// token. That includes function bodies, composite literals,
|
||||
// switch/select bodies, and all blocks of statements.
|
||||
// This will lead to non-void functions that don't have return
|
||||
// statements, which of course is a type error, but that's ok.
|
||||
// not immediately preceded by a "struct" or "interface" token,
|
||||
// and that are not the body of a length-elided array literal.
|
||||
// That includes function bodies, switch/select bodies, and most
|
||||
// composite literals; this will lead to non-void functions that
|
||||
// don't have return statements, which of course is a type error,
|
||||
// but that's ok.
|
||||
|
||||
var out bytes.Buffer
|
||||
file := token.NewFileSet().AddFile("", -1, len(src))
|
||||
|
|
@ -34,7 +40,8 @@ func PurgeFuncBodies(src []byte) []byte {
|
|||
sc.Init(file, src, nil, 0)
|
||||
var prev token.Token
|
||||
var cursor int // last consumed src offset
|
||||
var braces []token.Pos // stack of unclosed braces or -1 for struct/interface type
|
||||
var braces []token.Pos // stack of unclosed braces, or -1 for a region we preserve
|
||||
var ellipsis bool // saw "[...]" not yet consumed by a literal-body "{"
|
||||
for {
|
||||
pos, tok, _ := sc.Scan()
|
||||
if tok == token.EOF {
|
||||
|
|
@ -44,9 +51,23 @@ func PurgeFuncBodies(src []byte) []byte {
|
|||
case token.COMMENT:
|
||||
// TODO(adonovan): opt: skip, to save an estimated 20% of time.
|
||||
|
||||
case token.SEMICOLON:
|
||||
ellipsis = false
|
||||
|
||||
case token.RBRACK:
|
||||
// "...]" occurs only in the array-type prefix of a
|
||||
// composite literal; variadic "..." is followed by
|
||||
// a type or ")", never "]".
|
||||
if prev == token.ELLIPSIS {
|
||||
ellipsis = true
|
||||
}
|
||||
|
||||
case token.LBRACE:
|
||||
if prev == token.STRUCT || prev == token.INTERFACE {
|
||||
pos = -1
|
||||
pos = -1 // type body: preserve (don't consume ellipsis)
|
||||
} else if ellipsis {
|
||||
pos = -1 // [...]T literal body: preserve
|
||||
ellipsis = false
|
||||
}
|
||||
braces = append(braces, pos)
|
||||
|
||||
|
|
@ -55,7 +76,7 @@ func PurgeFuncBodies(src []byte) []byte {
|
|||
top := braces[last]
|
||||
braces = braces[:last]
|
||||
if top < 0 {
|
||||
// struct/interface type: leave alone
|
||||
// preserve
|
||||
} else if len(braces) == 0 { // toplevel only
|
||||
// Delete {...} body.
|
||||
start := file.Offset(top)
|
||||
|
|
|
|||
5
src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
generated
vendored
5
src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
generated
vendored
|
|
@ -254,3 +254,8 @@ func needsParens(parentNode ast.Node, old, new ast.Expr) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func is[T any](n any) bool {
|
||||
_, ok := n.(T)
|
||||
return ok
|
||||
}
|
||||
|
|
|
|||
13
src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
generated
vendored
13
src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
generated
vendored
|
|
@ -9,11 +9,12 @@ package goplsexport
|
|||
import "golang.org/x/tools/go/analysis"
|
||||
|
||||
var (
|
||||
ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
|
||||
ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
|
||||
SlicesBackwardModernizer *analysis.Analyzer // = modernize.slicesbackwardAnalyzer
|
||||
StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyzer
|
||||
PlusBuildModernizer *analysis.Analyzer // = modernize.plusbuildAnalyzer
|
||||
StringsCutModernizer *analysis.Analyzer // = modernize.stringscutAnalyzer
|
||||
UnsafeFuncsModernizer *analysis.Analyzer // = modernize.unsafeFuncsAnalyzer
|
||||
AtomicTypesModernizer *analysis.Analyzer // = modernize.atomicTypesAnalyzer
|
||||
StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyzer
|
||||
PlusBuildModernizer *analysis.Analyzer // = modernize.plusbuildAnalyzer
|
||||
StringsCutModernizer *analysis.Analyzer // = modernize.stringscutAnalyzer
|
||||
UnsafeFuncsModernizer *analysis.Analyzer // = modernize.unsafeFuncsAnalyzer
|
||||
AtomicTypesModernizer *analysis.Analyzer // = modernize.atomicTypesAnalyzer
|
||||
EmbedLitModernizer *analysis.Analyzer // = modernize.embedLitAnalyzer
|
||||
)
|
||||
|
|
|
|||
8
src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go
generated
vendored
8
src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go
generated
vendored
|
|
@ -53,3 +53,11 @@ func Len[T any](seq iter.Seq[T]) (n int) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Empty reports whether the sequence contains no elements.
|
||||
func Empty[T any](seq iter.Seq[T]) bool {
|
||||
for range seq {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
2
src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go
generated
vendored
2
src/cmd/vendor/golang.org/x/tools/internal/refactor/delete.go
generated
vendored
|
|
@ -439,7 +439,7 @@ Big:
|
|||
inStmtList = true
|
||||
case *ast.ForStmt:
|
||||
use(parent.For, parent.Body.Lbrace)
|
||||
// special handling, as init;cond;post BlockStmt is not a statment list
|
||||
// special handling, as init;cond;post BlockStmt is not a statement list
|
||||
if parent.Init != nil && parent.Cond != nil && stmt == parent.Init && lineOf(parent.Cond.Pos()) == lineOf(stmt.End()) {
|
||||
rightStmt = parent.Cond.Pos()
|
||||
} else if parent.Post != nil && parent.Cond != nil && stmt == parent.Post && lineOf(parent.Cond.End()) == lineOf(stmt.Pos()) {
|
||||
|
|
|
|||
15
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
generated
vendored
15
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go
generated
vendored
|
|
@ -530,9 +530,7 @@ func analyzeTypeParams(_ logger, fset *token.FileSet, info *types.Info, decl *as
|
|||
// We don't care about most of the properties that matter for parameter references:
|
||||
// a type is immutable, cannot have its address taken, and does not undergo conversions.
|
||||
// TODO(jba): can we nevertheless combine this with the traversal in analyzeParams?
|
||||
var stack []ast.Node
|
||||
stack = append(stack, decl.Type) // for scope of function itself
|
||||
ast.PreorderStack(decl.Body, stack, func(n ast.Node, stack []ast.Node) bool {
|
||||
visit := func(n ast.Node, stack []ast.Node) bool {
|
||||
if id, ok := n.(*ast.Ident); ok {
|
||||
if v, ok := info.Uses[id].(*types.TypeName); ok {
|
||||
if pinfo, ok := paramInfos[v]; ok {
|
||||
|
|
@ -543,7 +541,16 @@ func analyzeTypeParams(_ logger, fset *token.FileSet, info *types.Info, decl *as
|
|||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
var stack []ast.Node
|
||||
stack = append(stack, decl.Type) // for scope of function itself
|
||||
if decl.Type.Params != nil {
|
||||
ast.PreorderStack(decl.Type.Params, stack, visit)
|
||||
}
|
||||
if decl.Type.Results != nil {
|
||||
ast.PreorderStack(decl.Type.Results, stack, visit)
|
||||
}
|
||||
ast.PreorderStack(decl.Body, stack, visit)
|
||||
return params
|
||||
}
|
||||
|
||||
|
|
|
|||
2
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
generated
vendored
2
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
generated
vendored
|
|
@ -2050,7 +2050,7 @@ func resolveEffects(logf logger, args []*argument, effects []int, sg substGraph)
|
|||
}
|
||||
}
|
||||
if !sg.has(argi) {
|
||||
for j := 0; j < i; j++ {
|
||||
for j := range i {
|
||||
argj := args[j]
|
||||
if argj.pure {
|
||||
continue
|
||||
|
|
|
|||
27
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/typeindex/typeindex.go
generated
vendored
27
src/cmd/vendor/golang.org/x/tools/internal/typesinternal/typeindex/typeindex.go
generated
vendored
|
|
@ -17,6 +17,7 @@ import (
|
|||
"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/astutil"
|
||||
"golang.org/x/tools/internal/typesinternal"
|
||||
)
|
||||
|
||||
|
|
@ -217,10 +218,9 @@ func (ix *Index) Selection(path, typename, name string) types.Object {
|
|||
func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] {
|
||||
return func(yield func(inspector.Cursor) bool) {
|
||||
for cur := range ix.Uses(callee) {
|
||||
ek := cur.ParentEdgeKind()
|
||||
|
||||
// The call may be of the form f() or x.f(),
|
||||
// optionally with parens; ascend from f to call.
|
||||
// See logic in [typesinternal.UsedIdent], to which this is dual.
|
||||
//
|
||||
// It is tempting but wrong to use the first
|
||||
// CallExpr ancestor: we have to make sure the
|
||||
|
|
@ -229,25 +229,20 @@ func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] {
|
|||
// Avoiding Enclosing is also significantly faster.
|
||||
|
||||
// inverse unparen: f -> (f)
|
||||
for ek == edge.ParenExpr_X {
|
||||
cur = cur.Parent()
|
||||
ek = cur.ParentEdgeKind()
|
||||
cur = astutil.UnparenEnclosingCursor(cur)
|
||||
|
||||
// ascend selector (or qualified identifier): f -> x.f
|
||||
if cur.ParentEdgeKind() == edge.SelectorExpr_Sel {
|
||||
cur = astutil.UnparenEnclosingCursor(cur.Parent())
|
||||
}
|
||||
|
||||
// ascend selector: f -> x.f
|
||||
if ek == edge.SelectorExpr_Sel {
|
||||
cur = cur.Parent()
|
||||
ek = cur.ParentEdgeKind()
|
||||
}
|
||||
|
||||
// inverse unparen again
|
||||
for ek == edge.ParenExpr_X {
|
||||
cur = cur.Parent()
|
||||
ek = cur.ParentEdgeKind()
|
||||
// ascend typeparams: f -> f[T]; f -> f[T1, T2]
|
||||
if ek := cur.ParentEdgeKind(); ek == edge.IndexExpr_X || ek == edge.IndexListExpr_X {
|
||||
cur = astutil.UnparenEnclosingCursor(cur.Parent())
|
||||
}
|
||||
|
||||
// ascend from f or x.f to call
|
||||
if ek == edge.CallExpr_Fun {
|
||||
if cur.ParentEdgeKind() == edge.CallExpr_Fun {
|
||||
curCall := cur.Parent()
|
||||
call := curCall.Node().(*ast.CallExpr)
|
||||
if typeutil.Callee(ix.info, call) == callee {
|
||||
|
|
|
|||
4
src/cmd/vendor/modules.txt
vendored
4
src/cmd/vendor/modules.txt
vendored
|
|
@ -48,7 +48,7 @@ golang.org/x/sync/semaphore
|
|||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa
|
||||
# golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/telemetry
|
||||
golang.org/x/telemetry/counter
|
||||
|
|
@ -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.44.1-0.20260414062052-55fb96ff894f
|
||||
# golang.org/x/tools v0.45.1-0.20260520205638-b38156a7a9f5
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/tools/cmd/bisect
|
||||
golang.org/x/tools/cover
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue