). If so, delete the non-args
- // that come before the right parenthesis. Leaving an
- // extra comma here produces invalid code. (See
- // golang/go#74709)
- if arg.End() < conv.Rparen {
- edits = append(edits, analysis.TextEdit{
- Pos: arg.End(),
- End: conv.Rparen,
- })
- }
+ if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_19) {
+ continue
}
pass.Report(analysis.Diagnostic{
Pos: conv.Pos(),
@@ -110,3 +120,43 @@ func fmtappendf(pass *analysis.Pass) (any, error) {
}
return nil, nil
}
+
+// mayFormatEmpty reports whether fmt.Sprintf might produce an empty string.
+// It returns false in the following two cases:
+// 1. formatStr contains non-operation characters.
+// 2. formatStr contains formatting verbs besides s, v, x, X (verbs which may
+// produce empty results)
+//
+// In all other cases it returns true.
+func mayFormatEmpty(formatStr string) bool {
+ if formatStr == "" {
+ return true
+ }
+ operations, err := fmtstr.Parse(formatStr, 0)
+ if err != nil {
+ // If formatStr is malformed, the printf analyzer will report a
+ // diagnostic, so we can ignore this error.
+ // Calling Parse on a string without % formatters also returns an error,
+ // in which case we can safely return false.
+ return false
+ }
+ totalOpsLen := 0
+ for _, op := range operations {
+ totalOpsLen += len(op.Text)
+ if !strings.ContainsRune("svxX", rune(op.Verb.Verb)) && op.Prec.Fixed != 0 {
+ // A non [s, v, x, X] formatter with non-zero precision cannot
+ // produce an empty string.
+ return false
+ }
+ }
+ // If the format string contains non-operation characters, it cannot produce
+ // the empty string.
+ if totalOpsLen != len(formatStr) {
+ return false
+ }
+ // If we get here, it means that all formatting verbs are %s, %v, %x, %X,
+ // and there are no additional non-operation characters. We conservatively
+ // report that this may format as an empty string, ignoring uses of
+ // precision and the values of the formatter args.
+ return true
+}
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
index 795f5b6c6..7f3fd4e6f 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
@@ -92,13 +92,14 @@ func mapsloop(pass *analysis.Pass) (any, error) {
// and can we replace its RHS with slices.{Clone,Collect}?
//
// Beware: if x may be nil, we cannot use Clone as it preserves nilness.
- var mrhs ast.Expr // make(M) or M{}, or nil
+ var mrhs ast.Expr // make(M) or M{}, or nil
+ var mAssign token.Token // token used to assign m
if curPrev, ok := curRange.PrevSibling(); ok {
if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
astutil.EqualSyntax(assign.Lhs[0], m) {
-
+ mAssign = assign.Tok
// Have: m = rhs; for k, v := range x { m[k] = v }
var newMap bool
rhs := assign.Rhs[0]
@@ -175,12 +176,13 @@ func mapsloop(pass *analysis.Pass) (any, error) {
// ->
//
// /* comments */
- // m = maps.Copy(x)
+ // m = maps.Collect(x)
curPrev, _ := curRange.PrevSibling()
start, end = curPrev.Node().Pos(), rng.End()
- newText = fmt.Appendf(nil, "%s%s = %s%s(%s)",
+ newText = fmt.Appendf(nil, "%s%s %s %s%s(%s)",
allComments(file, start, end),
astutil.Format(pass.Fset, m),
+ mAssign.String(),
prefix,
funcName,
astutil.Format(pass.Fset, x))
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
index a77ed8389..93aadf04a 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
@@ -55,6 +55,10 @@ var MinMaxAnalyzer = &analysis.Analyzer{
// - "x := a" or "x = a" or "var x = a" in pattern 2
// - "x < b" or "a < b" in pattern 2
func minmax(pass *analysis.Pass) (any, error) {
+ var (
+ inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ info = pass.TypesInfo
+ )
// Check for user-defined min/max functions that can be removed
checkUserDefinedMinMax(pass)
@@ -104,6 +108,9 @@ func minmax(pass *analysis.Pass) (any, error) {
if !is[*types.Builtin](lookup(pass.TypesInfo, curIfStmt, sym)) {
return // min/max function is shadowed
}
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
+ return // min/max is too new
+ }
// pattern 1
//
@@ -147,6 +154,13 @@ func minmax(pass *analysis.Pass) (any, error) {
lhs0 := fassign.Lhs[0]
rhs0 := fassign.Rhs[0]
+ // If the assignment occurs within a select
+ // comms clause (like "case lhs0 := <-rhs0:"),
+ // there's no way of rewriting it into a min/max call.
+ if prev.ParentEdgeKind() == edge.CommClause_Comm {
+ return
+ }
+
if astutil.EqualSyntax(lhs, lhs0) {
if astutil.EqualSyntax(rhs, a) && (astutil.EqualSyntax(rhs0, b) || astutil.EqualSyntax(lhs0, b)) {
sign = +sign
@@ -170,6 +184,10 @@ func minmax(pass *analysis.Pass) (any, error) {
b = rhs0
}
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
+ return // min/max is too new
+ }
+
// pattern 2
pass.Report(analysis.Diagnostic{
// Highlight the condition a < b.
@@ -197,31 +215,26 @@ func minmax(pass *analysis.Pass) (any, error) {
}
// Find all "if a < b { lhs = rhs }" statements.
- info := pass.TypesInfo
- for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
- astFile := curFile.Node().(*ast.File)
- for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
- ifStmt := curIfStmt.Node().(*ast.IfStmt)
-
- // Don't bother handling "if a < b { lhs = rhs }" when it appears
- // as the "else" branch of another if-statement.
- // if cond { ... } else if a < b { lhs = rhs }
- // (This case would require introducing another block
- // if cond { ... } else { if a < b { lhs = rhs } }
- // and checking that there is no following "else".)
- if curIfStmt.ParentEdgeKind() == edge.IfStmt_Else {
- continue
- }
+ for curIfStmt := range inspect.Root().Preorder((*ast.IfStmt)(nil)) {
+ ifStmt := curIfStmt.Node().(*ast.IfStmt)
+ // Don't bother handling "if a < b { lhs = rhs }" when it appears
+ // as the "else" branch of another if-statement.
+ // if cond { ... } else if a < b { lhs = rhs }
+ // (This case would require introducing another block
+ // if cond { ... } else { if a < b { lhs = rhs } }
+ // and checking that there is no following "else".)
+ if curIfStmt.ParentEdgeKind() == edge.IfStmt_Else {
+ continue
+ }
- if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
- ifStmt.Init == nil &&
- isInequality(compare.Op) != 0 &&
- 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) {
- // Have: if a < b { lhs = rhs }
- check(astFile, curIfStmt, compare)
- }
+ if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
+ ifStmt.Init == nil &&
+ isInequality(compare.Op) != 0 &&
+ 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) {
+ // Have: if a < b { lhs = rhs }
+ check(astutil.EnclosingFile(curIfStmt), curIfStmt, compare)
}
}
}
@@ -293,8 +306,10 @@ func checkUserDefinedMinMax(pass *analysis.Pass) {
// Use typeindex to get the FuncDecl directly
if def, ok := index.Def(fn); ok {
decl := def.Parent().Node().(*ast.FuncDecl)
- // Check if this function matches the built-in min/max signature and behavior
- if canUseBuiltinMinMax(fn, decl.Body) {
+ // Check if this function matches the built-in min/max signature
+ // and behavior, and verify that we have go1.21.
+ if canUseBuiltinMinMax(fn, decl.Body) &&
+ analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(def), versions.Go1_21) {
// Expand to include leading doc comment
pos := decl.Pos()
if docs := astutil.DocComment(decl); docs != nil {
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
index ef28a40d1..db3b86fbf 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
@@ -20,6 +20,8 @@ import (
"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"
+ "golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/packagepath"
@@ -33,6 +35,7 @@ var doc string
// Suite lists all modernize analyzers.
var Suite = []*analysis.Analyzer{
AnyAnalyzer,
+ atomicTypesAnalyzer,
// AppendClippedAnalyzer, // not nil-preserving!
// BLoopAnalyzer, // may skew benchmark results, see golang/go#74967
FmtAppendfAnalyzer,
@@ -44,6 +47,7 @@ var Suite = []*analysis.Analyzer{
plusBuildAnalyzer,
RangeIntAnalyzer,
ReflectTypeForAnalyzer,
+ slicesbackwardAnalyzer,
SlicesContainsAnalyzer,
// SlicesDeleteAnalyzer, // not nil-preserving!
SlicesSortAnalyzer,
@@ -54,7 +58,7 @@ var Suite = []*analysis.Analyzer{
StringsBuilderAnalyzer,
TestingContextAnalyzer,
unsafeFuncsAnalyzer,
- WaitGroupAnalyzer,
+ WaitGroupGoAnalyzer,
}
// -- helpers --
@@ -81,6 +85,12 @@ func isIntLiteral(info *types.Info, e ast.Expr, n int64) bool {
return info.Types[e].Value == constant.MakeInt64(n)
}
+// isInteger reports whether t is an integer type.
+func isInteger(t types.Type) bool {
+ basic, ok := t.Underlying().(*types.Basic)
+ return ok && basic.Info()&types.IsInteger != 0
+}
+
// filesUsingGoVersion returns a cursor for each *ast.File in the inspector
// that uses at least the specified version of Go (e.g. "go1.24").
//
@@ -144,3 +154,37 @@ func lookup(info *types.Info, cur inspector.Cursor, name string) types.Object {
}
func first[T any](x T, _ any) T { return x }
+
+// freshName returns a fresh name at the given pos and scope based on preferredName.
+// It generates a new name using refactor.FreshName only if:
+// (a) the preferred name is already defined at definedCur, and
+// (b) there are references to it from within usedCur.
+// If useAfterPos.IsValid(), the references must be after
+// useAfterPos within usedCur in order to warrant a fresh name.
+// Otherwise, it returns preferredName, since shadowing is valid in this case.
+// (declaredCur and usedCur may be identical in some use cases).
+func freshName(info *types.Info, index *typeindex.Index, scope *types.Scope, pos token.Pos, defCur inspector.Cursor, useCur inspector.Cursor, useAfterPos token.Pos, preferredName string) string {
+ obj := lookup(info, defCur, preferredName)
+ if obj == nil {
+ // preferredName has not been declared here.
+ return preferredName
+ }
+ for use := range index.Uses(obj) {
+ if useCur.Contains(use) && use.Node().Pos() >= useAfterPos {
+ return refactor.FreshName(scope, pos, preferredName)
+ }
+ }
+ // Name is taken but not used in the given block; shadowing is acceptable.
+ return preferredName
+}
+
+// isLocal reports whether obj is local to some function.
+// Precondition: not a struct field or interface method.
+func isLocal(obj types.Object) bool {
+ // [... 5=stmt 4=func 3=file 2=pkg 1=universe]
+ var depth int
+ for scope := obj.Parent(); scope != nil; scope = scope.Parent() {
+ depth++
+ }
+ return depth >= 4
+}
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
index cd924ec85..c99d9c24d 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
@@ -6,12 +6,11 @@ package modernize
import (
_ "embed"
+ "fmt"
"go/ast"
"go/token"
"go/types"
- "strings"
-
- "fmt"
+ "slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -94,7 +93,9 @@ func run(pass *analysis.Pass) (any, error) {
// older Go file; see https://go.dev/issue/75726.
//
// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
- if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
+ if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
+ return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
+ }) {
edits = append(edits, analysis.TextEdit{
Pos: decl.Pos(),
End: decl.Pos(),
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
index 257f9e9ec..7c529c97d 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
@@ -9,6 +9,7 @@ import (
"go/ast"
"go/token"
"go/types"
+ "log"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -18,7 +19,7 @@ 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/moreiters"
+ "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"
@@ -142,15 +143,12 @@ func rangeint(pass *analysis.Pass) (any, error) {
// Find references to i within the loop body.
v := info.ObjectOf(index).(*types.Var)
- // TODO(adonovan): use go1.25 v.Kind() == types.PackageVar
- if typesinternal.IsPackageLevel(v) {
+ switch v.Kind() {
+ case types.PackageVar:
continue nextLoop
- }
-
- // If v is a named result, it is implicitly
- // used after the loop (go.dev/issue/76880).
- // TODO(adonovan): use go1.25 v.Kind() == types.ResultVar.
- if moreiters.Contains(enclosingSignature(curLoop, info).Results().Variables(), v) {
+ case types.ResultVar:
+ // If v is a named result, it is implicitly
+ // used after the loop (go.dev/issue/76880).
continue nextLoop
}
@@ -218,6 +216,36 @@ func rangeint(pass *analysis.Pass) (any, error) {
}
}
+ // The loop index (v) must not be a type parameter constrained by
+ // multiple distinct integer types, or a type parameter constrained
+ // by non-integer types. Transforming such instances to a range loop
+ // would result in a compiler error.
+ // See golang/go#78571.
+ terms, err := typeparams.NormalTerms(v.Type()) // NormalTerms works for any type
+ if err != nil {
+ log.Fatalf("internal error: cannot compute type set of loop var %v: %v", v, err)
+ }
+ if len(terms) != 0 {
+ // From the spec (https://go.dev/ref/spec#For_range):
+ // "If the type of the range expression is a type parameter, all
+ // types in its type set must have the same underlying type and the
+ // range expression must be valid for that type."
+ //
+ // Check if all terms have the same underlying type by comparing
+ // them to the first term.
+ u := terms[0].Type().Underlying()
+ // If the constraint has any non-integer terms, skip. (Range over
+ // float is not allowed.)
+ if !isInteger(u) {
+ continue nextLoop
+ }
+ for _, term := range terms[1:] {
+ if !types.Identical(u, term.Type().Underlying()) {
+ continue nextLoop
+ }
+ }
+ }
+
// If limit is len(slice),
// simplify "range len(slice)" to "range slice".
if call, ok := limit.(*ast.CallExpr); ok &&
@@ -230,7 +258,7 @@ func rangeint(pass *analysis.Pass) (any, error) {
// such as "const limit = 1e3", its effective type may
// differ between the two forms.
// In a for loop, it must be comparable with int i,
- // for i := 0; i < limit; i++
+ // for i := 0; i < limit; i++ {}
// but in a range loop it would become a float,
// for i := range limit {}
// which is a type error. We need to convert it to int
@@ -249,9 +277,24 @@ func rangeint(pass *analysis.Pass) (any, error) {
beforeLimit, afterLimit = fmt.Sprintf("%s(", types.TypeString(tVar, qual)), ")"
info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)}
if types.CheckExpr(pass.Fset, pass.Pkg, limit.Pos(), limit, info2) == nil {
- tLimit := types.Default(info2.TypeOf(limit))
- if types.AssignableTo(tLimit, tVar) {
- beforeLimit, afterLimit = "", ""
+ tLimit := info2.TypeOf(limit)
+ // Eliminate conversion when safe.
+ //
+ // Redundant conversions are not only unsightly but may in some cases cause
+ // architecture-specific types (e.g. syscall.Timespec.Nsec) to be inserted
+ // into otherwise portable files.
+ //
+ // The operand must have an integer type (not, say, '1e6')
+ // even when assigning to an existing integer variable.
+ if isInteger(tLimit) {
+ // When declaring a new var from an untyped limit,
+ // the limit's default type is what matters.
+ if init.Tok != token.ASSIGN {
+ tLimit = types.Default(tLimit)
+ }
+ if types.AssignableTo(tLimit, tVar) {
+ beforeLimit, afterLimit = "", ""
+ }
}
}
}
@@ -346,24 +389,3 @@ func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool {
}
return false
}
-
-// enclosingSignature returns the signature of the innermost
-// function enclosing the syntax node denoted by cur
-// or nil if the node is not within a function.
-//
-// TODO(adonovan): factor with gopls/internal/util/typesutil.EnclosingSignature.
-func enclosingSignature(cur inspector.Cursor, info *types.Info) *types.Signature {
- if c, ok := enclosingFunc(cur); ok {
- switch n := c.Node().(type) {
- case *ast.FuncDecl:
- if f, ok := info.Defs[n.Name]; ok {
- return f.Type().(*types.Signature)
- }
- case *ast.FuncLit:
- if f, ok := info.Types[n]; ok {
- return f.Type.(*types.Signature)
- }
- }
- }
- return nil
-}
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
index 1dca2be37..fb1f2e2ef 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
@@ -47,6 +47,14 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
// Have: reflect.TypeOf(expr)
expr := call.Args[0]
+
+ // reflect.TypeFor cannot be instantiated with an untyped nil.
+ // We use type information rather than checking the identifier name
+ // to correctly handle edge cases where "nil" is shadowed (e.g. nil := "nil").
+ if info.Types[expr].IsNil() {
+ continue
+ }
+
if !typesinternal.NoEffects(info, expr) {
continue // don't eliminate operand: may have effects
}
@@ -54,20 +62,20 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
t := info.TypeOf(expr)
var edits []analysis.TextEdit
- // Special case for TypeOf((*T)(nil)).Elem(),
- // needed when T is an interface type.
+ // 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()
if curSel.ParentEdgeKind() == edge.CallExpr_Fun {
- call2 := unparenEnclosing(curSel).Parent().Node().(*ast.CallExpr)
+ call2 := unparenEnclosing(curSel).Parent().Node().(*ast.CallExpr) // potentially .Elem()
obj := typeutil.Callee(info, call2)
if typesinternal.IsMethodNamed(obj, "reflect", "Type", "Elem") {
- if ptr, ok := t.(*types.Pointer); ok {
- // Have: TypeOf(expr).Elem() where expr : *T
- t = ptr.Elem()
- // reflect.TypeOf(expr).Elem()
- // -------
- // reflect.TypeOf(expr)
+ // reflect.TypeOf(expr).Elem()
+ // -------
+ // reflect.TypeOf(expr)
+ if typ, hasElem := t.(interface{ Elem() types.Type }); hasElem {
+ // Have: TypeOf(expr).Elem() where expr is *T, []T, [k]T, chan T, map[K]T, etc.
+ t = typ.Elem()
edits = []analysis.TextEdit{{
Pos: call.End(),
End: call2.End(),
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go
new file mode 100644
index 000000000..293a7c0c3
--- /dev/null
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go
@@ -0,0 +1,215 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modernize
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "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/types/typeutil"
+ "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/typeindex"
+ "golang.org/x/tools/internal/versions"
+)
+
+var slicesbackwardAnalyzer = &analysis.Analyzer{
+ Name: "slicesbackward",
+ Doc: analyzerutil.MustExtractDoc(doc, "slicesbackward"),
+ Requires: []*analysis.Analyzer{
+ inspect.Analyzer,
+ typeindexanalyzer.Analyzer,
+ },
+ Run: slicesbackward,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesbackward",
+}
+
+func init() {
+ // Export to gopls until this is a published modernizer.
+ goplsexport.SlicesBackwardModernizer = slicesbackwardAnalyzer
+}
+
+// slicesbackward offers a fix to replace a manually-written backward loop:
+//
+// for i := len(s) - 1; i >= 0; i-- {
+// use(s[i])
+// }
+//
+// with a range loop using slices.Backward (added in Go 1.23):
+//
+// for _, v := range slices.Backward(s) {
+// use(v)
+// }
+//
+// If the loop index is needed beyond just indexing into the slice, both
+// the index and value variables are kept:
+//
+// for i, v := range slices.Backward(s) { ... }
+func slicesbackward(pass *analysis.Pass) (any, error) {
+ // Skip packages that are in the slices stdlib dependency tree to
+ // avoid import cycles.
+ if within(pass, "slices") {
+ return nil, nil
+ }
+
+ var (
+ info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ )
+
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_23) {
+ file := curFile.Node().(*ast.File)
+
+ nextLoop:
+ for curLoop := range curFile.Preorder((*ast.ForStmt)(nil)) {
+ loop := curLoop.Node().(*ast.ForStmt)
+
+ // Match init: i := len(s) - 1 or i = len(s) - 1
+ init, ok := loop.Init.(*ast.AssignStmt)
+ if !ok || !isSimpleAssign(init) {
+ continue
+ }
+ indexIdent, ok := init.Lhs[0].(*ast.Ident)
+ if !ok {
+ continue
+ }
+ indexObj := info.ObjectOf(indexIdent).(*types.Var)
+
+ // RHS must be len(s) - 1.
+ binRhs, ok := init.Rhs[0].(*ast.BinaryExpr)
+ if !ok || binRhs.Op != token.SUB {
+ continue
+ }
+ if !isIntLiteral(info, binRhs.Y, 1) {
+ continue
+ }
+ lenCall, ok := binRhs.X.(*ast.CallExpr)
+ if !ok || typeutil.Callee(info, lenCall) != builtinLen {
+ continue
+ }
+ if len(lenCall.Args) != 1 {
+ continue
+ }
+ sliceExpr := lenCall.Args[0]
+ if _, ok := info.TypeOf(sliceExpr).Underlying().(*types.Slice); !ok {
+ continue
+ }
+
+ // Match cond: i >= 0
+ cond, ok := loop.Cond.(*ast.BinaryExpr)
+ if !ok || cond.Op != token.GEQ {
+ continue
+ }
+ if !astutil.EqualSyntax(cond.X, indexIdent) {
+ continue
+ }
+ if !isZeroIntConst(info, cond.Y) {
+ continue
+ }
+
+ // Match post: i--
+ dec, ok := loop.Post.(*ast.IncDecStmt)
+ if !ok || dec.Tok != token.DEC {
+ continue
+ }
+ if !astutil.EqualSyntax(dec.X, indexIdent) {
+ continue
+ }
+
+ // Check that i is not used as an lvalue in the loop body.
+ // If init is = (not :=), i is a pre-existing variable; also
+ // check that it is not used as an lvalue outside the loop
+ // (e.g. &i before the loop).
+ bodyCur := curLoop.Child(loop.Body)
+ for curUse := range index.Uses(indexObj) {
+ if !isScalarLvalue(info, curUse) {
+ continue
+ }
+ if bodyCur.Contains(curUse) {
+ continue nextLoop // i is mutated in loop body
+ }
+ if init.Tok == token.ASSIGN && !curLoop.Contains(curUse) {
+ continue nextLoop // pre-existing i is an lvalue outside the loop
+ }
+ }
+
+ // Find all uses of i in the loop body. Classify as:
+ // 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
+ )
+ for curUse := range index.Uses(indexObj) {
+ if !bodyCur.Contains(curUse) {
+ continue
+ }
+ // Is i in the Index position of an s[i] expression?
+ if curUse.ParentEdgeKind() == edge.IndexExpr_Index {
+ idxExpr := curUse.Parent().Node().(*ast.IndexExpr)
+ if astutil.EqualSyntax(idxExpr.X, sliceExpr) {
+ sliceIndexes = append(sliceIndexes, idxExpr)
+ continue
+ }
+ }
+ otherUses++
+ }
+
+ // Build the suggested fix.
+ //
+ // for i := len(s) - 1; i >= 0; i-- { ... s[i] ... }
+ // ---------------------------- ----
+ // _, 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")
+
+ // Replace each s[i] with elemName.
+ for _, sx := range sliceIndexes {
+ edits = append(edits, analysis.TextEdit{
+ Pos: sx.Pos(),
+ End: sx.End(),
+ NewText: []byte(elemName),
+ })
+ }
+
+ // 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)
+ }
+ edits = append(edits, analysis.TextEdit{
+ Pos: loop.Init.Pos(),
+ End: loop.Post.End(),
+ NewText: []byte(header),
+ })
+
+ pass.Report(analysis.Diagnostic{
+ Pos: loop.Init.Pos(),
+ End: loop.Post.End(),
+ Message: "backward loop over slice can be modernized using slices.Backward",
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: fmt.Sprintf("Replace with range slices.Backward(%s)", sliceStr),
+ TextEdits: edits,
+ }},
+ })
+ }
+ }
+ return nil, nil
+}
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
index 3b3268526..c1d42188f 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicescontains.go
@@ -231,7 +231,9 @@ func slicescontains(pass *analysis.Pass) (any, error) {
// that might affected by melting down the loop.
//
// TODO(adonovan): relax check by analyzing branch target.
+ numBodyStmts := 0
for curBodyStmt := range curBody.Children() {
+ numBodyStmts += 1
if curBodyStmt != curLastStmt {
for range curBodyStmt.Preorder((*ast.BranchStmt)(nil), (*ast.ReturnStmt)(nil)) {
return
@@ -292,7 +294,16 @@ func slicescontains(pass *analysis.Pass) (any, error) {
case *ast.BranchStmt:
if lastStmt.Tok == token.BREAK && lastStmt.Label == nil { // unlabeled break
// Have: for ... { if ... { stmts; break } }
-
+ if numBodyStmts == 1 {
+ // If the only stmt in the body is an unlabeled "break" that
+ // will get deleted in the fix, don't suggest a fix, as it
+ // produces confusing code:
+ // if slices.Contains(slice, f) {}
+ // Explicitly discarding the result isn't much better:
+ // _ = slices.Contains(slice, f) // just for effects
+ // See https://go.dev/issue/77677.
+ return
+ }
var prevStmt ast.Stmt // previous statement to range (if any)
if curPrev, ok := curRange.PrevSibling(); ok {
// If the RangeStmt's previous sibling is a Stmt,
@@ -316,12 +327,13 @@ func slicescontains(pass *analysis.Pass) (any, error) {
len(assign.Rhs) == 1 {
// Have: body={ lhs = rhs; break }
+ assignBool := isTrueOrFalse(info, assign.Rhs[0])
if prevAssign, ok := prevStmt.(*ast.AssignStmt); ok &&
len(prevAssign.Lhs) == 1 &&
len(prevAssign.Rhs) == 1 &&
+ assignBool != 0 && // non-bool assignments don't apply in this case
astutil.EqualSyntax(prevAssign.Lhs[0], assign.Lhs[0]) &&
- isTrueOrFalse(info, assign.Rhs[0]) ==
- -isTrueOrFalse(info, prevAssign.Rhs[0]) {
+ assignBool == -isTrueOrFalse(info, prevAssign.Rhs[0]) {
// Have:
// lhs = false
@@ -332,7 +344,7 @@ func slicescontains(pass *analysis.Pass) (any, error) {
// TODO(adonovan):
// - support "var lhs bool = false" and variants.
// - allow the break to be omitted.
- neg := cond(isTrueOrFalse(info, assign.Rhs[0]) < 0, "!", "")
+ neg := cond(assignBool < 0, "!", "")
report([]analysis.TextEdit{
// Replace "rhs" of previous assignment by [!]slices.Contains(...)
{
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
index 1d1a9ca3b..86e1c8fd4 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
@@ -18,7 +18,6 @@ import (
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/stdlib"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
@@ -182,22 +181,9 @@ func stditerators(pass *analysis.Pass) (any, error) {
}
loop := curBody.Parent().Node()
-
- // Choose a fresh name only if
- // (a) the preferred name is already declared here, and
- // (b) there are references to it from the loop body.
- // TODO(adonovan): this pattern also appears in errorsastype,
- // and is wanted elsewhere; factor.
- name := row.elemname
- if v := lookup(info, curBody, name); v != nil {
- // is it free in body?
- for curUse := range index.Uses(v) {
- if curBody.Contains(curUse) {
- name = refactor.FreshName(info.Scopes[loop], loop.Pos(), name)
- break
- }
- }
- }
+ // We generate a new name only if the preferred name is already declared here
+ // and is used within the loop body.
+ name := freshName(info, index, info.Scopes[loop], loop.Pos(), curBody, curBody, token.NoPos, row.elemname)
return name, nil
}
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
index a6c2c1f86..e89baa9b0 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
@@ -22,7 +22,6 @@ import (
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
- "golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
)
@@ -57,7 +56,7 @@ func stringsbuilder(pass *analysis.Pass) (any, error) {
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 &&
- !typesinternal.IsPackageLevel(v) && // TODO(adonovan): in go1.25, use v.Kind() == types.LocalVar &&
+ v.Kind() == types.LocalVar &&
types.Identical(v.Type(), builtinString.Type()) {
candidates[v] = true
}
@@ -254,8 +253,8 @@ nextcand:
// var s string
// for ... { s += expr }
//
- // - The final use of s must be as an rvalue (e.g. use(s), not &s).
- // This will become s.String().
+ // - All uses of s after the last += must be rvalue uses (e.g. use(s), not &s).
+ // Each of these will become s.String().
//
// Perhaps surprisingly, it is fine for there to be an
// intervening loop or lambda w.r.t. the declaration of s:
@@ -270,7 +269,7 @@ nextcand:
var (
numLoopAssigns int // number of += assignments within a loop
loopAssign *ast.AssignStmt // first += assignment within a loop
- seenRvalueUse bool // => we've seen the sole final use of s as an rvalue
+ seenRvalueUse bool // => we've seen at least one rvalue use of s
)
for curUse := range index.Uses(v) {
// Strip enclosing parens around Ident.
@@ -280,11 +279,6 @@ nextcand:
ek = curUse.ParentEdgeKind()
}
- // The rvalueUse must be the lexically last use.
- if seenRvalueUse {
- continue nextcand
- }
-
// intervening reports whether cur has an ancestor of
// one of the given types that is within the scope of v.
intervening := func(types ...ast.Node) bool {
@@ -297,6 +291,11 @@ nextcand:
}
if ek == edge.AssignStmt_Lhs {
+ // After an rvalue use, no more assignments are allowed.
+ if seenRvalueUse {
+ continue nextcand
+ }
+
assign := curUse.Parent().Node().(*ast.AssignStmt)
if assign.Tok != token.ADD_ASSIGN {
continue nextcand
@@ -317,9 +316,9 @@ nextcand:
// ------------- -
// s.WriteString(expr)
edits = append(edits, []analysis.TextEdit{
- // replace += with .WriteString()
+ // replace " += " with ".WriteString("
{
- Pos: assign.TokPos,
+ Pos: assign.Lhs[0].End(),
End: assign.Rhs[0].Pos(),
NewText: []byte(".WriteString("),
},
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
index f963b547b..6192c56fa 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
@@ -22,7 +22,7 @@ import (
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/moreiters"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
@@ -124,6 +124,8 @@ func stringscut(pass *analysis.Pass) (any, error) {
bytesIndexByte = index.Object("bytes", "IndexByte")
)
+ 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{
stringsIndex,
stringsIndexByte,
@@ -183,7 +185,7 @@ func stringscut(pass *analysis.Pass) (any, error) {
// len(substr)]), then we can replace the call to Index()
// with a call to Cut() and use the returned ok, before,
// and after variables accordingly.
- negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr)
+ negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr, iObj)
// Either there are no uses of before, after, or ok, or some use
// of i does not match our criteria - don't suggest a fix.
@@ -194,14 +196,48 @@ func stringscut(pass *analysis.Pass) (any, error) {
// If the only uses are ok and !ok, don't suggest a Cut() fix - these should be using Contains()
isContains := (len(negative) > 0 || len(nonnegative) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0
+ enclosingBlock, ok := moreiters.First(curCall.Enclosing((*ast.BlockStmt)(nil)))
+ if !ok {
+ continue
+ }
scope := iObj.Parent()
- var (
- // TODO(adonovan): avoid FreshName when not needed; see errorsastype.
- okVarName = refactor.FreshName(scope, iIdent.Pos(), "ok")
- beforeVarName = refactor.FreshName(scope, iIdent.Pos(), "before")
- afterVarName = refactor.FreshName(scope, iIdent.Pos(), "after")
- foundVarName = refactor.FreshName(scope, iIdent.Pos(), "found") // for Contains()
- )
+ // Generate fresh names for ok, before, after, found, but only if
+ // they are defined by the end of the enclosing block and used
+ // within the enclosing block after the Index call. We need a Cursor
+ // for the end of the enclosing block, but we can't just find the
+ // Cursor at scope.End() because it corresponds to the entire
+ // enclosingBlock. Instead, get the last child of the enclosing
+ // block.
+ lastStmtCur, _ := enclosingBlock.LastChild()
+ lastStmt := lastStmtCur.Node()
+
+ fresh := func(preferred string) string {
+ return freshName(info, index, scope, lastStmt.End(), lastStmtCur, enclosingBlock, iIdent.Pos(), preferred)
+ }
+
+ var okVarName, beforeVarName, afterVarName, foundVarName string
+ if isContains {
+ foundVarName = fresh("found")
+ } else {
+ okVarName = fresh("ok")
+ beforeVarName = fresh("before")
+ afterVarName = fresh("after")
+ }
+
+ // If we are already suggesting a fix within the index's scope, we
+ // must get fresh names for before, after and ok.
+ // This is a specific symptom of the general problem that analyzers
+ // can generate conflicting fixes.
+ if scopeFixCount[scope] > 0 {
+ suffix := scopeFixCount[scope] - 1 // start at 0
+ if isContains {
+ foundVarName = fresh(fmt.Sprintf("%s%d", foundVarName, suffix))
+ } else {
+ okVarName = fresh(fmt.Sprintf("%s%d", okVarName, suffix))
+ beforeVarName = fresh(fmt.Sprintf("%s%d", beforeVarName, suffix))
+ afterVarName = fresh(fmt.Sprintf("%s%d", afterVarName, suffix))
+ }
+ }
// If there will be no uses of ok, before, or after, use the
// blank identifier instead.
@@ -313,6 +349,7 @@ func stringscut(pass *analysis.Pass) (any, error) {
}...)
}
}
+ scopeFixCount[scope]++
pass.Report(analysis.Diagnostic{
Pos: indexCall.Fun.Pos(),
End: indexCall.Fun.End(),
@@ -374,14 +411,31 @@ func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afte
// 2. nonnegative - a condition equivalent to i >= 0
// 3. beforeSlice - a slice of `s` that matches either s[:i], s[0:i]
// 4. afterSlice - a slice of `s` that matches one of: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
-func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) {
+//
+// Additionally, all beforeSlice and afterSlice uses must be dominated by a
+// nonnegative guard on i (i.e., inside the body of an if whose condition
+// checks i >= 0, or in the else of a negative check, or after an
+// early-return negative check). This ensures that the rewrite from
+// s[i+len(sep):] to "after" preserves semantics, since when i == -1,
+// s[i+len(sep):] may yield a valid substring (e.g. s[0:] for single-byte
+// separators), but "after" would be "".
+//
+// When len(substr)==1, it's safe to use s[i+1:] even when i < 0.
+// Otherwise, each replacement of s[i+1:] must be guarded by a check
+// that i is nonnegative.
+func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr, iObj types.Object) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) {
+ requireGuard := true
+ if l := constSubstrLen(info, substr); l != -1 && l != 1 {
+ requireGuard = false
+ }
+
use := func(cur inspector.Cursor) bool {
ek := cur.ParentEdgeKind()
n := cur.Parent().Node()
switch ek {
case edge.BinaryExpr_X, edge.BinaryExpr_Y:
check := n.(*ast.BinaryExpr)
- switch checkIdxComparison(info, check) {
+ switch checkIdxComparison(info, check, iObj) {
case -1:
negative = append(negative, check)
return true
@@ -397,10 +451,10 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a
if slice, ok := cur.Parent().Parent().Node().(*ast.SliceExpr); ok &&
sameObject(info, s, slice.X) &&
slice.Max == nil {
- if isBeforeSlice(info, ek, slice) {
+ if isBeforeSlice(info, ek, slice) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
beforeSlice = append(beforeSlice, slice)
return true
- } else if isAfterSlice(info, ek, slice, substr) {
+ } else if isAfterSlice(info, ek, slice, substr) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
afterSlice = append(afterSlice, slice)
return true
}
@@ -410,10 +464,10 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a
// Check that the thing being sliced is s and that the slice doesn't
// have a max index.
if sameObject(info, s, slice.X) && slice.Max == nil {
- if isBeforeSlice(info, ek, slice) {
+ if isBeforeSlice(info, ek, slice) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
beforeSlice = append(beforeSlice, slice)
return true
- } else if isAfterSlice(info, ek, slice, substr) {
+ } else if isAfterSlice(info, ek, slice, substr) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) {
afterSlice = append(afterSlice, slice)
return true
}
@@ -465,8 +519,15 @@ func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPo
// Since strings.Index returns exactly -1 if the substring is not found, we
// don't need to handle expressions like i <= -3.
// We return 0 if the expression does not match any of these options.
-// We assume that a check passed to checkIdxComparison has i as one of its operands.
-func checkIdxComparison(info *types.Info, check *ast.BinaryExpr) int {
+func checkIdxComparison(info *types.Info, check *ast.BinaryExpr, iObj types.Object) int {
+ isI := func(e ast.Expr) bool {
+ id, ok := e.(*ast.Ident)
+ return ok && info.Uses[id] == iObj
+ }
+ if !isI(check.X) && !isI(check.Y) {
+ return 0
+ }
+
// Ensure that the constant (if any) is on the right.
x, op, y := check.X, check.Op, check.Y
if info.Types[x].Value != nil {
@@ -515,43 +576,48 @@ func isBeforeSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr) bool {
return ek == edge.SliceExpr_High && (slice.Low == nil || isZeroIntConst(info, slice.Low))
}
-// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):],
-// or s[i + k:] where k is a const is equal to len(substr).
-func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool {
- lowExpr, ok := slice.Low.(*ast.BinaryExpr)
- if !ok || slice.High != nil {
- return false
- }
- // Returns true if the expression is a call to len(substr).
- isLenCall := func(expr ast.Expr) bool {
- call, ok := expr.(*ast.CallExpr)
- if !ok || len(call.Args) != 1 {
- return false
- }
- return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen
- }
-
+// constSubstrLen returns the constant length of substr, or -1 if unknown.
+func constSubstrLen(info *types.Info, substr ast.Expr) int {
// Handle len([]byte(substr))
- if is[*ast.CallExpr](substr) {
- call := substr.(*ast.CallExpr)
+ if call, ok := substr.(*ast.CallExpr); ok {
tv := info.Types[call.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) {
// Only one arg in []byte conversion.
substr = call.Args[0]
}
}
- substrLen := -1
substrVal := info.Types[substr].Value
if substrVal != nil {
switch substrVal.Kind() {
case constant.String:
- substrLen = len(constant.StringVal(substrVal))
+ return len(constant.StringVal(substrVal))
case constant.Int:
// constant.Value is a byte literal, e.g. bytes.IndexByte(_, 'a')
// or a numeric byte literal, e.g. bytes.IndexByte(_, 65)
- substrLen = 1
+ // ([]byte(rune) is not legal.)
+ return 1
}
}
+ return -1
+}
+
+// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):],
+// or s[i + k:] where k is a const is equal to len(substr).
+func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool {
+ lowExpr, ok := slice.Low.(*ast.BinaryExpr)
+ if !ok || slice.High != nil {
+ return false
+ }
+ // Returns true if the expression is a call to len(substr).
+ isLenCall := func(expr ast.Expr) bool {
+ call, ok := expr.(*ast.CallExpr)
+ if !ok || len(call.Args) != 1 {
+ return false
+ }
+ return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen
+ }
+
+ substrLen := constSubstrLen(info, substr)
switch ek {
case edge.BinaryExpr_X:
@@ -578,6 +644,75 @@ func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr a
return false
}
+// isSliceIndexGuarded reports whether a use of the index variable i (at the given cursor)
+// inside a slice expression is dominated by a nonnegative guard.
+// A use is considered guarded if any of the following are true:
+// - It is inside the Body of an IfStmt whose condition is a nonnegative check on i.
+// - It is inside the Else of an IfStmt whose condition is a negative check on i.
+// - It is preceded (in the same block) by an IfStmt whose condition is a
+// negative check on i with a terminating body (e.g., early return).
+//
+// Conversely, a use is immediately rejected if:
+// - It is inside the Body of an IfStmt whose condition is a negative check on i.
+// - It is inside the Else of an IfStmt whose condition is a nonnegative check on i.
+//
+// We have already checked (see [hasModifyingUses]) that there are no
+// intervening uses (incl. via aliases) of i that might alter its value.
+func isSliceIndexGuarded(info *types.Info, cur inspector.Cursor, iObj types.Object) bool {
+ for anc := range cur.Enclosing() {
+ switch anc.ParentEdgeKind() {
+ case edge.IfStmt_Body, edge.IfStmt_Else:
+ ifStmt := anc.Parent().Node().(*ast.IfStmt)
+ check := condChecksIdx(info, ifStmt.Cond, iObj)
+ if anc.ParentEdgeKind() == edge.IfStmt_Else {
+ check = -check
+ }
+ if check > 0 {
+ return true // inside nonnegative-guarded block (i >= 0 here)
+ }
+ if check < 0 {
+ return false // inside negative-guarded block (i < 0 here)
+ }
+ case edge.BlockStmt_List:
+ // Check preceding siblings for early-return negative checks.
+ for sib, ok := anc.PrevSibling(); ok; sib, ok = sib.PrevSibling() {
+ ifStmt, ok := sib.Node().(*ast.IfStmt)
+ if ok && condChecksIdx(info, ifStmt.Cond, iObj) < 0 && bodyTerminates(ifStmt.Body) {
+ return true // preceded by early-return negative check
+ }
+ }
+ case edge.FuncDecl_Body, edge.FuncLit_Body:
+ return false // stop at function boundary
+ }
+ }
+ return false
+}
+
+// condChecksIdx reports whether cond is a BinaryExpr that checks
+// the index variable iObj for negativity or non-negativity.
+// Returns -1 for negative (e.g. i < 0), +1 for nonnegative (e.g. i >= 0), 0 otherwise.
+func condChecksIdx(info *types.Info, cond ast.Expr, iObj types.Object) int {
+ binExpr, ok := cond.(*ast.BinaryExpr)
+ if !ok {
+ return 0
+ }
+ return checkIdxComparison(info, binExpr, iObj)
+}
+
+// bodyTerminates reports whether the given block statement unconditionally
+// terminates execution (via return, break, continue, or goto).
+func bodyTerminates(block *ast.BlockStmt) bool {
+ if len(block.List) == 0 {
+ return false
+ }
+ last := block.List[len(block.List)-1]
+ switch last.(type) {
+ case *ast.ReturnStmt, *ast.BranchStmt:
+ return true // return, break, continue, goto
+ }
+ return false
+}
+
// sameObject reports whether we know that the expressions resolve to the same object.
func sameObject(info *types.Info, expr1, expr2 ast.Expr) bool {
if ident1, ok := expr1.(*ast.Ident); ok {
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go
index 7dc11308d..ae6345400 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscutprefix.go
@@ -201,8 +201,7 @@ func stringscutprefix(pass *analysis.Pass) (any, error) {
if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
(astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
- // TODO(adonovan): avoid FreshName when not needed; see errorsastype.
- okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
+ okVarName := freshName(info, index, info.Scopes[ifStmt], ifStmt.Pos(), curIfStmt, curIfStmt, token.NoPos, "ok")
// Have one of:
// if rest := TrimPrefix(s, prefix); rest != s { (ditto Suffix)
// if rest := TrimPrefix(s, prefix); s != rest { (ditto Suffix)
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/unsafefuncs.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/unsafefuncs.go
index 0b36908ec..91c2f378e 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/unsafefuncs.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/unsafefuncs.go
@@ -56,11 +56,6 @@ func unsafefuncs(pass *analysis.Pass) (any, error) {
tUnsafePointer = types.Typ[types.UnsafePointer]
)
- isInteger := func(t types.Type) bool {
- basic, ok := t.Underlying().(*types.Basic)
- return ok && basic.Info()&types.IsInteger != 0
- }
-
// isConversion reports whether e is a conversion T(x).
// If so, it returns T and x.
isConversion := func(curExpr inspector.Cursor) (t types.Type, x inspector.Cursor) {
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go b/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroupgo.go
similarity index 93%
rename from vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go
rename to vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroupgo.go
index abf5885ce..9af2d3bdc 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroupgo.go
@@ -22,18 +22,18 @@ import (
"golang.org/x/tools/internal/versions"
)
-var WaitGroupAnalyzer = &analysis.Analyzer{
- Name: "waitgroup",
- Doc: analyzerutil.MustExtractDoc(doc, "waitgroup"),
+var WaitGroupGoAnalyzer = &analysis.Analyzer{
+ Name: "waitgroupgo",
+ Doc: analyzerutil.MustExtractDoc(doc, "waitgroupgo"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: waitgroup,
- URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#waitgroup",
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#waitgroupgo",
}
-// The waitgroup pass replaces old more complex code with
+// The waitgroupgo pass replaces old more complex code with
// go1.25 added API WaitGroup.Go.
//
// Patterns:
@@ -97,6 +97,9 @@ func waitgroup(pass *analysis.Pass) (any, error) {
if !ok || len(goStmt.Call.Args) != 0 {
continue // go argument is not func(){...}()
}
+ if lit.Type.Results != nil && len(lit.Type.Results.List) > 0 {
+ continue // function literal has return values; wg.Go requires func()
+ }
list := lit.Body.List
if len(list) == 0 {
continue
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go b/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
index a09bfd1c6..1d5366483 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
@@ -57,6 +57,16 @@
//
// fmt.Printf("%s", message)
//
+// The %w verb, as used in fmt.Errorf, should have an operand whose type
+// implements error, not a pointer to a type that implements error. Using a
+// pointer can result in surprising behavior when passing the resulting error to
+// errors.Is and errors.As. In the example below, MyError implements error:
+//
+// // %w wants operand of error type MyError, not pointer type *MyError (defeats errors.Is)
+// err := fmt.Errorf("%w", &MyError{Msg: "my error"})
+//
+// This feature applies only to files using at least Go 1.27.
+//
// # Inferred printf wrappers
//
// Functions that delegate their arguments to fmt.Printf are
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
index 1e130f429..130653127 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
@@ -770,10 +770,6 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
anyIndex = true
}
rng := opRange(formatArg, op)
- if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
- // One error per format is enough.
- return
- }
if op.Verb.Verb == 'w' {
switch kind {
case KindNone, KindPrint, KindPrintf:
@@ -781,6 +777,10 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
return
}
}
+ if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
+ // One error per format is enough.
+ return
+ }
}
// Dotdotdot is hard.
if call.Ellipsis.IsValid() && maxArgIndex >= len(call.Args)-2 {
@@ -966,6 +966,26 @@ func okPrintfArg(pass *analysis.Pass, fileVersion string, call *ast.CallExpr, rn
return false
}
arg := call.Args[verbArgIndex]
+ if verb == 'w' {
+ // Check if arg is of type *E where E implements error.
+ // This diagnostic will help prevent potential misuses of errors.As,
+ // errors.Is, etc. If the %w argument in fmt.Errorf is a pointer to an error
+ // type, where the pointer type also happens to implement error, then calls
+ // to errors.Is using the result of fmt.Errorf may behave unexpectedly.
+ // See golang/go#61342.
+ typ := pass.TypesInfo.Types[arg].Type
+ if t, ok := typ.Underlying().(*types.Pointer); ok && types.Implements(t.Elem(), errorType) && versions.AtLeast(fileVersion, versions.Go1_27) {
+ // qual is a qualifier that returns the package name if different from the current package.
+ qual := func(p *types.Package) string {
+ if p == pass.Pkg {
+ return ""
+ }
+ return p.Name()
+ }
+ pass.ReportRangef(rng, "%%w wants operand of error type %s, not pointer type %s (defeats errors.Is)", types.TypeString(t.Elem(), qual), types.TypeString(typ, qual))
+ return false
+ }
+ }
if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, astutil.Format(pass.Fset, arg))
return false
diff --git a/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go b/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
index 826add2c4..7e97a4a13 100644
--- a/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
+++ b/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
@@ -222,7 +222,7 @@ var (
// in the canonical format, which is a space-separated list of key:"value"
// settings. The value may contain spaces.
func validateStructTag(tag string) error {
- // This code is based on the StructTag.Get code in package reflect.
+ // This code is based on the StructTag.Lookup code in package reflect.
n := 0
for ; tag != ""; n++ {
diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go
index 680a70ca8..a6c17cf63 100644
--- a/vendor/golang.org/x/tools/go/packages/golist.go
+++ b/vendor/golang.org/x/tools/go/packages/golist.go
@@ -61,13 +61,42 @@ func (r *responseDeduper) addAll(dr *DriverResponse) {
}
func (r *responseDeduper) addPackage(p *Package) {
- if r.seenPackages[p.ID] != nil {
+ if prev := r.seenPackages[p.ID]; prev != nil {
+ // Package already seen in a previous response. Merge the file lists,
+ // removing duplicates. This can happen when the same package appears
+ // in multiple driver responses that are being merged together.
+ prev.GoFiles = appendUniqueStrings(prev.GoFiles, p.GoFiles)
+ prev.CompiledGoFiles = appendUniqueStrings(prev.CompiledGoFiles, p.CompiledGoFiles)
+ prev.OtherFiles = appendUniqueStrings(prev.OtherFiles, p.OtherFiles)
+ prev.IgnoredFiles = appendUniqueStrings(prev.IgnoredFiles, p.IgnoredFiles)
+ prev.EmbedFiles = appendUniqueStrings(prev.EmbedFiles, p.EmbedFiles)
+ prev.EmbedPatterns = appendUniqueStrings(prev.EmbedPatterns, p.EmbedPatterns)
return
}
r.seenPackages[p.ID] = p
r.dr.Packages = append(r.dr.Packages, p)
}
+// appendUniqueStrings appends elements from src to dst, skipping duplicates.
+func appendUniqueStrings(dst, src []string) []string {
+ if len(src) == 0 {
+ return dst
+ }
+
+ seen := make(map[string]bool, len(dst))
+ for _, s := range dst {
+ seen[s] = true
+ }
+
+ for _, s := range src {
+ if !seen[s] {
+ dst = append(dst, s)
+ }
+ }
+
+ return dst
+}
+
func (r *responseDeduper) addRoot(id string) {
if r.seenRoots[id] {
return
@@ -832,6 +861,8 @@ func golistargs(cfg *Config, words []string, goVersion int) []string {
// go list doesn't let you pass -test and -find together,
// probably because you'd just get the TestMain.
fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)),
+ // VCS information is not needed when not printing Stale or StaleReason fields
+ "-buildvcs=false",
}
// golang/go#60456: with go1.21 and later, go list serves pgo variants, which
diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go
index b249a5c7e..412ba06b5 100644
--- a/vendor/golang.org/x/tools/go/packages/packages.go
+++ b/vendor/golang.org/x/tools/go/packages/packages.go
@@ -403,6 +403,10 @@ func mergeResponses(responses ...*DriverResponse) *DriverResponse {
if len(responses) == 0 {
return nil
}
+ // No dedup needed
+ if len(responses) == 1 {
+ return responses[0]
+ }
response := newDeduper()
response.dr.NotHandled = false
response.dr.Compiler = responses[0].Compiler
diff --git a/vendor/golang.org/x/tools/go/ssa/builder.go b/vendor/golang.org/x/tools/go/ssa/builder.go
index a75257c8b..242074925 100644
--- a/vendor/golang.org/x/tools/go/ssa/builder.go
+++ b/vendor/golang.org/x/tools/go/ssa/builder.go
@@ -100,6 +100,7 @@ var (
// Type constants.
tBool = types.Typ[types.Bool]
tByte = types.Typ[types.Byte]
+ tRune = types.Universe.Lookup("rune").Type() // prints as "rune" (Typ[Rune] is same as Int32)
tInt = types.Typ[types.Int]
tInvalid = types.Typ[types.Invalid]
tString = types.Typ[types.String]
@@ -892,6 +893,10 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
bound := createBound(fn.Prog, obj)
b.enqueue(bound)
+ // The assignment may widen a type parameter to its
+ // interface bound (case #3 of go.dev/issue.78110).
+ v = emitConv(fn, v, bound.FreeVars[0].Type())
+
c := &MakeClosure{
Fn: bound,
Bindings: []Value{v},
@@ -1270,7 +1275,7 @@ func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 {
// x := T{a: 1}
// x = T{a: x.a}
//
-// all the reads must occur before all the writes. Thus all stores to
+// all the reads must occur before all the writes. Thus all stores to
// loc are emitted to the storebuf sb for later execution.
//
// A CompositeLit may have pointer type only in the recursive (nested)
@@ -1287,28 +1292,35 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero
sb.store(&address{addr, e.Lbrace, nil}, zeroConst(zt))
isZero = true
}
+ var fIndices []int
for i, e := range e.Elts {
- fieldIndex := i
- pos := e.Pos()
- if kv, ok := e.(*ast.KeyValueExpr); ok {
+ var (
+ pos token.Pos
+ fType types.Type
+ )
+
+ if kv, ok := e.(*ast.KeyValueExpr); ok { // tagged field
fname := kv.Key.(*ast.Ident).Name
- for i, n := 0, t.NumFields(); i < n; i++ {
- sf := t.Field(i)
- if sf.Name() == fname {
- fieldIndex = i
- pos = kv.Colon
- e = kv.Value
- break
- }
- }
+ obj, index, _ := types.LookupFieldOrMethod(t, true, fn.declaredPackage().Pkg, fname)
+ fIndices = append(fIndices[:0], index...)
+ pos = kv.Colon
+ e = kv.Value
+ fType = obj.Type()
+ } else { // untagged field
+ fIndices = append(fIndices[:0], i)
+ pos = e.Pos()
+ fType = t.Field(i).Type()
}
- sf := t.Field(fieldIndex)
+
+ last := len(fIndices) - 1
+ v := emitImplicitSelections(fn, addr, fIndices[:last], pos)
+
faddr := &FieldAddr{
- X: addr,
- Field: fieldIndex,
+ X: v,
+ Field: fIndices[last],
}
faddr.setPos(pos)
- faddr.setType(types.NewPointer(sf.Type()))
+ faddr.setType(types.NewPointer(fType))
fn.emit(faddr)
b.assign(fn, &address{addr: faddr, pos: pos, expr: e}, e, isZero, sb)
}
@@ -1467,13 +1479,14 @@ func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) {
var nextCond *BasicBlock
for _, cond := range cc.List {
nextCond = fn.newBasicBlock("switch.next")
- // TODO(adonovan): opt: when tag==vTrue, we'd
- // get better code if we use b.cond(cond)
- // instead of BinOp(EQL, tag, b.expr(cond))
- // followed by If. Don't forget conversions
- // though.
- cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos())
- emitIf(fn, cond, body, nextCond)
+ // For boolean switches, emit short-circuit control flow,
+ // just like an if/else-chain.
+ if tag == vTrue && !isNonTypeParamInterface(fn.info.Types[cond].Type) {
+ b.cond(fn, cond, body, nextCond)
+ } else {
+ c := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos())
+ emitIf(fn, c, body, nextCond)
+ }
fn.currentBlock = nextCond
}
fn.currentBlock = body
@@ -2149,13 +2162,6 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.
// done: (target of break)
//
- if tk == nil {
- tk = tInvalid
- }
- if tv == nil {
- tv = tInvalid
- }
-
rng := &Range{X: x}
rng.setPos(pos)
rng.setType(tRangeIter)
@@ -2165,14 +2171,29 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.
emitJump(fn, loop)
fn.currentBlock = loop
+ var ak, av types.Type
+ isString := false
+ if m, ok := typeparams.CoreType(x.Type()).(*types.Map); ok {
+ ak, av = m.Key(), m.Elem()
+ } else {
+ isString = true
+ ak, av = tInt, tRune
+ }
+ if tk == nil {
+ ak = tInvalid
+ }
+ if tv == nil {
+ av = tInvalid
+ }
+
okv := &Next{
Iter: it,
- IsString: isBasic(typeparams.CoreType(x.Type())),
+ IsString: isString,
}
okv.setType(types.NewTuple(
varOk,
- newVar("k", tk),
- newVar("v", tv),
+ newVar("k", ak),
+ newVar("v", av),
))
fn.emit(okv)
@@ -2181,11 +2202,14 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token.
emitIf(fn, emitExtract(fn, okv, 0), body, done)
fn.currentBlock = body
- if tk != tInvalid {
- k = emitExtract(fn, okv, 1)
+ // The assignment may widen a map or string
+ // key/value to a variable's interface type
+ // (cases #1 and #2 of go.dev/issue/78110).
+ if tk != nil {
+ k = emitConv(fn, emitExtract(fn, okv, 1), tk)
}
- if tv != tInvalid {
- v = emitExtract(fn, okv, 2)
+ if tv != nil {
+ v = emitConv(fn, emitExtract(fn, okv, 2), tv)
}
return
}
diff --git a/vendor/golang.org/x/tools/go/ssa/sanity.go b/vendor/golang.org/x/tools/go/ssa/sanity.go
index c47a137c8..5bd5d97df 100644
--- a/vendor/golang.org/x/tools/go/ssa/sanity.go
+++ b/vendor/golang.org/x/tools/go/ssa/sanity.go
@@ -16,6 +16,8 @@ import (
"os"
"slices"
"strings"
+
+ "golang.org/x/tools/internal/typeparams"
)
type sanity struct {
@@ -154,12 +156,17 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *Lookup:
case *MakeChan:
case *MakeClosure:
- numFree := len(instr.Fn.(*Function).FreeVars)
- numBind := len(instr.Bindings)
- if numFree != numBind {
+ fn := instr.Fn.(*Function)
+ if numFree, numBind := len(fn.FreeVars), len(instr.Bindings); numFree != numBind {
s.errorf("MakeClosure has %d Bindings for function %s with %d free vars",
numBind, instr.Fn, numFree)
-
+ } else {
+ for i, fv := range fn.FreeVars {
+ if !types.Identical(instr.Bindings[i].Type(), fv.Type()) {
+ s.errorf("MakeClosure binding %d for %s has type %s, expected %s",
+ i, fv.Name(), instr.Bindings[i].Type(), fv.Type())
+ }
+ }
}
if recv := instr.Type().(*types.Signature).Recv(); recv != nil {
s.errorf("MakeClosure's type includes receiver %s", recv.Type())
@@ -170,12 +177,42 @@ func (s *sanity) checkInstr(idx int, instr Instruction) {
case *MakeSlice:
case *MapUpdate:
case *Next:
+ rng, ok := instr.Iter.(*Range)
+ if !ok {
+ s.errorf("Next: Iter is %T, not *Range", instr.Iter)
+ }
+ if rng.Type() != tRangeIter {
+ s.errorf("Next: Iter has type %s, expected %s", rng.Type(), tRangeIter)
+ }
+ var ek, ev types.Type
+ switch xt := typeparams.CoreType(rng.X.Type()).(type) {
+ case *types.Basic:
+ if types.Default(xt) != tString {
+ s.errorf("Next: basic operand of Next.Iter (Range) is %s, want string or untyped string", xt)
+ }
+ ek, ev = tInt, tRune
+ case *types.Map:
+ ek, ev = xt.Key(), xt.Elem()
+ }
+
+ res := instr.Type().(*types.Tuple) // (ok bool, k K, v V), but K or V may be invalid if unused
+ if !types.Identical(res.At(1).Type(), ek) && res.At(1).Type() != tInvalid {
+ s.errorf("Next: key type %s does not match map key type %s", res.At(1).Type(), ek)
+ }
+ if !types.Identical(res.At(2).Type(), ev) && res.At(2).Type() != tInvalid {
+ s.errorf("Next: value type %s does not match map value type %s", res.At(2).Type(), ev)
+ }
+
case *Range:
case *RunDefers:
case *Select:
case *Send:
case *Slice:
case *Store:
+ if !types.Identical(instr.Val.Type(), typeparams.CoreType(instr.Addr.Type()).(*types.Pointer).Elem()) {
+ s.errorf("Store: value type %s does not match address type %s",
+ instr.Val.Type(), instr.Addr.Type())
+ }
case *TypeAssert:
case *UnOp:
case *DebugRef:
diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
index 56723d1f8..77aad553d 100644
--- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
+++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
@@ -524,7 +524,7 @@ func (f *finder) find(T types.Type, path []byte) []byte {
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
if f.seenMethods[m] {
- return nil
+ continue // break cycles (see TestIssue70418)
}
path2 := appendOpArg(path, opMethod, i)
if m == f.obj {
diff --git a/vendor/golang.org/x/tools/internal/astutil/comment.go b/vendor/golang.org/x/tools/internal/astutil/comment.go
index 7e52aeaaa..5ed4765c7 100644
--- a/vendor/golang.org/x/tools/internal/astutil/comment.go
+++ b/vendor/golang.org/x/tools/internal/astutil/comment.go
@@ -31,7 +31,7 @@ func Deprecation(doc *ast.CommentGroup) string {
// -- plundered from the future (CL 605517, issue #68021) --
-// TODO(adonovan): replace with ast.Directive after go1.25 (#68021).
+// TODO(adonovan): replace with ast.Directive in go1.26 (#68021).
// Beware of our local mods to handle analysistest
// "want" comments on the same line.
diff --git a/vendor/golang.org/x/tools/internal/astutil/stringlit.go b/vendor/golang.org/x/tools/internal/astutil/stringlit.go
index ce1e7de88..eb49d4512 100644
--- a/vendor/golang.org/x/tools/internal/astutil/stringlit.go
+++ b/vendor/golang.org/x/tools/internal/astutil/stringlit.go
@@ -40,20 +40,64 @@ func PosInStringLiteral(lit *ast.BasicLit, offset int) (token.Pos, error) {
return 0, fmt.Errorf("invalid offset")
}
+ pos, _ := walkStringLiteral(lit, lit.End(), offset)
+ return pos, nil
+}
+
+// OffsetInStringLiteral returns the byte offset within the logical (unquoted)
+// string corresponding to the specified source position.
+func OffsetInStringLiteral(lit *ast.BasicLit, pos token.Pos) (int, error) {
+ if !NodeContainsPos(lit, pos) {
+ return 0, fmt.Errorf("invalid position")
+ }
+
+ raw := lit.Value
+
+ value, err := strconv.Unquote(raw)
+ if err != nil {
+ return 0, err
+ }
+
+ _, offset := walkStringLiteral(lit, pos, len(value))
+ return offset, nil
+}
+
+// walkStringLiteral iterates through the raw string literal to map between
+// a file position and a logical byte offset. It stops when it reaches
+// either the targetPos or the targetOffset.
+//
+// TODO(hxjiang): consider making an iterator.
+func walkStringLiteral(lit *ast.BasicLit, targetPos token.Pos, targetOffset int) (token.Pos, int) {
+ raw := lit.Value
+ norm := int(lit.End()-lit.Pos()) > len(lit.Value)
+
// remove quotes
quote := raw[0] // '"' or '`'
raw = raw[1 : len(raw)-1]
var (
- i = 0 // byte index within logical value
- pos = lit.ValuePos + 1 // position within literal
+ i = 0 // byte index within logical value
+ pos = lit.Pos() + 1 // position within literal
)
- for raw != "" && i < offset {
+
+ for raw != "" {
r, _, rest, _ := strconv.UnquoteChar(raw, quote) // can't fail
sz := len(raw) - len(rest) // length of literal char in raw bytes
- pos += token.Pos(sz)
+
+ nextPos := pos + token.Pos(sz)
+ if norm && r == '\n' {
+ nextPos++
+ }
+ nextI := i + utf8.RuneLen(r) // length of logical char in "cooked" bytes
+
+ if nextPos > targetPos || nextI > targetOffset {
+ break
+ }
+
raw = raw[sz:]
- i += utf8.RuneLen(r)
+ i = nextI
+ pos = nextPos
}
- return pos, nil
+
+ return pos, i
}
diff --git a/vendor/golang.org/x/tools/internal/astutil/util.go b/vendor/golang.org/x/tools/internal/astutil/util.go
index 6378b50c5..b855a5600 100644
--- a/vendor/golang.org/x/tools/internal/astutil/util.go
+++ b/vendor/golang.org/x/tools/internal/astutil/util.go
@@ -15,40 +15,6 @@ import (
"golang.org/x/tools/internal/moreiters"
)
-// PreorderStack traverses the tree rooted at root,
-// calling f before visiting each node.
-//
-// Each call to f provides the current node and traversal stack,
-// consisting of the original value of stack appended with all nodes
-// from root to n, excluding n itself. (This design allows calls
-// to PreorderStack to be nested without double counting.)
-//
-// If f returns false, the traversal skips over that subtree. Unlike
-// [ast.Inspect], no second call to f is made after visiting node n.
-// In practice, the second call is nearly always used only to pop the
-// stack, and it is surprisingly tricky to do this correctly; see
-// https://go.dev/issue/73319.
-//
-// TODO(adonovan): replace with [ast.PreorderStack] when go1.25 is assured.
-func PreorderStack(root ast.Node, stack []ast.Node, f func(n ast.Node, stack []ast.Node) bool) {
- before := len(stack)
- ast.Inspect(root, func(n ast.Node) bool {
- if n != nil {
- if !f(n, stack) {
- // Do not push, as there will be no corresponding pop.
- return false
- }
- stack = append(stack, n) // push
- } else {
- stack = stack[:len(stack)-1] // pop
- }
- return true
- })
- if len(stack) != before {
- panic("push/pop mismatch")
- }
-}
-
// NodeContains reports whether the Pos/End range of node n encloses
// the given range.
//
@@ -181,6 +147,10 @@ func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos
// extraneous whitespace and comments. Use it in new code instead of
// PathEnclosingInterval. When the exact extent of a node is known,
// use [Cursor.FindByPos] instead.
+//
+// TODO(hxjiang): Consider refactoring the function signature. It is currently
+// confusing that an error is returned even when a valid enclosing node is
+// successfully found. Consider grouping all cursors into one struct.
func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) {
curEnclosing, ok := curFile.FindByPos(start, end)
if !ok {
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader.go
similarity index 88%
rename from vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go
rename to vendor/golang.org/x/tools/internal/gcimporter/ureader.go
index 2e0d80585..3db62b890 100644
--- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go
+++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader.go
@@ -35,6 +35,10 @@ type pkgReader struct {
// laterFns holds functions that need to be invoked at the end of
// import reading.
+ //
+ // TODO(mdempsky): Is it safe to have a single "later" slice or do
+ // we need to have multiple passes? See comments on CL 386002 and
+ // go.dev/issue/52104.
laterFns []func()
// laterFors is used in case of 'type A B' to ensure that B is processed before A.
laterFors map[types.Type]int
@@ -158,12 +162,11 @@ type reader struct {
// A readerDict holds the state for type parameters that parameterize
// the current unified IR element.
type readerDict struct {
- // bounds is a slice of typeInfos corresponding to the underlying
- // bounds of the element's type parameters.
- bounds []typeInfo
+ rtbounds []typeInfo // contains constraint types for each parameter in rtparams
+ rtparams []*types.TypeParam // contains receiver type parameters for an element
- // tparams is a slice of the constructed TypeParams for the element.
- tparams []*types.TypeParam
+ tbounds []typeInfo // contains constraint types for each parameter in tparams
+ tparams []*types.TypeParam // contains type parameters for an element
// derived is a slice of types derived from tparams, which may be
// instantiated while reading the current element.
@@ -353,7 +356,11 @@ func (r *reader) doTyp() (res types.Type) {
return name.Type()
case pkgbits.TypeTypeParam:
- return r.dict.tparams[r.Len()]
+ n := r.Len()
+ if n < len(r.dict.rtbounds) {
+ return r.dict.rtparams[n]
+ }
+ return r.dict.tparams[n-len(r.dict.rtbounds)]
case pkgbits.TypeArray:
len := int64(r.Uint64())
@@ -534,7 +541,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
pos := r.pos()
var tparams []*types.TypeParam
if r.Version().Has(pkgbits.AliasTypeParamNames) {
- tparams = r.typeParamNames()
+ tparams = r.typeParamNames(false)
}
typ := r.typ()
declare(aliases.New(pos, objPkg, objName, typ, tparams))
@@ -547,8 +554,15 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
case pkgbits.ObjFunc:
pos := r.pos()
- tparams := r.typeParamNames()
- sig := r.signature(nil, nil, tparams)
+ var rtparams []*types.TypeParam
+ var recv *types.Var
+ if r.Version().Has(pkgbits.GenericMethods) && r.Bool() {
+ r.selector()
+ rtparams = r.typeParamNames(true)
+ recv = r.param()
+ }
+ tparams := r.typeParamNames(false)
+ sig := r.signature(recv, rtparams, tparams)
declare(types.NewFunc(pos, objPkg, objName, sig))
case pkgbits.ObjType:
@@ -558,7 +572,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
named := types.NewNamed(obj, nil, nil)
declare(obj)
- named.SetTypeParams(r.typeParamNames())
+ named.SetTypeParams(r.typeParamNames(false))
setUnderlying := func(underlying types.Type) {
// If the underlying type is an interface, we need to
@@ -638,9 +652,20 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict {
errorf("unexpected object with %v implicit type parameter(s)", implicits)
}
- dict.bounds = make([]typeInfo, r.Len())
- for i := range dict.bounds {
- dict.bounds[i] = r.typInfo()
+ nreceivers := 0
+ if r.Version().Has(pkgbits.GenericMethods) && r.Bool() {
+ nreceivers = r.Len()
+ }
+ nexplicits := r.Len()
+
+ dict.rtbounds = make([]typeInfo, nreceivers)
+ for i := range dict.rtbounds {
+ dict.rtbounds[i] = r.typInfo()
+ }
+
+ dict.tbounds = make([]typeInfo, nexplicits)
+ for i := range dict.tbounds {
+ dict.tbounds[i] = r.typInfo()
}
dict.derived = make([]derivedInfo, r.Len())
@@ -659,15 +684,24 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict {
return &dict
}
-func (r *reader) typeParamNames() []*types.TypeParam {
+func (r *reader) typeParamNames(isGenMeth bool) []*types.TypeParam {
r.Sync(pkgbits.SyncTypeParamNames)
- // Note: This code assumes it only processes objects without
- // implement type parameters. This is currently fine, because
- // reader is only used to read in exported declarations, which are
- // always package scoped.
+ // Note: This code assumes there are no implicit type parameters.
+ // This is fine since it only reads exported declarations, which
+ // never have implicits.
- if len(r.dict.bounds) == 0 {
+ var in []typeInfo
+ var out *[]*types.TypeParam
+ if isGenMeth {
+ in = r.dict.rtbounds
+ out = &r.dict.rtparams
+ } else {
+ in = r.dict.tbounds
+ out = &r.dict.tparams
+ }
+
+ if len(in) == 0 {
return nil
}
@@ -676,40 +710,34 @@ func (r *reader) typeParamNames() []*types.TypeParam {
// create all the TypeNames and TypeParams, then we construct and
// set the bound type.
- r.dict.tparams = make([]*types.TypeParam, len(r.dict.bounds))
- for i := range r.dict.bounds {
+ // We have to save tparams outside of the closure, because typeParamNames
+ // can be called multiple times with the same dictionary instance.
+ tparams := make([]*types.TypeParam, len(in))
+ *out = tparams
+
+ for i := range in {
pos := r.pos()
pkg, name := r.localIdent()
tname := types.NewTypeName(pos, pkg, name, nil)
- r.dict.tparams[i] = types.NewTypeParam(tname, nil)
+ tparams[i] = types.NewTypeParam(tname, nil)
}
- typs := make([]types.Type, len(r.dict.bounds))
- for i, bound := range r.dict.bounds {
- typs[i] = r.p.typIdx(bound, r.dict)
+ // The reader dictionary will continue mutating before we have time
+ // to call delayed functions; make a local copy of the constraints.
+ types := make([]types.Type, len(in))
+ for i, info := range in {
+ types[i] = r.p.typIdx(info, r.dict)
}
- // TODO(mdempsky): This is subtle, elaborate further.
- //
- // We have to save tparams outside of the closure, because
- // typeParamNames() can be called multiple times with the same
- // dictionary instance.
- //
- // Also, this needs to happen later to make sure SetUnderlying has
- // been called.
- //
- // TODO(mdempsky): Is it safe to have a single "later" slice or do
- // we need to have multiple passes? See comments on CL 386002 and
- // go.dev/issue/52104.
- tparams := r.dict.tparams
+ // This needs to happen later to make sure SetUnderlying has been called.
r.p.later(func() {
- for i, typ := range typs {
+ for i, typ := range types {
tparams[i].SetConstraint(typ)
}
})
- return r.dict.tparams
+ return tparams
}
func (r *reader) method() *types.Func {
@@ -717,7 +745,7 @@ func (r *reader) method() *types.Func {
pos := r.pos()
pkg, name := r.selector()
- rparams := r.typeParamNames()
+ rparams := r.typeParamNames(false)
sig := r.signature(r.param(), rparams, nil)
_ = r.pos() // TODO(mdempsky): Remove; this is a hacker for linker.go.
diff --git a/vendor/golang.org/x/tools/internal/gocommand/version.go b/vendor/golang.org/x/tools/internal/gocommand/version.go
index 446c5846a..cce290c41 100644
--- a/vendor/golang.org/x/tools/internal/gocommand/version.go
+++ b/vendor/golang.org/x/tools/internal/gocommand/version.go
@@ -26,6 +26,9 @@ func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) {
inv.BuildFlags = nil // This is not a build command.
inv.ModFlag = ""
inv.ModFile = ""
+ // Set GO111MODULE=off so that we are immune to errors in go.{work,mod}.
+ // Unfortunately, this breaks the Go 1.21+ toolchain directive and
+ // may affect the set of ReleaseTags; see #68495.
inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off")
stdoutBytes, err := r.Run(ctx, inv)
diff --git a/vendor/golang.org/x/tools/internal/goplsexport/export.go b/vendor/golang.org/x/tools/internal/goplsexport/export.go
index b0572f596..57c15c414 100644
--- a/vendor/golang.org/x/tools/internal/goplsexport/export.go
+++ b/vendor/golang.org/x/tools/internal/goplsexport/export.go
@@ -9,9 +9,11 @@ package goplsexport
import "golang.org/x/tools/go/analysis"
var (
- ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
- StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyzer
- PlusBuildModernizer *analysis.Analyzer // = modernize.plusbuildAnalyzer
- StringsCutModernizer *analysis.Analyzer // = modernize.stringscutAnalyzer
- UnsafeFuncsModernizer *analysis.Analyzer // = modernize.unsafeFuncsAnalyzer
+ 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
)
diff --git a/vendor/golang.org/x/tools/internal/imports/source_modindex.go b/vendor/golang.org/x/tools/internal/imports/source_modindex.go
deleted file mode 100644
index ca745d4a1..000000000
--- a/vendor/golang.org/x/tools/internal/imports/source_modindex.go
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package imports
-
-import (
- "context"
- "sync"
- "time"
-
- "golang.org/x/tools/internal/modindex"
-)
-
-// This code is here rather than in the modindex package
-// to avoid import loops
-
-// TODO(adonovan): this code is only used by a test in this package.
-// Can we delete it? Or is there a plan to call NewIndexSource from
-// cmd/goimports?
-
-// implements Source using modindex, so only for module cache.
-//
-// this is perhaps over-engineered. A new Index is read at first use.
-// And then Update is called after every 15 minutes, and a new Index
-// is read if the index changed. It is not clear the Mutex is needed.
-type IndexSource struct {
- modcachedir string
- mu sync.Mutex
- index *modindex.Index // (access via getIndex)
- expires time.Time
-}
-
-// create a new Source. Called from NewView in cache/session.go.
-func NewIndexSource(cachedir string) *IndexSource {
- return &IndexSource{modcachedir: cachedir}
-}
-
-func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) {
- /// This is used by goimports to resolve the package names of imports of the
- // current package, which is irrelevant for the module cache.
- return nil, nil
-}
-
-func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) {
- index, err := s.getIndex()
- if err != nil {
- return nil, err
- }
- var cs []modindex.Candidate
- for pkg, nms := range missing {
- for nm := range nms {
- x := index.Lookup(pkg, nm, false)
- cs = append(cs, x...)
- }
- }
- found := make(map[string]*Result)
- for _, c := range cs {
- var x *Result
- if x = found[c.ImportPath]; x == nil {
- x = &Result{
- Import: &ImportInfo{
- ImportPath: c.ImportPath,
- Name: "",
- },
- Package: &PackageInfo{
- Name: c.PkgName,
- Exports: make(map[string]bool),
- },
- }
- found[c.ImportPath] = x
- }
- x.Package.Exports[c.Name] = true
- }
- var ans []*Result
- for _, x := range found {
- ans = append(ans, x)
- }
- return ans, nil
-}
-
-func (s *IndexSource) getIndex() (*modindex.Index, error) {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- // (s.index = nil => s.expires is zero,
- // so the first condition is strictly redundant.
- // But it makes the postcondition very clear.)
- if s.index == nil || time.Now().After(s.expires) {
- index, err := modindex.Update(s.modcachedir)
- if err != nil {
- return nil, err
- }
- s.index = index
- s.expires = index.ValidAt.Add(15 * time.Minute) // (refresh period)
- }
- // Inv: s.index != nil
-
- return s.index, nil
-}
diff --git a/vendor/golang.org/x/tools/internal/modindex/directories.go b/vendor/golang.org/x/tools/internal/modindex/directories.go
deleted file mode 100644
index 9a963744b..000000000
--- a/vendor/golang.org/x/tools/internal/modindex/directories.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package modindex
-
-import (
- "fmt"
- "log"
- "os"
- "path/filepath"
- "regexp"
- "strings"
- "sync"
- "time"
-
- "golang.org/x/mod/semver"
- "golang.org/x/tools/internal/gopathwalk"
-)
-
-type directory struct {
- path string // relative to GOMODCACHE
- importPath string
- version string // semantic version
-}
-
-// bestDirByImportPath returns the best directory for each import
-// path, where "best" means most recent semantic version. These import
-// paths are inferred from the GOMODCACHE-relative dir names in dirs.
-func bestDirByImportPath(dirs []string) (map[string]directory, error) {
- dirsByPath := make(map[string]directory)
- for _, dir := range dirs {
- importPath, version, err := dirToImportPathVersion(dir)
- if err != nil {
- return nil, err
- }
- new := directory{
- path: dir,
- importPath: importPath,
- version: version,
- }
- if old, ok := dirsByPath[importPath]; !ok || compareDirectory(new, old) < 0 {
- dirsByPath[importPath] = new
- }
- }
- return dirsByPath, nil
-}
-
-// compareDirectory defines an ordering of path@version directories,
-// by descending version, then by ascending path.
-func compareDirectory(x, y directory) int {
- if sign := -semver.Compare(x.version, y.version); sign != 0 {
- return sign // latest first
- }
- return strings.Compare(string(x.path), string(y.path))
-}
-
-// modCacheRegexp splits a relpathpath into module, module version, and package.
-var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
-
-// dirToImportPathVersion computes import path and semantic version
-// from a GOMODCACHE-relative directory name.
-func dirToImportPathVersion(dir string) (string, string, error) {
- m := modCacheRegexp.FindStringSubmatch(string(dir))
- // m[1] is the module path
- // m[2] is the version major.minor.patch(- 1 && flds[1][1] == 'D',
- }
- if px.Type == Func {
- n, err := strconv.Atoi(flds[2])
- if err != nil {
- continue // should never happen
- }
- px.Results = int16(n)
- if len(flds) >= 4 {
- sig := strings.Split(flds[3], " ")
- for i := range sig {
- // $ cannot otherwise occur. removing the spaces
- // almost works, but for chan struct{}, e.g.
- sig[i] = strings.Replace(sig[i], "$", " ", -1)
- }
- px.Sig = toFields(sig)
- }
- }
- ans = append(ans, px)
- }
- }
- return ans
-}
-
-func toFields(sig []string) []Field {
- ans := make([]Field, len(sig)/2)
- for i := range ans {
- ans[i] = Field{Arg: sig[2*i], Type: sig[2*i+1]}
- }
- return ans
-}
-
-// benchmarks show this is measurably better than strings.Split
-// split into first 4 fields separated by single space
-func fastSplit(x string) []string {
- ans := make([]string, 0, 4)
- nxt := 0
- start := 0
- for i := 0; i < len(x); i++ {
- if x[i] != ' ' {
- continue
- }
- ans = append(ans, x[start:i])
- nxt++
- start = i + 1
- if nxt >= 3 {
- break
- }
- }
- ans = append(ans, x[start:])
- return ans
-}
-
-func asLexType(c byte) LexType {
- switch c {
- case 'C':
- return Const
- case 'V':
- return Var
- case 'T':
- return Type
- case 'F':
- return Func
- }
- return -1
-}
diff --git a/vendor/golang.org/x/tools/internal/modindex/modindex.go b/vendor/golang.org/x/tools/internal/modindex/modindex.go
deleted file mode 100644
index 5fa285d98..000000000
--- a/vendor/golang.org/x/tools/internal/modindex/modindex.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package modindex contains code for building and searching an
-// [Index] of the Go module cache.
-package modindex
-
-// The directory containing the index, returned by
-// [IndexDir], contains a file index-name- that contains the name
-// of the current index. We believe writing that short file is atomic.
-// [Read] reads that file to get the file name of the index.
-// WriteIndex writes an index with a unique name and then
-// writes that name into a new version of index-name-.
-// ( stands for the CurrentVersion of the index format.)
-
-import (
- "maps"
- "os"
- "path/filepath"
- "slices"
- "strings"
- "time"
-
- "golang.org/x/mod/semver"
-)
-
-// Update updates the index for the specified Go
-// module cache directory, creating it as needed.
-// On success it returns the current index.
-func Update(gomodcache string) (*Index, error) {
- prev, err := Read(gomodcache)
- if err != nil {
- if !os.IsNotExist(err) {
- return nil, err
- }
- prev = nil
- }
- return update(gomodcache, prev)
-}
-
-// update builds, writes, and returns the current index.
-//
-// If old is nil, the new index is built from all of GOMODCACHE;
-// otherwise it is built from the old index plus cache updates
-// since the previous index's time.
-func update(gomodcache string, old *Index) (*Index, error) {
- gomodcache, err := filepath.Abs(gomodcache)
- if err != nil {
- return nil, err
- }
- new, changed, err := build(gomodcache, old)
- if err != nil {
- return nil, err
- }
- if old == nil || changed {
- if err := write(gomodcache, new); err != nil {
- return nil, err
- }
- }
- return new, nil
-}
-
-// build returns a new index for the specified Go module cache (an
-// absolute path).
-//
-// If an old index is provided, only directories more recent than it
-// that it are scanned; older directories are provided by the old
-// Index.
-//
-// The boolean result indicates whether new entries were found.
-func build(gomodcache string, old *Index) (*Index, bool, error) {
- // Set the time window.
- var start time.Time // = dawn of time
- if old != nil {
- start = old.ValidAt
- }
- now := time.Now()
- end := now.Add(24 * time.Hour) // safely in the future
-
- // Enumerate GOMODCACHE package directories.
- // Choose the best (latest) package for each import path.
- pkgDirs := findDirs(gomodcache, start, end)
- dirByPath, err := bestDirByImportPath(pkgDirs)
- if err != nil {
- return nil, false, err
- }
-
- // For each import path it might occur only in
- // dirByPath, only in old, or in both.
- // If both, use the semantically later one.
- var entries []Entry
- if old != nil {
- for _, entry := range old.Entries {
- dir, ok := dirByPath[entry.ImportPath]
- if !ok || semver.Compare(dir.version, entry.Version) <= 0 {
- // New dir is missing or not more recent; use old entry.
- entries = append(entries, entry)
- delete(dirByPath, entry.ImportPath)
- }
- }
- }
-
- // Extract symbol information for all the new directories.
- newEntries := extractSymbols(gomodcache, maps.Values(dirByPath))
- entries = append(entries, newEntries...)
- slices.SortFunc(entries, func(x, y Entry) int {
- if n := strings.Compare(x.PkgName, y.PkgName); n != 0 {
- return n
- }
- return strings.Compare(x.ImportPath, y.ImportPath)
- })
-
- return &Index{
- GOMODCACHE: gomodcache,
- ValidAt: now, // time before the directories were scanned
- Entries: entries,
- }, len(newEntries) > 0, nil
-}
diff --git a/vendor/golang.org/x/tools/internal/modindex/symbols.go b/vendor/golang.org/x/tools/internal/modindex/symbols.go
deleted file mode 100644
index 8e9702d84..000000000
--- a/vendor/golang.org/x/tools/internal/modindex/symbols.go
+++ /dev/null
@@ -1,244 +0,0 @@
-// Copyright 2024 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package modindex
-
-import (
- "fmt"
- "go/ast"
- "go/parser"
- "go/token"
- "go/types"
- "iter"
- "os"
- "path/filepath"
- "runtime"
- "slices"
- "strings"
- "sync"
-
- "golang.org/x/sync/errgroup"
-)
-
-// The name of a symbol contains information about the symbol:
-// T for types, TD if the type is deprecated
-// C for consts, CD if the const is deprecated
-// V for vars, VD if the var is deprecated
-// and for funcs: F ( )*
-// any spaces in are replaced by $s so that the fields
-// of the name are space separated. F is replaced by FD if the func
-// is deprecated.
-type symbol struct {
- pkg string // name of the symbols's package
- name string // declared name
- kind string // T, C, V, or F, followed by D if deprecated
- sig string // signature information, for F
-}
-
-// extractSymbols returns a (new, unordered) array of Entries, one for
-// each provided package directory, describing its exported symbols.
-func extractSymbols(cwd string, dirs iter.Seq[directory]) []Entry {
- var (
- mu sync.Mutex
- entries []Entry
- )
-
- var g errgroup.Group
- g.SetLimit(max(2, runtime.GOMAXPROCS(0)/2))
- for dir := range dirs {
- g.Go(func() error {
- thedir := filepath.Join(cwd, string(dir.path))
- mode := parser.SkipObjectResolution | parser.ParseComments
-
- // Parse all Go files in dir and extract symbols.
- dirents, err := os.ReadDir(thedir)
- if err != nil {
- return nil // log this someday?
- }
- var syms []symbol
- for _, dirent := range dirents {
- if !strings.HasSuffix(dirent.Name(), ".go") ||
- strings.HasSuffix(dirent.Name(), "_test.go") {
- continue
- }
- fname := filepath.Join(thedir, dirent.Name())
- tr, err := parser.ParseFile(token.NewFileSet(), fname, nil, mode)
- if err != nil {
- continue // ignore errors, someday log them?
- }
- syms = append(syms, getFileExports(tr)...)
- }
-
- // Create an entry for the package.
- pkg, names := processSyms(syms)
- if pkg != "" {
- mu.Lock()
- defer mu.Unlock()
- entries = append(entries, Entry{
- PkgName: pkg,
- Dir: dir.path,
- ImportPath: dir.importPath,
- Version: dir.version,
- Names: names,
- })
- }
-
- return nil
- })
- }
- g.Wait() // ignore error
-
- return entries
-}
-
-func getFileExports(f *ast.File) []symbol {
- pkg := f.Name.Name
- if pkg == "main" || pkg == "" {
- return nil
- }
- var ans []symbol
- // should we look for //go:build ignore?
- for _, decl := range f.Decls {
- switch decl := decl.(type) {
- case *ast.FuncDecl:
- if decl.Recv != nil {
- // ignore methods, as we are completing package selections
- continue
- }
- name := decl.Name.Name
- dtype := decl.Type
- // not looking at dtype.TypeParams. That is, treating
- // generic functions just like non-generic ones.
- sig := dtype.Params
- kind := "F"
- if isDeprecated(decl.Doc) {
- kind += "D"
- }
- result := []string{fmt.Sprintf("%d", dtype.Results.NumFields())}
- for _, x := range sig.List {
- // This code creates a string representing the type.
- // TODO(pjw): it may be fragile:
- // 1. x.Type could be nil, perhaps in ill-formed code
- // 2. ExprString might someday change incompatibly to
- // include struct tags, which can be arbitrary strings
- if x.Type == nil {
- // Can this happen without a parse error? (Files with parse
- // errors are ignored in getSymbols)
- continue // maybe report this someday
- }
- tp := types.ExprString(x.Type)
- if len(tp) == 0 {
- // Can this happen?
- continue // maybe report this someday
- }
- // This is only safe if ExprString never returns anything with a $
- // The only place a $ can occur seems to be in a struct tag, which
- // can be an arbitrary string literal, and ExprString does not presently
- // print struct tags. So for this to happen the type of a formal parameter
- // has to be a explicit struct, e.g. foo(x struct{a int "$"}) and ExprString
- // would have to show the struct tag. Even testing for this case seems
- // a waste of effort, but let's remember the possibility
- if strings.Contains(tp, "$") {
- continue
- }
- tp = strings.Replace(tp, " ", "$", -1)
- if len(x.Names) == 0 {
- result = append(result, "_")
- result = append(result, tp)
- } else {
- for _, y := range x.Names {
- result = append(result, y.Name)
- result = append(result, tp)
- }
- }
- }
- sigs := strings.Join(result, " ")
- if s := newsym(pkg, name, kind, sigs); s != nil {
- ans = append(ans, *s)
- }
- case *ast.GenDecl:
- depr := isDeprecated(decl.Doc)
- switch decl.Tok {
- case token.CONST, token.VAR:
- tp := "V"
- if decl.Tok == token.CONST {
- tp = "C"
- }
- if depr {
- tp += "D"
- }
- for _, sp := range decl.Specs {
- for _, x := range sp.(*ast.ValueSpec).Names {
- if s := newsym(pkg, x.Name, tp, ""); s != nil {
- ans = append(ans, *s)
- }
- }
- }
- case token.TYPE:
- tp := "T"
- if depr {
- tp += "D"
- }
- for _, sp := range decl.Specs {
- if s := newsym(pkg, sp.(*ast.TypeSpec).Name.Name, tp, ""); s != nil {
- ans = append(ans, *s)
- }
- }
- }
- }
- }
- return ans
-}
-
-func newsym(pkg, name, kind, sig string) *symbol {
- if len(name) == 0 || !ast.IsExported(name) {
- return nil
- }
- sym := symbol{pkg: pkg, name: name, kind: kind, sig: sig}
- return &sym
-}
-
-func isDeprecated(doc *ast.CommentGroup) bool {
- if doc == nil {
- return false
- }
- // go.dev/wiki/Deprecated Paragraph starting 'Deprecated:'
- // This code fails for /* Deprecated: */, but it's the code from
- // gopls/internal/analysis/deprecated
- for line := range strings.SplitSeq(doc.Text(), "\n\n") {
- if strings.HasPrefix(line, "Deprecated:") {
- return true
- }
- }
- return false
-}
-
-// return the package name and the value for the symbols.
-// if there are multiple packages, choose one arbitrarily
-// the returned slice is sorted lexicographically
-func processSyms(syms []symbol) (string, []string) {
- if len(syms) == 0 {
- return "", nil
- }
- slices.SortFunc(syms, func(l, r symbol) int {
- return strings.Compare(l.name, r.name)
- })
- pkg := syms[0].pkg
- var names []string
- for _, s := range syms {
- if s.pkg != pkg {
- // Symbols came from two files in same dir
- // with different package declarations.
- continue
- }
- var nx string
- if s.sig != "" {
- nx = fmt.Sprintf("%s %s %s", s.name, s.kind, s.sig)
- } else {
- nx = fmt.Sprintf("%s %s", s.name, s.kind)
- }
- names = append(names, nx)
- }
- return pkg, names
-}
diff --git a/vendor/golang.org/x/tools/internal/pkgbits/version.go b/vendor/golang.org/x/tools/internal/pkgbits/version.go
index 53af9df22..0db965274 100644
--- a/vendor/golang.org/x/tools/internal/pkgbits/version.go
+++ b/vendor/golang.org/x/tools/internal/pkgbits/version.go
@@ -28,6 +28,15 @@ const (
// - remove derived info "needed" bool
V2
+ // V3: introduces a more compact format for composite literal element lists
+ // - negative lengths indicate that (some) elements may have keys
+ // - positive lengths indicate that no element has a key
+ // - a negative struct field index indicates an embedded field
+ V3
+
+ // V4: encodes generic methods as standalone function objects
+ V4
+
numVersions = iota
)
@@ -61,6 +70,12 @@ const (
// whether a type was a derived type.
DerivedInfoNeeded
+ // Composite literals use a more compact format for element lists.
+ CompactCompLiterals
+
+ // Generic methods may appear as standalone function objects.
+ GenericMethods
+
numFields = iota
)
@@ -68,6 +83,8 @@ const (
var introduced = [numFields]Version{
Flags: V1,
AliasTypeParamNames: V2,
+ CompactCompLiterals: V3,
+ GenericMethods: V4,
}
// removed is the version a field was removed in or 0 for fields
diff --git a/vendor/golang.org/x/tools/internal/refactor/refactor.go b/vendor/golang.org/x/tools/internal/refactor/refactor.go
index 8664377f8..1d6c05433 100644
--- a/vendor/golang.org/x/tools/internal/refactor/refactor.go
+++ b/vendor/golang.org/x/tools/internal/refactor/refactor.go
@@ -17,10 +17,9 @@ import (
// FreshName returns the name of an identifier that is undefined
// at the specified position, based on the preferred name.
//
-// TODO(adonovan): refine this to choose a fresh name only when there
-// would be a conflict with the existing declaration: it's fine to
-// redeclare a name in a narrower scope so long as there are no free
-// references to the outer name from within the narrower scope.
+// export/use freshName in go/analysis/passes/modernize/modernize.go if you want
+// to generate a fresh name only when necessary (i.e., there is both an existing
+// declaration and some free reference to the name within a narrower scope)
func FreshName(scope *types.Scope, pos token.Pos, preferred string) string {
newName := preferred
for i := 0; ; i++ {
diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go
index 27a2b1792..2e05de464 100644
--- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go
+++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go
@@ -11,7 +11,9 @@ import (
// CoreType returns the core type of T or nil if T does not have a core type.
//
-// See https://go.dev/ref/spec#Core_types for the definition of a core type.
+// As of Go1.25, the notion of a core type has been removed from the language spec.
+// See https://go.dev/blog/coretypes for more details.
+// TODO(mkalil): We should eventually consider removing all uses of CoreType.
func CoreType(T types.Type) types.Type {
U := T.Underlying()
if _, ok := U.(*types.Interface); !ok {
@@ -34,7 +36,7 @@ func CoreType(T types.Type) types.Type {
}
if identical == len(terms) {
- // https://go.dev/ref/spec#Core_types
+ // From the deprecated core types spec:
// "There is a single type U which is the underlying type of all types in the type set of T"
return U
}
@@ -42,7 +44,7 @@ func CoreType(T types.Type) types.Type {
if !ok {
return nil // no core type as identical < len(terms) and U is not a channel.
}
- // https://go.dev/ref/spec#Core_types
+ // From the deprecated core types spec:
// "the type chan E if T contains only bidirectional channels, or the type chan<- E or
// <-chan E depending on the direction of the directional channels present."
for chans := identical; chans < len(terms); chans++ {
diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go
index 7112318fc..6582cc81f 100644
--- a/vendor/golang.org/x/tools/internal/typesinternal/types.go
+++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go
@@ -194,3 +194,51 @@ func Imports(pkg *types.Package, path string) bool {
}
return false
}
+
+// ObjectKind returns a description of the object's kind.
+//
+// from objectKind in go/types
+func ObjectKind(obj types.Object) string {
+ switch obj := obj.(type) {
+ case *types.PkgName:
+ return "package name"
+ case *types.Const:
+ return "constant"
+ case *types.TypeName:
+ if obj.IsAlias() {
+ return "type alias"
+ } else if _, ok := obj.Type().(*types.TypeParam); ok {
+ return "type parameter"
+ } else {
+ return "defined type"
+ }
+ case *types.Var:
+ switch obj.Kind() {
+ case PackageVar:
+ return "package-level variable"
+ case LocalVar:
+ return "local variable"
+ case RecvVar:
+ return "receiver"
+ case ParamVar:
+ return "parameter"
+ case ResultVar:
+ return "result variable"
+ case FieldVar:
+ return "struct field"
+ }
+ case *types.Func:
+ if obj.Signature().Recv() != nil {
+ return "method"
+ } else {
+ return "function"
+ }
+ case *types.Label:
+ return "label"
+ case *types.Builtin:
+ return "built-in function"
+ case *types.Nil:
+ return "untyped nil"
+ }
+ return "unknown symbol"
+}
diff --git a/vendor/golang.org/x/tools/internal/versions/features.go b/vendor/golang.org/x/tools/internal/versions/features.go
index cdd36c388..360a5b552 100644
--- a/vendor/golang.org/x/tools/internal/versions/features.go
+++ b/vendor/golang.org/x/tools/internal/versions/features.go
@@ -19,6 +19,7 @@ const (
Go1_24 = "go1.24"
Go1_25 = "go1.25"
Go1_26 = "go1.26"
+ Go1_27 = "go1.27"
)
// Future is an invalid unknown Go version sometime in the future.
diff --git a/vendor/golang.org/x/tools/refactor/satisfy/find.go b/vendor/golang.org/x/tools/refactor/satisfy/find.go
index bb3837553..3d21ce6d2 100644
--- a/vendor/golang.org/x/tools/refactor/satisfy/find.go
+++ b/vendor/golang.org/x/tools/refactor/satisfy/find.go
@@ -395,8 +395,11 @@ func (f *Finder) expr(e ast.Expr) types.Type {
f.expr(e.X)
case *ast.SelectorExpr:
- if _, ok := f.info.Selections[e]; ok {
- f.expr(e.X) // selection
+ if seln, ok := f.info.Selections[e]; ok {
+ // If e.X is a type (e.g., e is interface{ m() }.m), don't visit it.
+ if seln.Kind() != types.MethodExpr {
+ f.expr(e.X)
+ }
} else {
return f.info.Uses[e.Sel].Type() // qualified identifier
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2ebb8e999..84332810c 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -362,7 +362,7 @@ github.com/ccojocar/zxcvbn-go/utils/math
# github.com/cenkalti/backoff/v5 v5.0.3
## explicit; go 1.23
github.com/cenkalti/backoff/v5
-# github.com/cert-manager/cert-manager v1.20.2 => github.com/openshift/jetstack-cert-manager v1.20.2
+# github.com/cert-manager/cert-manager v1.20.3 => github.com/openshift/jetstack-cert-manager v1.20.3
## explicit; go 1.25.0
github.com/cert-manager/cert-manager/pkg/apis/acme
github.com/cert-manager/cert-manager/pkg/apis/acme/v1
@@ -1249,7 +1249,7 @@ github.com/onsi/gomega/types
# github.com/opencontainers/go-digest v1.0.0
## explicit; go 1.13
github.com/opencontainers/go-digest
-# github.com/openshift/api v0.0.0-20260423140559-e9fad7d4cba1
+# github.com/openshift/api v0.0.0-20260513085653-694421e64aee
## explicit; go 1.25.0
github.com/openshift/api
github.com/openshift/api/apiextensions
@@ -1371,7 +1371,7 @@ github.com/openshift/client-go/route/applyconfigurations/internal
github.com/openshift/client-go/route/applyconfigurations/route/v1
github.com/openshift/client-go/route/clientset/versioned/scheme
github.com/openshift/client-go/route/clientset/versioned/typed/route/v1
-# github.com/openshift/library-go v0.0.0-20260429151228-ecbc792a4313
+# github.com/openshift/library-go v0.0.0-20260512161954-889c2cd3e381
## explicit; go 1.25.0
github.com/openshift/library-go/pkg/apiserver/jsonpatch
github.com/openshift/library-go/pkg/authorization/hardcodedauthorizer
@@ -1806,7 +1806,7 @@ go.yaml.in/yaml/v2
# go.yaml.in/yaml/v3 v3.0.4
## explicit; go 1.16
go.yaml.in/yaml/v3
-# golang.org/x/crypto v0.49.0
+# golang.org/x/crypto v0.52.0
## explicit; go 1.25.0
golang.org/x/crypto/chacha20
golang.org/x/crypto/chacha20poly1305
@@ -1826,14 +1826,14 @@ golang.org/x/exp/slices
# golang.org/x/exp/typeparams v0.0.0-20251023183803-a4bb9ffd2546
## explicit; go 1.24.0
golang.org/x/exp/typeparams
-# golang.org/x/mod v0.33.0
-## explicit; go 1.24.0
+# golang.org/x/mod v0.35.0
+## explicit; go 1.25.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/module
golang.org/x/mod/semver
golang.org/x/mod/sumdb/dirhash
-# golang.org/x/net v0.52.0
+# golang.org/x/net v0.55.0
## explicit; go 1.25.0
golang.org/x/net/context
golang.org/x/net/html
@@ -1867,15 +1867,15 @@ golang.org/x/oauth2/jwt
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
golang.org/x/sync/singleflight
-# golang.org/x/sys v0.42.0
+# golang.org/x/sys v0.45.0
## explicit; go 1.25.0
golang.org/x/sys/cpu
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
golang.org/x/sys/windows/registry
-# golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4
-## explicit; go 1.24.0
+# golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa
+## explicit; go 1.25.0
golang.org/x/telemetry
golang.org/x/telemetry/counter
golang.org/x/telemetry/internal/config
@@ -1885,10 +1885,10 @@ golang.org/x/telemetry/internal/crashmonitor
golang.org/x/telemetry/internal/mmap
golang.org/x/telemetry/internal/telemetry
golang.org/x/telemetry/internal/upload
-# golang.org/x/term v0.41.0
+# golang.org/x/term v0.43.0
## explicit; go 1.25.0
golang.org/x/term
-# golang.org/x/text v0.35.0
+# golang.org/x/text v0.37.0
## explicit; go 1.25.0
golang.org/x/text/cases
golang.org/x/text/encoding
@@ -1923,8 +1923,8 @@ golang.org/x/text/width
# golang.org/x/time v0.14.0
## explicit; go 1.24.0
golang.org/x/time/rate
-# golang.org/x/tools v0.42.0
-## explicit; go 1.24.0
+# golang.org/x/tools v0.44.0
+## explicit; go 1.25.0
golang.org/x/tools/cover
golang.org/x/tools/go/analysis
golang.org/x/tools/go/analysis/passes/appends
@@ -2010,7 +2010,6 @@ golang.org/x/tools/internal/gocommand
golang.org/x/tools/internal/gopathwalk
golang.org/x/tools/internal/goplsexport
golang.org/x/tools/internal/imports
-golang.org/x/tools/internal/modindex
golang.org/x/tools/internal/moreiters
golang.org/x/tools/internal/packagepath
golang.org/x/tools/internal/packagesinternal
@@ -3206,7 +3205,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client
sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/client/metrics
sigs.k8s.io/apiserver-network-proxy/konnectivity-client/pkg/common/metrics
sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client
-# sigs.k8s.io/controller-runtime v0.23.1
+# sigs.k8s.io/controller-runtime v0.23.3
## explicit; go 1.25.0
sigs.k8s.io/controller-runtime
sigs.k8s.io/controller-runtime/pkg/builder
@@ -3429,4 +3428,4 @@ sigs.k8s.io/structured-merge-diff/v6/value
# sigs.k8s.io/yaml v1.6.0
## explicit; go 1.22
sigs.k8s.io/yaml
-# github.com/cert-manager/cert-manager => github.com/openshift/jetstack-cert-manager v1.20.2
+# github.com/cert-manager/cert-manager => github.com/openshift/jetstack-cert-manager v1.20.3
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/defaulter_custom.go b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/defaulter_custom.go
index d946966d4..9fec8003f 100644
--- a/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/defaulter_custom.go
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/webhook/admission/defaulter_custom.go
@@ -129,11 +129,7 @@ func (h *defaulterForType[T]) Handle(ctx context.Context, req Request) Response
return Errored(http.StatusBadRequest, err)
}
- // Keep a copy of the object if needed
- var originalObj T
- if !h.removeUnknownOrOmitableFields {
- originalObj = obj.DeepCopyObject().(T)
- }
+ originalObj := obj.DeepCopyObject().(T)
// Default the object
if err := h.defaulter.Default(ctx, obj); err != nil {
@@ -144,6 +140,21 @@ func (h *defaulterForType[T]) Handle(ctx context.Context, req Request) Response
return Denied(err.Error())
}
+ // If the object is not changed, there's no reason to go through the expensive patch calculation below.
+ // Note: While jsonpatch.CreatePatch short-circuits if both byte arrays are equal this is likely never the case.
+ // * json.Marshal that we use below sorts fields alphabetically
+ // * for builtin types the apiserver also sorts alphabetically (but it seems like it adds an empty line at the end)
+ // * for CRDs the apiserver uses the field order in the OpenAPI schema which very likely is not alphabetically sorted
+ // Note: If removeUnknownOrOmitableFields is set we have to compute a patch to remove unknown or omitable fields even
+ // if the objects are equal
+ if !h.removeUnknownOrOmitableFields && reflect.DeepEqual(originalObj, obj) {
+ return Response{
+ AdmissionResponse: admissionv1.AdmissionResponse{
+ Allowed: true,
+ },
+ }
+ }
+
// Create the patch
marshalled, err := json.Marshal(obj)
if err != nil {