From: Dmitri Shuralyov Date: Thu, 4 Dec 2025 22:57:40 +0000 (-0500) Subject: all: update vendored x/tools X-Git-Tag: go1.26rc1~2^2~15 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=91267f0a709191d8f1e3f4fa805c9d88856f9957;p=gostls13.git all: update vendored x/tools Pull in the following x/tools changes: - CL 726000: go/analysis/passes/modernize: omitzero: suppress on kubebuilder - CL 726621: internal/refactor/inline: built-ins may affect inference - CL 727041: go/analysis/passes/modernize: fix stringscut false positives - CL 727040: go/analysis/unitchecker: write fixed files to an archive Fixes #76649. Fixes #76287. Fixes #76687. For #71859. [git-generate] go install golang.org/x/build/cmd/updatestd@latest go install golang.org/x/tools/cmd/bundle@latest updatestd -goroot=$(pwd) -branch=internal-branch.go1.26-vendor Change-Id: I0a369ad85b06adab3a977c2c523b8214fb53271a Reviewed-on: https://go-review.googlesource.com/c/go/+/727022 Auto-Submit: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan --- diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 9a1a38bccc..90c3717334 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -11,7 +11,7 @@ require ( golang.org/x/sys v0.38.1-0.20251125153526-08e54827f670 golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec golang.org/x/term v0.37.0 - golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 + golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2 ) require ( diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 52d55d8173..774820d54c 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -22,7 +22,7 @@ golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9 h1:IjQf87/qLz2y0SiCc0uY3DwajALXkAgP1Pxal0mmdrM= golang.org/x/text v0.31.1-0.20251128220601-087616b6cde9/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 h1:i4GzAuZW4RuKXltwKyLYAfk7E1TSKQBxRAI7XKfLjSk= -golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2 h1:2Qqv605Nus9iUp3ErvEU/q92Q3HAzeROztzl9pzAno8= +golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8= rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ= diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go index 7469002f56..45aed7909c 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go @@ -199,12 +199,19 @@ are often used to express optionality. omitzero: suggest replacing omitempty with omitzero for struct fields -The omitzero analyzer identifies uses of the `omitempty` JSON struct tag on -fields that are themselves structs. The `omitempty` tag has no effect on -struct-typed fields. The analyzer offers two suggestions: either remove the +The omitzero analyzer identifies uses of the `omitempty` JSON struct +tag on fields that are themselves structs. For struct-typed fields, +the `omitempty` tag has no effect on the behavior of json.Marshal and +json.Unmarshal. The analyzer offers two suggestions: either remove the tag, or replace it with `omitzero` (added in Go 1.24), which correctly omits the field if the struct value is zero. +However, some other serialization packages (notably kubebuilder, see +https://book.kubebuilder.io/reference/markers.html) may have their own +interpretation of the `json:",omitzero"` tag, so removing it may affect +program behavior. For this reason, the omitzero modernizer will not +make changes in any package that contains +kubebuilder annotations. + Replacing `omitempty` with `omitzero` is a change in behavior. The original code would always encode the struct field, whereas the modified code will omit it if it is a zero-value. diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go index 4a05d64f42..59ba950651 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/omitzero.go @@ -9,6 +9,8 @@ import ( "go/types" "reflect" "strconv" + "strings" + "sync" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" @@ -25,82 +27,106 @@ var OmitZeroAnalyzer = &analysis.Analyzer{ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero", } -func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Field) { - typ := info.TypeOf(curField.Type) - _, ok := typ.Underlying().(*types.Struct) - if !ok { - // Not a struct - return - } - tag := curField.Tag - if tag == nil { - // No tag to check - return - } - // The omitempty tag may be used by other packages besides json, but we should only modify its use with json - tagconv, _ := strconv.Unquote(tag.Value) - match := omitemptyRegex.FindStringSubmatchIndex(tagconv) - if match == nil { - // No omitempty in json tag - return - } - omitEmpty, err := astutil.RangeInStringLiteral(curField.Tag, match[2], match[3]) - if err != nil { - return - } - var remove analysis.Range = omitEmpty +// The omitzero pass searches for instances of "omitempty" in a json field tag on a +// struct. Since "omitfilesUsingGoVersions not have any effect when applied to a struct field, +// it suggests either deleting "omitempty" or replacing it with "omitzero", which +// correctly excludes structs from a json encoding. +func omitzero(pass *analysis.Pass) (any, error) { + // usesKubebuilder reports whether "+kubebuilder:" appears in + // any comment in the package, since it has its own + // interpretation of what omitzero means; see go.dev/issue/76649. + // It is computed once, on demand. + usesKubebuilder := sync.OnceValue[bool](func() bool { + for _, file := range pass.Files { + for _, comment := range file.Comments { + if strings.Contains(comment.Text(), "+kubebuilder:") { + return true + } + } + } + return false + }) + + checkField := func(field *ast.Field) { + typ := pass.TypesInfo.TypeOf(field.Type) + _, ok := typ.Underlying().(*types.Struct) + if !ok { + // Not a struct + return + } + tag := field.Tag + if tag == nil { + // No tag to check + return + } + // The omitempty tag may be used by other packages besides json, but we should only modify its use with json + tagconv, _ := strconv.Unquote(tag.Value) + match := omitemptyRegex.FindStringSubmatchIndex(tagconv) + if match == nil { + // No omitempty in json tag + return + } + omitEmpty, err := astutil.RangeInStringLiteral(field.Tag, match[2], match[3]) + if err != nil { + return + } + var remove analysis.Range = omitEmpty - jsonTag := reflect.StructTag(tagconv).Get("json") - if jsonTag == ",omitempty" { - // Remove the entire struct tag if json is the only package used - if match[1]-match[0] == len(tagconv) { - remove = curField.Tag - } else { - // Remove the json tag if omitempty is the only field - remove, err = astutil.RangeInStringLiteral(curField.Tag, match[0], match[1]) - if err != nil { - return + jsonTag := reflect.StructTag(tagconv).Get("json") + if jsonTag == ",omitempty" { + // Remove the entire struct tag if json is the only package used + if match[1]-match[0] == len(tagconv) { + remove = field.Tag + } else { + // Remove the json tag if omitempty is the only field + remove, err = astutil.RangeInStringLiteral(field.Tag, match[0], match[1]) + if err != nil { + return + } } } - } - pass.Report(analysis.Diagnostic{ - Pos: curField.Tag.Pos(), - End: curField.Tag.End(), - Message: "Omitempty has no effect on nested struct fields", - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: "Remove redundant omitempty tag", - TextEdits: []analysis.TextEdit{ - { - Pos: remove.Pos(), - End: remove.End(), + + // Don't offer a fix if the package seems to use kubebuilder, + // as it has its own intepretation of "omitzero" tags. + // https://book.kubebuilder.io/reference/markers.html + if usesKubebuilder() { + return + } + + pass.Report(analysis.Diagnostic{ + Pos: field.Tag.Pos(), + End: field.Tag.End(), + Message: "Omitempty has no effect on nested struct fields", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Remove redundant omitempty tag", + TextEdits: []analysis.TextEdit{ + { + Pos: remove.Pos(), + End: remove.End(), + }, }, }, - }, - { - Message: "Replace omitempty with omitzero (behavior change)", - TextEdits: []analysis.TextEdit{ - { - Pos: omitEmpty.Pos(), - End: omitEmpty.End(), - NewText: []byte(",omitzero"), + { + Message: "Replace omitempty with omitzero (behavior change)", + TextEdits: []analysis.TextEdit{ + { + Pos: omitEmpty.Pos(), + End: omitEmpty.End(), + NewText: []byte(",omitzero"), + }, }, }, - }, - }}) -} + }}) + } -// The omitzero pass searches for instances of "omitempty" in a json field tag on a -// struct. Since "omitfilesUsingGoVersions not have any effect when applied to a struct field, -// it suggests either deleting "omitempty" or replacing it with "omitzero", which -// correctly excludes structs from a json encoding. -func omitzero(pass *analysis.Pass) (any, error) { for curFile := range filesUsingGoVersion(pass, versions.Go1_24) { for curStruct := range curFile.Preorder((*ast.StructType)(nil)) { for _, curField := range curStruct.Node().(*ast.StructType).Fields.List { - checkOmitEmptyField(pass, pass.TypesInfo, curField) + checkField(curField) } } } + return nil, nil } diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go index bc4ad677cd..954997ad6f 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go @@ -50,9 +50,14 @@ func init() { // The following must hold for a replacement to occur: // // 1. All instances of i and s must be in one of these forms. -// Binary expressions: -// (a): establishing that i < 0: e.g.: i < 0, 0 > i, i == -1, -1 == i -// (b): establishing that i > -1: e.g.: i >= 0, 0 <= i, i == 0, 0 == i +// +// Binary expressions must be inequalities equivalent to +// "Index failed" (e.g. i < 0) or "Index succeeded" (i >= 0), +// or identities such as these (and their negations): +// +// 0 > i (flips left and right) +// i <= -1, -1 >= i (replace strict inequality by non-strict) +// i == -1, -1 == i (Index() guarantees i < 0 => i == -1) // // Slice expressions: // a: s[:i], s[0:i] @@ -86,9 +91,9 @@ func init() { // use(before, after) // } // -// If the condition involving `i` establishes that i > -1, then we replace it with -// `if ok“. Variants listed above include i >= 0, i > 0, and i == 0. -// If the condition is negated (e.g. establishes `i < 0`), we use `if !ok` instead. +// If the condition involving `i` is equivalent to i >= 0, then we replace it with +// `if ok“. +// If the condition is negated (e.g. equivalent to `i < 0`), we use `if !ok` instead. // If the slices of `s` match `s[:i]` or `s[i+len(substr):]` or their variants listed above, // then we replace them with before and after. // @@ -178,16 +183,16 @@ 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. - lessZero, greaterNegOne, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr) + negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr) // 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. - if lessZero == nil && greaterNegOne == nil && beforeSlice == nil && afterSlice == nil { + if negative == nil && nonnegative == nil && beforeSlice == nil && afterSlice == nil { continue } // If the only uses are ok and !ok, don't suggest a Cut() fix - these should be using Contains() - isContains := (len(lessZero) > 0 || len(greaterNegOne) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0 + isContains := (len(negative) > 0 || len(nonnegative) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0 scope := iObj.Parent() var ( @@ -200,7 +205,7 @@ func stringscut(pass *analysis.Pass) (any, error) { // If there will be no uses of ok, before, or after, use the // blank identifier instead. - if len(lessZero) == 0 && len(greaterNegOne) == 0 { + if len(negative) == 0 && len(nonnegative) == 0 { okVarName = "_" } if len(beforeSlice) == 0 { @@ -226,8 +231,8 @@ func stringscut(pass *analysis.Pass) (any, error) { replacedFunc := "Cut" if isContains { replacedFunc = "Contains" - replace(lessZero, "!"+foundVarName) // idx < 0 -> !found - replace(greaterNegOne, foundVarName) // idx > -1 -> found + replace(negative, "!"+foundVarName) // idx < 0 -> !found + replace(nonnegative, foundVarName) // idx > -1 -> found // Replace the assignment with found, and replace the call to // Index or IndexByte with a call to Contains. @@ -244,8 +249,8 @@ func stringscut(pass *analysis.Pass) (any, error) { NewText: []byte("Contains"), }) } else { - replace(lessZero, "!"+okVarName) // idx < 0 -> !ok - replace(greaterNegOne, okVarName) // idx > -1 -> ok + replace(negative, "!"+okVarName) // idx < 0 -> !ok + replace(nonnegative, okVarName) // idx > -1 -> ok replace(beforeSlice, beforeVarName) // s[:idx] -> before replace(afterSlice, afterVarName) // s[idx+k:] -> after @@ -364,11 +369,11 @@ func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afte // one of the following four valid formats, it returns a list of occurrences for // each format. If any of the uses do not match one of the formats, return nil // for all values, since we should not offer a replacement. -// 1. lessZero - a condition involving i establishing that i is negative (e.g. i < 0, 0 > i, i == -1, -1 == i) -// 2. greaterNegOne - a condition involving i establishing that i is non-negative (e.g. i >= 0, 0 <= i, i == 0, 0 == i) +// 1. negative - a condition equivalent to i < 0 +// 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) (lessZero, greaterNegOne, beforeSlice, afterSlice []ast.Expr) { +func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) { use := func(cur inspector.Cursor) bool { ek, _ := cur.ParentEdge() n := cur.Parent().Node() @@ -377,13 +382,13 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a check := n.(*ast.BinaryExpr) switch checkIdxComparison(info, check) { case -1: - lessZero = append(lessZero, check) + negative = append(negative, check) return true case 1: - greaterNegOne = append(greaterNegOne, check) + nonnegative = append(nonnegative, check) return true } - // Check does not establish that i < 0 or i > -1. + // Check is not equivalent to that i < 0 or i >= 0. // Might be part of an outer slice expression like s[i + k] // which requires a different check. // Check that the thing being sliced is s and that the slice @@ -421,7 +426,7 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a return nil, nil, nil, nil } } - return lessZero, greaterNegOne, beforeSlice, afterSlice + return negative, nonnegative, beforeSlice, afterSlice } // hasModifyingUses reports whether any of the uses involve potential @@ -451,52 +456,57 @@ func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPo return false } -// checkIdxComparison reports whether the check establishes that i is negative -// or non-negative. It returns -1 in the first case, 1 in the second, and 0 if -// we can confirm neither condition. We assume that a check passed to -// checkIdxComparison has i as one of its operands. +// checkIdxComparison reports whether the check is equivalent to i < 0 or its negation, or neither. +// For equivalent to i >= 0, we only accept this exact BinaryExpr since +// expressions like i > 0 or i >= 1 make a stronger statement about the value of i. +// We avoid suggesting a fix in this case since it may result in an invalid +// transformation (See golang/go#76687). +// 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 { - // Check establishes that i is negative. - // e.g.: i < 0, 0 > i, i == -1, -1 == i - if check.Op == token.LSS && (isNegativeConst(info, check.Y) || isZeroIntConst(info, check.Y)) || //i < (0 or neg) - check.Op == token.GTR && (isNegativeConst(info, check.X) || isZeroIntConst(info, check.X)) || // (0 or neg) > i - check.Op == token.LEQ && (isNegativeConst(info, check.Y)) || //i <= (neg) - check.Op == token.GEQ && (isNegativeConst(info, check.X)) || // (neg) >= i - check.Op == token.EQL && - (isNegativeConst(info, check.X) || isNegativeConst(info, check.Y)) { // i == neg; neg == i - return -1 + // 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 { + x, op, y = y, flip(op), x } - // Check establishes that i is non-negative. - // e.g.: i >= 0, 0 <= i, i == 0, 0 == i - if check.Op == token.GTR && (isNonNegativeConst(info, check.Y) || isIntLiteral(info, check.Y, -1)) || // i > (non-neg or -1) - check.Op == token.LSS && (isNonNegativeConst(info, check.X) || isIntLiteral(info, check.X, -1)) || // (non-neg or -1) < i - check.Op == token.GEQ && isNonNegativeConst(info, check.Y) || // i >= (non-neg) - check.Op == token.LEQ && isNonNegativeConst(info, check.X) || // (non-neg) <= i - check.Op == token.EQL && - (isNonNegativeConst(info, check.X) || isNonNegativeConst(info, check.Y)) { // i == non-neg; non-neg == i - return 1 + + yIsInt := func(k int64) bool { + return isIntLiteral(info, y, k) } - return 0 -} -// isNegativeConst returns true if the expr is a const int with value < zero. -func isNegativeConst(info *types.Info, expr ast.Expr) bool { - if tv, ok := info.Types[expr]; ok && tv.Value != nil && tv.Value.Kind() == constant.Int { - if v, ok := constant.Int64Val(tv.Value); ok { - return v < 0 - } + if op == token.LSS && yIsInt(0) || // i < 0 + op == token.EQL && yIsInt(-1) || // i == -1 + op == token.LEQ && yIsInt(-1) { // i <= -1 + return -1 // check <=> i is negative } - return false + + if op == token.GEQ && yIsInt(0) || // i >= 0 + op == token.NEQ && yIsInt(-1) || // i != -1 + op == token.GTR && yIsInt(-1) { // i > -1 + return +1 // check <=> i is non-negative + } + + return 0 // unknown } -// isNonNegativeConst returns true if the expr is a const int with value >= zero. -func isNonNegativeConst(info *types.Info, expr ast.Expr) bool { - if tv, ok := info.Types[expr]; ok && tv.Value != nil && tv.Value.Kind() == constant.Int { - if v, ok := constant.Int64Val(tv.Value); ok { - return v >= 0 - } +// flip changes the comparison token as if the operands were flipped. +// It is defined only for == and the four inequalities. +func flip(op token.Token) token.Token { + switch op { + case token.EQL: + return token.EQL // (same) + case token.GEQ: + return token.LEQ + case token.GTR: + return token.LSS + case token.LEQ: + return token.GEQ + case token.LSS: + return token.GTR } - return false + return op } // isBeforeSlice reports whether the SliceExpr is of the form s[:i] or s[0:i]. diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go index 0180a341e5..bc15ef8b96 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go @@ -27,6 +27,7 @@ package unitchecker // printf checker. import ( + "archive/zip" "encoding/gob" "encoding/json" "flag" @@ -74,6 +75,7 @@ type Config struct { VetxOnly bool // run analysis only for facts, not diagnostics VetxOutput string // where to write file of fact information Stdout string // write stdout (e.g. JSON, unified diff) to this file + FixArchive string // write fixed files to this zip archive, if non-empty SucceedOnTypecheckFailure bool // obsolete awful hack; see #18395 and below } @@ -153,7 +155,7 @@ func Run(configFile string, analyzers []*analysis.Analyzer) { // In VetxOnly mode, the analysis is run only for facts. if !cfg.VetxOnly { - code = processResults(fset, cfg.ID, results) + code = processResults(fset, cfg.ID, cfg.FixArchive, results) } os.Exit(code) @@ -177,7 +179,7 @@ func readConfig(filename string) (*Config, error) { return cfg, nil } -func processResults(fset *token.FileSet, id string, results []result) (exit int) { +func processResults(fset *token.FileSet, id, fixArchive string, results []result) (exit int) { if analysisflags.Fix { // Don't print the diagnostics, // but apply all fixes from the root actions. @@ -194,7 +196,40 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int) Diagnostics: res.diagnostics, } } - if err := driverutil.ApplyFixes(fixActions, analysisflags.Diff, false); err != nil { + + // By default, fixes overwrite the original file. + // With the -diff flag, print the diffs to stdout. + // If "go fix" provides a fix archive, we write files + // into it so that mutations happen after the build. + write := func(filename string, content []byte) error { + return os.WriteFile(filename, content, 0644) + } + if fixArchive != "" { + f, err := os.Create(fixArchive) + if err != nil { + log.Fatalf("can't create -fix archive: %v", err) + } + zw := zip.NewWriter(f) + zw.SetComment(id) // ignore error + defer func() { + if err := zw.Close(); err != nil { + log.Fatalf("closing -fix archive zip writer: %v", err) + } + if err := f.Close(); err != nil { + log.Fatalf("closing -fix archive file: %v", err) + } + }() + write = func(filename string, content []byte) error { + f, err := zw.Create(filename) + if err != nil { + return err + } + _, err = f.Write(content) + return err + } + } + + if err := driverutil.ApplyFixes(fixActions, write, analysisflags.Diff, false); err != nil { // Fail when applying fixes failed. log.Print(err) exit = 1 diff --git a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go index 37b09588a7..7769b39beb 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/analysis/driverutil/fix.go @@ -93,11 +93,13 @@ type FixAction struct { // // If printDiff (from the -diff flag) is set, instead of updating the // files it display the final patch composed of all the cleanly merged -// fixes. +// fixes. (It is tempting to factor printDiff as just a variant of +// writeFile that is provided the old and new content, but it's hard +// to generate a good summary that way.) // // TODO(adonovan): handle file-system level aliases such as symbolic // links using robustio.FileID. -func ApplyFixes(actions []FixAction, printDiff, verbose bool) error { +func ApplyFixes(actions []FixAction, writeFile func(filename string, content []byte) error, printDiff, verbose bool) error { generated := make(map[*token.File]bool) // Select fixes to apply. @@ -264,12 +266,11 @@ fixloop: os.Stdout.WriteString(unified) } else { - // write + // write file totalFiles++ - // TODO(adonovan): abstract the I/O. - if err := os.WriteFile(file, final, 0644); err != nil { + if err := writeFile(file, final); err != nil { log.Println(err) - continue + continue // (causes ApplyFix to return an error) } filesUpdated++ } diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go index ce5beb2724..9a960bd293 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/callee.go @@ -725,8 +725,48 @@ func analyzeAssignment(info *types.Info, stack []ast.Node) (assignable, ifaceAss paramType := paramTypeAtIndex(sig, call, i) ifaceAssign := paramType == nil || types.IsInterface(paramType) affectsInference := false - if fn := typeutil.StaticCallee(info, call); fn != nil { - if sig2 := fn.Type().(*types.Signature); sig2.Recv() == nil { + switch callee := typeutil.Callee(info, call).(type) { + case *types.Builtin: + // Consider this litmus test: + // + // func f(x int64) any { return max(x) } + // func main() { fmt.Printf("%T", f(42)) } + // + // If we lose the implicit conversion from untyped int + // to int64, the type inferred for the max(x) call changes, + // resulting in a different dynamic behavior: it prints + // int, not int64. + // + // Inferred result type affected: + // new + // complex, real, imag + // min, max + // + // Dynamic behavior change: + // append -- dynamic type of append([]any(nil), x)[0] + // delete(m, x) -- dynamic key type where m is map[any]unit + // panic -- dynamic type of panic value + // + // Unaffected: + // recover + // make + // len, cap + // clear + // close + // copy + // print, println -- only uses underlying types (?) + // + // The dynamic type cases are all covered by + // the ifaceAssign logic. + switch callee.Name() { + case "new", "complex", "real", "imag", "min", "max": + affectsInference = true + } + + case *types.Func: + // Only standalone (non-method) functions have type + // parameters affected by the call arguments. + if sig2 := callee.Signature(); sig2.Recv() == nil { originParamType := paramTypeAtIndex(sig2, call, i) affectsInference = originParamType == nil || new(typeparams.Free).Has(originParamType) } diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 8641f2dca3..7e48798071 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -73,7 +73,7 @@ golang.org/x/text/internal/tag golang.org/x/text/language golang.org/x/text/transform golang.org/x/text/unicode/norm -# golang.org/x/tools v0.39.1-0.20251130212600-1ad6f3d02713 +# golang.org/x/tools v0.39.1-0.20251205000126-062ef7b6ced2 ## explicit; go 1.24.0 golang.org/x/tools/cmd/bisect golang.org/x/tools/cover