From 1bc54868d4fd03e3716e7ad9669d87c34c8aaa2d Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 20 Nov 2025 16:45:17 -0500 Subject: [PATCH] cmd/vendor: update to x/tools@68724af This causes the go1.26 vet printf analyzer to deduce printf wrappers via interface methods (#76368). For #76368 Change-Id: I80e6d3b21bdffb5d925a162af7c4b21b1357bb89 Reviewed-on: https://go-review.googlesource.com/c/go/+/722540 Auto-Submit: Alan Donovan Commit-Queue: Alan Donovan Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov LUCI-TryBot-Result: Go LUCI --- src/cmd/go.mod | 2 +- src/cmd/go.sum | 4 +- .../tools/go/analysis/passes/inline/inline.go | 149 ++-- .../go/analysis/passes/modernize/maps.go | 15 +- .../analysis/passes/modernize/stringscut.go | 2 +- .../x/tools/go/analysis/passes/printf/doc.go | 30 + .../tools/go/analysis/passes/printf/printf.go | 321 +++++--- .../x/tools/internal/astutil/util.go | 2 +- .../x/tools/internal/moreiters/iters.go | 8 + .../tools/internal/refactor/inline/inline.go | 54 +- .../x/tools/refactor/satisfy/find.go | 727 ++++++++++++++++++ src/cmd/vendor/modules.txt | 3 +- 12 files changed, 1115 insertions(+), 202 deletions(-) create mode 100644 src/cmd/vendor/golang.org/x/tools/refactor/satisfy/find.go diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 090b2c943f3..2f13ca0dc96 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -11,7 +11,7 @@ require ( golang.org/x/sys v0.38.0 golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 golang.org/x/term v0.34.0 - golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883 + golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 ) require ( diff --git a/src/cmd/go.sum b/src/cmd/go.sum index e4955f224b4..d75d9339fbf 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -22,7 +22,7 @@ golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883 h1:aeO0AW8d+a+5+hNQx9f4J5egD89zftrY2x42KGQjLzI= -golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 h1:BGuEUnbWU1H+VhF4Z52lwCvzRT8Q/Z7kJC3okSME58w= +golang.org/x/tools v0.39.1-0.20251120214200-68724afed209/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/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go index 1b3cb108c6d..c0b75202589 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go @@ -21,6 +21,7 @@ import ( "golang.org/x/tools/go/ast/inspector" "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/diff" "golang.org/x/tools/internal/moreiters" @@ -28,6 +29,7 @@ import ( "golang.org/x/tools/internal/refactor" "golang.org/x/tools/internal/refactor/inline" "golang.org/x/tools/internal/typesinternal" + "golang.org/x/tools/internal/typesinternal/typeindex" ) //go:embed doc.go @@ -43,20 +45,29 @@ var Analyzer = &analysis.Analyzer{ (*goFixInlineConstFact)(nil), (*goFixInlineAliasFact)(nil), }, - Requires: []*analysis.Analyzer{inspect.Analyzer}, + Requires: []*analysis.Analyzer{ + inspect.Analyzer, + typeindexanalyzer.Analyzer, + }, } -var allowBindingDecl bool +var ( + allowBindingDecl bool + lazyEdits bool +) func init() { Analyzer.Flags.BoolVar(&allowBindingDecl, "allow_binding_decl", false, "permit inlinings that require a 'var params = args' declaration") + Analyzer.Flags.BoolVar(&lazyEdits, "lazy_edits", false, + "compute edits lazily (only meaningful to gopls driver)") } // analyzer holds the state for this analysis. type analyzer struct { - pass *analysis.Pass - root inspector.Cursor + pass *analysis.Pass + root inspector.Cursor + index *typeindex.Index // memoization of repeated calls for same file. fileContent map[string][]byte // memoization of fact imports (nil => no fact) @@ -69,6 +80,7 @@ func run(pass *analysis.Pass) (any, error) { a := &analyzer{ pass: pass, root: pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root(), + index: pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index), fileContent: make(map[string][]byte), inlinableFuncs: make(map[*types.Func]*inline.Callee), inlinableConsts: make(map[*types.Const]*goFixInlineConstFact), @@ -170,63 +182,88 @@ func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) { return // don't inline a function from within its own test } - // Inline the call. - content, err := a.readFile(call) - if err != nil { - a.pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err) - return - } - curFile := astutil.EnclosingFile(cur) - caller := &inline.Caller{ - Fset: a.pass.Fset, - Types: a.pass.Pkg, - Info: a.pass.TypesInfo, - File: curFile, - Call: call, - Content: content, - } - res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard}) - if err != nil { - a.pass.Reportf(call.Lparen, "%v", err) - return + // Compute the edits. + // + // Ordinarily the analyzer reports a fix containing + // edits. However, the algorithm is somewhat expensive + // (unnecessarily so: see go.dev/issue/75773) so + // to reduce costs in gopls, we omit the edits, + // meaning that gopls must compute them on demand + // (based on the Diagnostic.Category) when they are + // requested via a code action. + // + // This does mean that the following categories of + // caller-dependent obstacles to inlining will be + // reported when the gopls user requests the fix, + // rather than by quietly suppressing the diagnostic: + // - shadowing problems + // - callee imports inaccessible "internal" packages + // - callee refers to nonexported symbols + // - callee uses too-new Go features + // - inlining call from a cgo file + var edits []analysis.TextEdit + if !lazyEdits { + // Inline the call. + content, err := a.readFile(call) + if err != nil { + a.pass.Reportf(call.Lparen, "invalid inlining candidate: cannot read source file: %v", err) + return + } + curFile := astutil.EnclosingFile(cur) + caller := &inline.Caller{ + Fset: a.pass.Fset, + Types: a.pass.Pkg, + Info: a.pass.TypesInfo, + File: curFile, + Call: call, + Content: content, + CountUses: func(pkgname *types.PkgName) int { + return moreiters.Len(a.index.Uses(pkgname)) + }, + } + res, err := inline.Inline(caller, callee, &inline.Options{Logf: discard}) + if err != nil { + a.pass.Reportf(call.Lparen, "%v", err) + return + } + + if res.Literalized { + // Users are not fond of inlinings that literalize + // f(x) to func() { ... }(), so avoid them. + // + // (Unfortunately the inliner is very timid, + // and often literalizes when it cannot prove that + // reducing the call is safe; the user of this tool + // has no indication of what the problem is.) + return + } + if res.BindingDecl && !allowBindingDecl { + // When applying fix en masse, users are similarly + // unenthusiastic about inlinings that cannot + // entirely eliminate the parameters and + // insert a 'var params = args' declaration. + // The flag allows them to decline such fixes. + return + } + got := res.Content + + for _, edit := range diff.Bytes(content, got) { + edits = append(edits, analysis.TextEdit{ + Pos: curFile.FileStart + token.Pos(edit.Start), + End: curFile.FileStart + token.Pos(edit.End), + NewText: []byte(edit.New), + }) + } } - if res.Literalized { - // Users are not fond of inlinings that literalize - // f(x) to func() { ... }(), so avoid them. - // - // (Unfortunately the inliner is very timid, - // and often literalizes when it cannot prove that - // reducing the call is safe; the user of this tool - // has no indication of what the problem is.) - return - } - if res.BindingDecl && !allowBindingDecl { - // When applying fix en masse, users are similarly - // unenthusiastic about inlinings that cannot - // entirely eliminate the parameters and - // insert a 'var params = args' declaration. - // The flag allows them to decline such fixes. - return - } - got := res.Content - - // Suggest the "fix". - var textEdits []analysis.TextEdit - for _, edit := range diff.Bytes(content, got) { - textEdits = append(textEdits, analysis.TextEdit{ - Pos: curFile.FileStart + token.Pos(edit.Start), - End: curFile.FileStart + token.Pos(edit.End), - NewText: []byte(edit.New), - }) - } a.pass.Report(analysis.Diagnostic{ - Pos: call.Pos(), - End: call.End(), - Message: fmt.Sprintf("Call of %v should be inlined", callee), + Pos: call.Pos(), + End: call.End(), + Message: fmt.Sprintf("Call of %v should be inlined", callee), + Category: "inline_call", // keep consistent with gopls/internal/golang.fixInlineCall SuggestedFixes: []analysis.SuggestedFix{{ Message: fmt.Sprintf("Inline call of %v", callee), - TextEdits: textEdits, + TextEdits: edits, // within gopls, this is nil => compute fix's edits lazily }}, }) } diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go index 2352c8b6088..f97541d4b34 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go @@ -233,13 +233,16 @@ func mapsloop(pass *analysis.Pass) (any, error) { assign := rng.Body.List[0].(*ast.AssignStmt) if index, ok := assign.Lhs[0].(*ast.IndexExpr); ok && astutil.EqualSyntax(rng.Key, index.Index) && - astutil.EqualSyntax(rng.Value, assign.Rhs[0]) && - is[*types.Map](typeparams.CoreType(info.TypeOf(index.X))) && - types.Identical(info.TypeOf(index), info.TypeOf(rng.Value)) { // m[k], v + astutil.EqualSyntax(rng.Value, assign.Rhs[0]) { + if tmap, ok := typeparams.CoreType(info.TypeOf(index.X)).(*types.Map); ok && + types.Identical(info.TypeOf(index), info.TypeOf(rng.Value)) && // m[k], v + types.Identical(tmap.Key(), info.TypeOf(rng.Key)) { - // Have: for k, v := range x { m[k] = v } - // where there is no implicit conversion. - check(file, curRange, assign, index.X, rng.X) + // Have: for k, v := range x { m[k] = v } + // where there is no implicit conversion + // of either key or value. + check(file, curRange, assign, index.X, rng.X) + } } } } 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 521c264c51e..bc4ad677cd0 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 @@ -489,7 +489,7 @@ func isNegativeConst(info *types.Info, expr ast.Expr) bool { return false } -// isNoneNegativeConst returns true if the expr is a const int with value >= zero. +// 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 { diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go index f04e4414341..a09bfd1c6c8 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go @@ -92,6 +92,36 @@ // } // logf("%s", 123) // logf format %s has arg 123 of wrong type int // +// Interface methods may also be analyzed as printf wrappers, if +// within the interface's package there is an assignment from a +// implementation type whose corresponding method is a printf wrapper. +// +// For example, the var declaration below causes a *myLoggerImpl value +// to be assigned to a Logger variable: +// +// type Logger interface { +// Logf(format string, args ...any) +// } +// +// type myLoggerImpl struct{ ... } +// +// var _ Logger = (*myLoggerImpl)(nil) +// +// func (*myLoggerImpl) Logf(format string, args ...any) { +// println(fmt.Sprintf(format, args...)) +// } +// +// Since myLoggerImpl's Logf method is a printf wrapper, this +// establishes that Logger.Logf is a printf wrapper too, causing +// dynamic calls through the interface to be checked: +// +// func f(log Logger) { +// log.Logf("%s", 123) // Logger.Logf format %s has arg 123 of wrong type int +// } +// +// This feature applies only to interface methods declared in files +// using at least Go 1.26. +// // # Specifying printf wrappers by flag // // The -funcs flag specifies a comma-separated list of names of diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go index 1eac2589bfa..fd9fe164723 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go @@ -27,6 +27,7 @@ import ( "golang.org/x/tools/internal/typeparams" "golang.org/x/tools/internal/typesinternal" "golang.org/x/tools/internal/versions" + "golang.org/x/tools/refactor/satisfy" ) func init() { @@ -65,7 +66,7 @@ func (kind Kind) String() string { case KindErrorf: return "errorf" } - return "" + return "(none)" } // Result is the printf analyzer's result type. Clients may query the result @@ -138,7 +139,7 @@ type wrapper struct { type printfCaller struct { w *wrapper - call *ast.CallExpr + call *ast.CallExpr // forwarding call (nil for implicit interface method -> impl calls) } // formatArgsParams returns the "format string" and "args ...any" @@ -183,60 +184,12 @@ func findPrintLike(pass *analysis.Pass, res *Result) { wrappers []*wrapper byObj = make(map[types.Object]*wrapper) ) - for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) { - var ( - curBody inspector.Cursor // for *ast.BlockStmt - sig *types.Signature - obj types.Object - ) - switch f := cur.Node().(type) { - case *ast.FuncDecl: - // named function or method: - // - // func wrapf(format string, args ...any) {...} - if f.Body != nil { - curBody = cur.ChildAt(edge.FuncDecl_Body, -1) - obj = info.Defs[f.Name] - sig = obj.Type().(*types.Signature) - } + for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil), (*ast.InterfaceType)(nil)) { - case *ast.FuncLit: - // anonymous function directly assigned to a variable: - // - // var wrapf = func(format string, args ...any) {...} - // wrapf := func(format string, args ...any) {...} - // wrapf = func(format string, args ...any) {...} - // - // The LHS may also be a struct field x.wrapf or - // an imported var pkg.Wrapf. - // - sig = info.TypeOf(f).(*types.Signature) - curBody = cur.ChildAt(edge.FuncLit_Body, -1) - var lhs ast.Expr - switch ek, idx := cur.ParentEdge(); ek { - case edge.ValueSpec_Values: - curName := cur.Parent().ChildAt(edge.ValueSpec_Names, idx) - lhs = curName.Node().(*ast.Ident) - case edge.AssignStmt_Rhs: - curLhs := cur.Parent().ChildAt(edge.AssignStmt_Lhs, idx) - lhs = curLhs.Node().(ast.Expr) - } - - switch lhs := lhs.(type) { - case *ast.Ident: - // variable: wrapf = func(...) - obj = info.ObjectOf(lhs).(*types.Var) - case *ast.SelectorExpr: - if sel, ok := info.Selections[lhs]; ok { - // struct field: x.wrapf = func(...) - obj = sel.Obj().(*types.Var) - } else { - // imported var: pkg.Wrapf = func(...) - obj = info.Uses[lhs.Sel].(*types.Var) - } - } - } - if obj != nil { + // addWrapper records that a func (or var representing + // a FuncLit) is a potential print{,f} wrapper. + // curBody is its *ast.BlockStmt, if any. + addWrapper := func(obj types.Object, sig *types.Signature, curBody inspector.Cursor) *wrapper { format, args := formatArgsParams(sig) if args != nil { // obj (the symbol for a function/method, or variable @@ -254,17 +207,124 @@ func findPrintLike(pass *analysis.Pass, res *Result) { } byObj[w.obj] = w wrappers = append(wrappers, w) + return w + } + return nil + } + + switch f := cur.Node().(type) { + case *ast.FuncDecl: + // named function or method: + // + // func wrapf(format string, args ...any) {...} + if f.Body != nil { + fn := info.Defs[f.Name].(*types.Func) + addWrapper(fn, fn.Signature(), cur.ChildAt(edge.FuncDecl_Body, -1)) + } + + case *ast.FuncLit: + // anonymous function directly assigned to a variable: + // + // var wrapf = func(format string, args ...any) {...} + // wrapf := func(format string, args ...any) {...} + // wrapf = func(format string, args ...any) {...} + // + // The LHS may also be a struct field x.wrapf or + // an imported var pkg.Wrapf. + // + var lhs ast.Expr + switch ek, idx := cur.ParentEdge(); ek { + case edge.ValueSpec_Values: + curName := cur.Parent().ChildAt(edge.ValueSpec_Names, idx) + lhs = curName.Node().(*ast.Ident) + case edge.AssignStmt_Rhs: + curLhs := cur.Parent().ChildAt(edge.AssignStmt_Lhs, idx) + lhs = curLhs.Node().(ast.Expr) + } + + var v *types.Var + switch lhs := lhs.(type) { + case *ast.Ident: + // variable: wrapf = func(...) + v = info.ObjectOf(lhs).(*types.Var) + case *ast.SelectorExpr: + if sel, ok := info.Selections[lhs]; ok { + // struct field: x.wrapf = func(...) + v = sel.Obj().(*types.Var) + } else { + // imported var: pkg.Wrapf = func(...) + v = info.Uses[lhs.Sel].(*types.Var) + } + } + if v != nil { + sig := info.TypeOf(f).(*types.Signature) + curBody := cur.ChildAt(edge.FuncLit_Body, -1) + addWrapper(v, sig, curBody) + } + + case *ast.InterfaceType: + // Induction through interface methods is gated as + // if it were a go1.26 language feature, to avoid + // surprises when go test's vet suite gets stricter. + if analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(cur), versions.Go1_26) { + for imeth := range info.TypeOf(f).(*types.Interface).Methods() { + addWrapper(imeth, imeth.Signature(), inspector.Cursor{}) + } } } } + // impls maps abstract methods to implementations. + // + // Interface methods are modelled as if they have a body + // that calls each implementing method. + // + // In the code below, impls maps Logger.Logf to + // [myLogger.Logf], and if myLogger.Logf is discovered to be + // printf-like, then so will be Logger.Logf. + // + // type Logger interface { + // Logf(format string, args ...any) + // } + // type myLogger struct{ ... } + // func (myLogger) Logf(format string, args ...any) {...} + // var _ Logger = myLogger{} + impls := methodImplementations(pass) + // Pass 2: scan the body of each wrapper function // for calls to other printf-like functions. - // - // Also, reject tricky cases where the parameters - // are potentially mutated by AssignStmt or UnaryExpr. - // TODO: Relax these checks; issue 26555. for _, w := range wrappers { + + // doCall records a call from one wrapper to another. + doCall := func(callee types.Object, call *ast.CallExpr) { + // Call from one wrapper candidate to another? + // Record the edge so that if callee is found to be + // a true wrapper, w will be too. + if w2, ok := byObj[callee]; ok { + w2.callers = append(w2.callers, printfCaller{w, call}) + } + + // Is the candidate a true wrapper, because it calls + // a known print{,f}-like function from the allowlist + // or an imported fact, or another wrapper found + // to be a true wrapper? + // If so, convert all w's callers to kind. + kind := callKind(pass, callee, res) + if kind != KindNone { + checkForward(pass, w, call, kind, res) + } + } + + // An interface method has no body, but acts + // like an implicit call to each implementing method. + if w.curBody.Inspector() == nil { + for impl := range impls[w.obj.(*types.Func)] { + doCall(impl, nil) + } + continue // (no body) + } + + // Process all calls in the wrapper function's body. scan: for cur := range w.curBody.Preorder( (*ast.AssignStmt)(nil), @@ -272,6 +332,12 @@ func findPrintLike(pass *analysis.Pass, res *Result) { (*ast.CallExpr)(nil), ) { switch n := cur.Node().(type) { + + // Reject tricky cases where the parameters + // are potentially mutated by AssignStmt or UnaryExpr. + // (This logic checks for mutation only before the call.) + // TODO: Relax these checks; issue 26555. + case *ast.AssignStmt: // If the wrapper updates format or args // it is not a simple wrapper. @@ -294,23 +360,7 @@ func findPrintLike(pass *analysis.Pass, res *Result) { case *ast.CallExpr: if len(n.Args) > 0 && match(info, n.Args[len(n.Args)-1], w.args) { if callee := typeutil.Callee(pass.TypesInfo, n); callee != nil { - - // Call from one wrapper candidate to another? - // Record the edge so that if callee is found to be - // a true wrapper, w will be too. - if w2, ok := byObj[callee]; ok { - w2.callers = append(w2.callers, printfCaller{w, n}) - } - - // Is the candidate a true wrapper, because it calls - // a known print{,f}-like function from the allowlist - // or an imported fact, or another wrapper found - // to be a true wrapper? - // If so, convert all w's callers to kind. - kind := callKind(pass, callee, res) - if kind != KindNone { - checkForward(pass, w, n, kind, res) - } + doCall(callee, n) } } } @@ -318,41 +368,90 @@ func findPrintLike(pass *analysis.Pass, res *Result) { } } +// methodImplementations returns the mapping from interface methods +// declared in this package to their corresponding implementing +// methods (which may also be interface methods), according to the set +// of assignments to interface types that appear within this package. +func methodImplementations(pass *analysis.Pass) map[*types.Func]map[*types.Func]bool { + impls := make(map[*types.Func]map[*types.Func]bool) + + // To find interface/implementation relations, + // we use the 'satisfy' pass, but proposal #70638 + // provides a better way. + // + // This pass over the syntax could be factored out as + // a separate analysis pass if it is needed by other + // analyzers. + var f satisfy.Finder + f.Find(pass.TypesInfo, pass.Files) + for assign := range f.Result { + // Have: LHS = RHS, where LHS is an interface type. + for imeth := range assign.LHS.Underlying().(*types.Interface).Methods() { + // Limit to interface methods of current package. + if imeth.Pkg() != pass.Pkg { + continue + } + + if _, args := formatArgsParams(imeth.Signature()); args == nil { + continue // not print{,f}-like + } + + // Add implementing method to the set. + impl, _, _ := types.LookupFieldOrMethod(assign.RHS, false, pass.Pkg, imeth.Name()) // can't fail + set, ok := impls[imeth] + if !ok { + set = make(map[*types.Func]bool) + impls[imeth] = set + } + set[impl.(*types.Func)] = true + } + } + return impls +} + func match(info *types.Info, arg ast.Expr, param *types.Var) bool { id, ok := arg.(*ast.Ident) return ok && info.ObjectOf(id) == param } -// checkForward checks that a forwarding wrapper is forwarding correctly. -// It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...). +// checkForward checks whether a forwarding wrapper is forwarding correctly. +// If so, it propagates changes in wrapper kind information backwards +// through through the wrapper.callers graph of forwarding calls. +// +// If not, it reports a diagnostic that the user wrote +// fmt.Printf(format, args) instead of fmt.Printf(format, args...). func checkForward(pass *analysis.Pass, w *wrapper, call *ast.CallExpr, kind Kind, res *Result) { - matched := kind == KindPrint || - kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) - if !matched { - return - } + // Check correct call forwarding. + // (Interface methods forward correctly by construction.) + if call != nil { + matched := kind == KindPrint || + kind != KindNone && len(call.Args) >= 2 && match(pass.TypesInfo, call.Args[len(call.Args)-2], w.format) + if !matched { + return + } - if !call.Ellipsis.IsValid() { - typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature) - if !ok { + if !call.Ellipsis.IsValid() { + typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature) + if !ok { + return + } + if len(call.Args) > typ.Params().Len() { + // If we're passing more arguments than what the + // print/printf function can take, adding an ellipsis + // would break the program. For example: + // + // func foo(arg1 string, arg2 ...interface{}) { + // fmt.Printf("%s %v", arg1, arg2) + // } + return + } + desc := "printf" + if kind == KindPrint { + desc = "print" + } + pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc) return } - if len(call.Args) > typ.Params().Len() { - // If we're passing more arguments than what the - // print/printf function can take, adding an ellipsis - // would break the program. For example: - // - // func foo(arg1 string, arg2 ...interface{}) { - // fmt.Printf("%s %v", arg1, arg2) - // } - return - } - desc := "printf" - if kind == KindPrint { - desc = "print" - } - pass.ReportRangef(call, "missing ... in args forwarded to %s-like function", desc) - return } // If the candidate's print{,f} status becomes known, @@ -444,16 +543,14 @@ var isPrint = stringSet{ "(*testing.common).Logf": true, "(*testing.common).Skip": true, "(*testing.common).Skipf": true, - // *testing.T and B are detected by induction, but testing.TB is - // an interface and the inference can't follow dynamic calls. - "(testing.TB).Error": true, - "(testing.TB).Errorf": true, - "(testing.TB).Fatal": true, - "(testing.TB).Fatalf": true, - "(testing.TB).Log": true, - "(testing.TB).Logf": true, - "(testing.TB).Skip": true, - "(testing.TB).Skipf": true, + "(testing.TB).Error": true, + "(testing.TB).Errorf": true, + "(testing.TB).Fatal": true, + "(testing.TB).Fatalf": true, + "(testing.TB).Log": true, + "(testing.TB).Logf": true, + "(testing.TB).Skip": true, + "(testing.TB).Skipf": true, } // formatStringIndex returns the index of the format string (the last diff --git a/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go b/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go index 7a02fca21e2..6986a51875b 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go @@ -64,7 +64,7 @@ func NodeContains(n ast.Node, rng Range) bool { return NodeRange(n).Contains(rng) } -// NodeContainPos reports whether the Pos/End range of node n encloses +// NodeContainsPos reports whether the Pos/End range of node n encloses // the given pos. // // Like [NodeRange], it treats the range of an [ast.File] as the diff --git a/src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go b/src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go index 69c76ccb9b6..9e4aaf94855 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go @@ -45,3 +45,11 @@ func Any[T any](seq iter.Seq[T], pred func(T) bool) bool { } return false } + +// Len returns the number of elements in the sequence (by iterating). +func Len[T any](seq iter.Seq[T]) (n int) { + for range seq { + n++ + } + return +} diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go index 03ef0714e00..af1252cee86 100644 --- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go +++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go @@ -40,7 +40,11 @@ type Caller struct { Info *types.Info File *ast.File Call *ast.CallExpr - Content []byte // source of file containing + Content []byte // source of file containing (TODO(adonovan): see comment at Result.Content) + + // CountUses is an optional optimized computation of + // the number of times pkgname appears in Info.Uses. + CountUses func(pkgname *types.PkgName) int path []ast.Node // path from call to root of file syntax tree enclosingFunc *ast.FuncDecl // top-level function/method enclosing the call, if any @@ -57,6 +61,18 @@ type Options struct { // Result holds the result of code transformation. type Result struct { + // TODO(adonovan): the only textual results that should be + // needed are (1) an edit in the vicinity of the call (either + // to the CallExpr or one of its ancestors), and optionally + // (2) an edit to the import declaration. + // Change the inliner API to return a list of edits, + // and not to accept a Caller.Content, as it is only + // temptation to use such algorithmically expensive + // operations as reformatting the entire file, which is + // a significant source of non-linear dynamic behavior; + // see https://go.dev/issue/75773. + // This will require a sequence of changes to the tests + // and the inliner algorithm itself. Content []byte // formatted, transformed content of caller file Literalized bool // chosen strategy replaced callee() with func(){...}() BindingDecl bool // transformation added "var params = args" declaration @@ -432,27 +448,19 @@ func newImportState(logf func(string, ...any), caller *Caller, callee *gobCallee importMap: make(map[string][]string), } - // Build an index of used-once PkgNames. - type pkgNameUse struct { - count int - id *ast.Ident // an arbitrary use - } - pkgNameUses := make(map[*types.PkgName]pkgNameUse) - for id, obj := range caller.Info.Uses { - if pkgname, ok := obj.(*types.PkgName); ok { - u := pkgNameUses[pkgname] - u.id = id - u.count++ - pkgNameUses[pkgname] = u + // Provide an inefficient default implementation of CountUses. + // (Ideally clients amortize this for the entire package.) + countUses := caller.CountUses + if countUses == nil { + uses := make(map[*types.PkgName]int) + for _, obj := range caller.Info.Uses { + if pkgname, ok := obj.(*types.PkgName); ok { + uses[pkgname]++ + } } - } - // soleUse returns the ident that refers to pkgname, if there is exactly one. - soleUse := func(pkgname *types.PkgName) *ast.Ident { - u := pkgNameUses[pkgname] - if u.count == 1 { - return u.id + countUses = func(pkgname *types.PkgName) int { + return uses[pkgname] } - return nil } for _, imp := range caller.File.Imports { @@ -472,8 +480,10 @@ func newImportState(logf func(string, ...any), caller *Caller, callee *gobCallee // If that is the case, proactively check if any of the callee FreeObjs // need this import. Doing so eagerly simplifies the resulting logic. needed := true - sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr) - if ok && soleUse(pkgName) == sel.X { + if sel, ok := ast.Unparen(caller.Call.Fun).(*ast.SelectorExpr); ok && + is[*ast.Ident](sel.X) && + caller.Info.Uses[sel.X.(*ast.Ident)] == pkgName && + countUses(pkgName) == 1 { needed = false // no longer needed by caller // Check to see if any of the inlined free objects need this package. for _, obj := range callee.FreeObjs { diff --git a/src/cmd/vendor/golang.org/x/tools/refactor/satisfy/find.go b/src/cmd/vendor/golang.org/x/tools/refactor/satisfy/find.go new file mode 100644 index 00000000000..6d23aa690f0 --- /dev/null +++ b/src/cmd/vendor/golang.org/x/tools/refactor/satisfy/find.go @@ -0,0 +1,727 @@ +// Copyright 2014 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 satisfy inspects the type-checked ASTs of Go packages and +// reports the set of discovered type constraints of the form (lhs, rhs +// Type) where lhs is a non-trivial interface, rhs satisfies this +// interface, and this fact is necessary for the package to be +// well-typed. +// +// THIS PACKAGE IS EXPERIMENTAL AND MAY CHANGE AT ANY TIME. +// +// It is provided only for the gopls tool. It requires well-typed inputs. +package satisfy // import "golang.org/x/tools/refactor/satisfy" + +// NOTES: +// +// We don't care about numeric conversions, so we don't descend into +// types or constant expressions. This is unsound because +// constant expressions can contain arbitrary statements, e.g. +// const x = len([1]func(){func() { +// ... +// }}) +// +// Assignability conversions are possible in the following places: +// - in assignments y = x, y := x, var y = x. +// - from call argument types to formal parameter types +// - in append and delete calls +// - from return operands to result parameter types +// - in composite literal T{k:v}, from k and v to T's field/element/key type +// - in map[key] from key to the map's key type +// - in comparisons x==y and switch x { case y: }. +// - in explicit conversions T(x) +// - in sends ch <- x, from x to the channel element type +// - in type assertions x.(T) and switch x.(type) { case T: } +// +// The results of this pass provide information equivalent to the +// ssa.MakeInterface and ssa.ChangeInterface instructions. + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/typeparams" +) + +// A Constraint records the fact that the RHS type does and must +// satisfy the LHS type, which is an interface. +// The names are suggestive of an assignment statement LHS = RHS. +// +// The constraint is implicitly universally quantified over any type +// parameters appearing within the two types. +type Constraint struct { + LHS, RHS types.Type +} + +// A Finder inspects the type-checked ASTs of Go packages and +// accumulates the set of type constraints (x, y) such that x is +// assignable to y, y is an interface, and both x and y have methods. +// +// In other words, it returns the subset of the "implements" relation +// that is checked during compilation of a package. Refactoring tools +// will need to preserve at least this part of the relation to ensure +// continued compilation. +type Finder struct { + Result map[Constraint]bool + msetcache typeutil.MethodSetCache + + // per-Find state + info *types.Info + sig *types.Signature +} + +// Find inspects a single package, populating Result with its pairs of +// constrained types. +// +// The result is non-canonical and thus may contain duplicates (but this +// tends to preserves names of interface types better). +// +// The package must be free of type errors, and +// info.{Defs,Uses,Selections,Types} must have been populated by the +// type-checker. +func (f *Finder) Find(info *types.Info, files []*ast.File) { + if info.Defs == nil || info.Uses == nil || info.Selections == nil || info.Types == nil { + panic("Finder.Find: one of info.{Defs,Uses,Selections.Types} is not populated") + } + if f.Result == nil { + f.Result = make(map[Constraint]bool) + } + + f.info = info + for _, file := range files { + for _, d := range file.Decls { + switch d := d.(type) { + case *ast.GenDecl: + if d.Tok == token.VAR { // ignore consts + for _, spec := range d.Specs { + f.valueSpec(spec.(*ast.ValueSpec)) + } + } + + case *ast.FuncDecl: + if d.Body != nil { + f.sig = f.info.Defs[d.Name].Type().(*types.Signature) + f.stmt(d.Body) + f.sig = nil + } + } + } + } + f.info = nil +} + +var ( + tInvalid = types.Typ[types.Invalid] + tUntypedBool = types.Typ[types.UntypedBool] + tUntypedNil = types.Typ[types.UntypedNil] +) + +// exprN visits an expression in a multi-value context. +func (f *Finder) exprN(e ast.Expr) types.Type { + typ := f.info.Types[e].Type.(*types.Tuple) + switch e := e.(type) { + case *ast.ParenExpr: + return f.exprN(e.X) + + case *ast.CallExpr: + // x, err := f(args) + sig := typeparams.CoreType(f.expr(e.Fun)).(*types.Signature) + f.call(sig, e.Args) + + case *ast.IndexExpr: + // y, ok := x[i] + x := f.expr(e.X) + f.assign(f.expr(e.Index), typeparams.CoreType(x).(*types.Map).Key()) + + case *ast.TypeAssertExpr: + // y, ok := x.(T) + f.typeAssert(f.expr(e.X), typ.At(0).Type()) + + case *ast.UnaryExpr: // must be receive <- + // y, ok := <-x + f.expr(e.X) + + default: + panic(e) + } + return typ +} + +func (f *Finder) call(sig *types.Signature, args []ast.Expr) { + if len(args) == 0 { + return + } + + // Ellipsis call? e.g. f(x, y, z...) + if _, ok := args[len(args)-1].(*ast.Ellipsis); ok { + for i, arg := range args { + // The final arg is a slice, and so is the final param. + f.assign(sig.Params().At(i).Type(), f.expr(arg)) + } + return + } + + var argtypes []types.Type + + // Gather the effective actual parameter types. + if tuple, ok := f.info.Types[args[0]].Type.(*types.Tuple); ok { + // f(g()) call where g has multiple results? + f.expr(args[0]) + // unpack the tuple + for v := range tuple.Variables() { + argtypes = append(argtypes, v.Type()) + } + } else { + for _, arg := range args { + argtypes = append(argtypes, f.expr(arg)) + } + } + + // Assign the actuals to the formals. + if !sig.Variadic() { + for i, argtype := range argtypes { + f.assign(sig.Params().At(i).Type(), argtype) + } + } else { + // The first n-1 parameters are assigned normally. + nnormals := sig.Params().Len() - 1 + for i, argtype := range argtypes[:nnormals] { + f.assign(sig.Params().At(i).Type(), argtype) + } + // Remaining args are assigned to elements of varargs slice. + tElem := sig.Params().At(nnormals).Type().(*types.Slice).Elem() + for i := nnormals; i < len(argtypes); i++ { + f.assign(tElem, argtypes[i]) + } + } +} + +// builtin visits the arguments of a builtin type with signature sig. +func (f *Finder) builtin(obj *types.Builtin, sig *types.Signature, args []ast.Expr) { + switch obj.Name() { + case "make", "new": + for i, arg := range args { + if i == 0 && f.info.Types[arg].IsType() { + continue // skip the type operand + } + f.expr(arg) + } + + case "append": + s := f.expr(args[0]) + if _, ok := args[len(args)-1].(*ast.Ellipsis); ok && len(args) == 2 { + // append(x, y...) including append([]byte, "foo"...) + f.expr(args[1]) + } else { + // append(x, y, z) + tElem := typeparams.CoreType(s).(*types.Slice).Elem() + for _, arg := range args[1:] { + f.assign(tElem, f.expr(arg)) + } + } + + case "delete": + m := f.expr(args[0]) + k := f.expr(args[1]) + f.assign(typeparams.CoreType(m).(*types.Map).Key(), k) + + default: + // ordinary call + f.call(sig, args) + } +} + +func (f *Finder) extract(tuple types.Type, i int) types.Type { + if tuple, ok := tuple.(*types.Tuple); ok && i < tuple.Len() { + return tuple.At(i).Type() + } + return tInvalid +} + +func (f *Finder) valueSpec(spec *ast.ValueSpec) { + var T types.Type + if spec.Type != nil { + T = f.info.Types[spec.Type].Type + } + switch len(spec.Values) { + case len(spec.Names): // e.g. var x, y = f(), g() + for _, value := range spec.Values { + v := f.expr(value) + if T != nil { + f.assign(T, v) + } + } + + case 1: // e.g. var x, y = f() + tuple := f.exprN(spec.Values[0]) + for i := range spec.Names { + if T != nil { + f.assign(T, f.extract(tuple, i)) + } + } + } +} + +// assign records pairs of distinct types that are related by +// assignability, where the left-hand side is an interface and both +// sides have methods. +// +// It should be called for all assignability checks, type assertions, +// explicit conversions and comparisons between two types, unless the +// types are uninteresting (e.g. lhs is a concrete type, or the empty +// interface; rhs has no methods). +func (f *Finder) assign(lhs, rhs types.Type) { + if types.Identical(lhs, rhs) { + return + } + if !types.IsInterface(lhs) { + return + } + + if f.msetcache.MethodSet(lhs).Len() == 0 { + return + } + if f.msetcache.MethodSet(rhs).Len() == 0 { + return + } + // record the pair + f.Result[Constraint{lhs, rhs}] = true +} + +// typeAssert must be called for each type assertion x.(T) where x has +// interface type I. +func (f *Finder) typeAssert(I, T types.Type) { + // Type assertions are slightly subtle, because they are allowed + // to be "impossible", e.g. + // + // var x interface{f()} + // _ = x.(interface{f()int}) // legal + // + // (In hindsight, the language spec should probably not have + // allowed this, but it's too late to fix now.) + // + // This means that a type assert from I to T isn't exactly a + // constraint that T is assignable to I, but for a refactoring + // tool it is a conditional constraint that, if T is assignable + // to I before a refactoring, it should remain so after. + + if types.AssignableTo(T, I) { + f.assign(I, T) + } +} + +// compare must be called for each comparison x==y. +func (f *Finder) compare(x, y types.Type) { + if types.AssignableTo(x, y) { + f.assign(y, x) + } else if types.AssignableTo(y, x) { + f.assign(x, y) + } +} + +// expr visits a true expression (not a type or defining ident) +// and returns its type. +func (f *Finder) expr(e ast.Expr) types.Type { + tv := f.info.Types[e] + if tv.Value != nil { + return tv.Type // prune the descent for constants + } + + // tv.Type may be nil for an ast.Ident. + + switch e := e.(type) { + case *ast.BadExpr, *ast.BasicLit: + // no-op + + case *ast.Ident: + // (referring idents only) + if obj, ok := f.info.Uses[e]; ok { + return obj.Type() + } + if e.Name == "_" { // e.g. "for _ = range x" + return tInvalid + } + panic("undefined ident: " + e.Name) + + case *ast.Ellipsis: + if e.Elt != nil { + f.expr(e.Elt) + } + + case *ast.FuncLit: + saved := f.sig + f.sig = tv.Type.(*types.Signature) + f.stmt(e.Body) + f.sig = saved + + case *ast.CompositeLit: + switch T := typeparams.CoreType(typeparams.Deref(tv.Type)).(type) { + case *types.Struct: + for i, elem := range e.Elts { + if kv, ok := elem.(*ast.KeyValueExpr); ok { + f.assign(f.info.Uses[kv.Key.(*ast.Ident)].Type(), f.expr(kv.Value)) + } else { + f.assign(T.Field(i).Type(), f.expr(elem)) + } + } + + case *types.Map: + for _, elem := range e.Elts { + elem := elem.(*ast.KeyValueExpr) + f.assign(T.Key(), f.expr(elem.Key)) + f.assign(T.Elem(), f.expr(elem.Value)) + } + + case *types.Array, *types.Slice: + tElem := T.(interface { + Elem() types.Type + }).Elem() + for _, elem := range e.Elts { + if kv, ok := elem.(*ast.KeyValueExpr); ok { + // ignore the key + f.assign(tElem, f.expr(kv.Value)) + } else { + f.assign(tElem, f.expr(elem)) + } + } + + default: + panic(fmt.Sprintf("unexpected composite literal type %T: %v", tv.Type, tv.Type.String())) + } + + case *ast.ParenExpr: + f.expr(e.X) + + case *ast.SelectorExpr: + if _, ok := f.info.Selections[e]; ok { + f.expr(e.X) // selection + } else { + return f.info.Uses[e.Sel].Type() // qualified identifier + } + + case *ast.IndexExpr: + if instance(f.info, e.X) { + // f[T] or C[T] -- generic instantiation + } else { + // x[i] or m[k] -- index or lookup operation + x := f.expr(e.X) + i := f.expr(e.Index) + if ux, ok := typeparams.CoreType(x).(*types.Map); ok { + f.assign(ux.Key(), i) + } + } + + case *ast.IndexListExpr: + // f[X, Y] -- generic instantiation + + case *ast.SliceExpr: + f.expr(e.X) + if e.Low != nil { + f.expr(e.Low) + } + if e.High != nil { + f.expr(e.High) + } + if e.Max != nil { + f.expr(e.Max) + } + + case *ast.TypeAssertExpr: + x := f.expr(e.X) + f.typeAssert(x, f.info.Types[e.Type].Type) + + case *ast.CallExpr: + if tvFun := f.info.Types[e.Fun]; tvFun.IsType() { + // conversion + arg0 := f.expr(e.Args[0]) + f.assign(tvFun.Type, arg0) + } else { + // function call + + // unsafe call. Treat calls to functions in unsafe like ordinary calls, + // except that their signature cannot be determined by their func obj. + // Without this special handling, f.expr(e.Fun) would fail below. + if s, ok := ast.Unparen(e.Fun).(*ast.SelectorExpr); ok { + if obj, ok := f.info.Uses[s.Sel].(*types.Builtin); ok && obj.Pkg().Path() == "unsafe" { + sig := f.info.Types[e.Fun].Type.(*types.Signature) + f.call(sig, e.Args) + return tv.Type + } + } + + // builtin call + if id, ok := ast.Unparen(e.Fun).(*ast.Ident); ok { + if obj, ok := f.info.Uses[id].(*types.Builtin); ok { + sig := f.info.Types[id].Type.(*types.Signature) + f.builtin(obj, sig, e.Args) + return tv.Type + } + } + + // ordinary call + f.call(typeparams.CoreType(f.expr(e.Fun)).(*types.Signature), e.Args) + } + + case *ast.StarExpr: + f.expr(e.X) + + case *ast.UnaryExpr: + f.expr(e.X) + + case *ast.BinaryExpr: + x := f.expr(e.X) + y := f.expr(e.Y) + if e.Op == token.EQL || e.Op == token.NEQ { + f.compare(x, y) + } + + case *ast.KeyValueExpr: + f.expr(e.Key) + f.expr(e.Value) + + case *ast.ArrayType, + *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.ChanType: + panic(e) + } + + if tv.Type == nil { + panic(fmt.Sprintf("no type for %T", e)) + } + + return tv.Type +} + +func (f *Finder) stmt(s ast.Stmt) { + switch s := s.(type) { + case *ast.BadStmt, + *ast.EmptyStmt, + *ast.BranchStmt: + // no-op + + case *ast.DeclStmt: + d := s.Decl.(*ast.GenDecl) + if d.Tok == token.VAR { // ignore consts + for _, spec := range d.Specs { + f.valueSpec(spec.(*ast.ValueSpec)) + } + } + + case *ast.LabeledStmt: + f.stmt(s.Stmt) + + case *ast.ExprStmt: + f.expr(s.X) + + case *ast.SendStmt: + ch := f.expr(s.Chan) + val := f.expr(s.Value) + f.assign(typeparams.CoreType(ch).(*types.Chan).Elem(), val) + + case *ast.IncDecStmt: + f.expr(s.X) + + case *ast.AssignStmt: + switch s.Tok { + case token.ASSIGN, token.DEFINE: + // y := x or y = x + var rhsTuple types.Type + if len(s.Lhs) != len(s.Rhs) { + rhsTuple = f.exprN(s.Rhs[0]) + } + for i := range s.Lhs { + var lhs, rhs types.Type + if rhsTuple == nil { + rhs = f.expr(s.Rhs[i]) // 1:1 assignment + } else { + rhs = f.extract(rhsTuple, i) // n:1 assignment + } + + if id, ok := s.Lhs[i].(*ast.Ident); ok { + if id.Name != "_" { + if obj, ok := f.info.Defs[id]; ok { + lhs = obj.Type() // definition + } + } + } + if lhs == nil { + lhs = f.expr(s.Lhs[i]) // assignment + } + f.assign(lhs, rhs) + } + + default: + // y op= x + f.expr(s.Lhs[0]) + f.expr(s.Rhs[0]) + } + + case *ast.GoStmt: + f.expr(s.Call) + + case *ast.DeferStmt: + f.expr(s.Call) + + case *ast.ReturnStmt: + formals := f.sig.Results() + switch len(s.Results) { + case formals.Len(): // 1:1 + for i, result := range s.Results { + f.assign(formals.At(i).Type(), f.expr(result)) + } + + case 1: // n:1 + tuple := f.exprN(s.Results[0]) + for i := 0; i < formals.Len(); i++ { + f.assign(formals.At(i).Type(), f.extract(tuple, i)) + } + } + + case *ast.SelectStmt: + f.stmt(s.Body) + + case *ast.BlockStmt: + for _, s := range s.List { + f.stmt(s) + } + + case *ast.IfStmt: + if s.Init != nil { + f.stmt(s.Init) + } + f.expr(s.Cond) + f.stmt(s.Body) + if s.Else != nil { + f.stmt(s.Else) + } + + case *ast.SwitchStmt: + if s.Init != nil { + f.stmt(s.Init) + } + var tag types.Type = tUntypedBool + if s.Tag != nil { + tag = f.expr(s.Tag) + } + for _, cc := range s.Body.List { + cc := cc.(*ast.CaseClause) + for _, cond := range cc.List { + f.compare(tag, f.info.Types[cond].Type) + } + for _, s := range cc.Body { + f.stmt(s) + } + } + + case *ast.TypeSwitchStmt: + if s.Init != nil { + f.stmt(s.Init) + } + var I types.Type + switch ass := s.Assign.(type) { + case *ast.ExprStmt: // x.(type) + I = f.expr(ast.Unparen(ass.X).(*ast.TypeAssertExpr).X) + case *ast.AssignStmt: // y := x.(type) + I = f.expr(ast.Unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X) + } + for _, cc := range s.Body.List { + cc := cc.(*ast.CaseClause) + for _, cond := range cc.List { + tCase := f.info.Types[cond].Type + if tCase != tUntypedNil { + f.typeAssert(I, tCase) + } + } + for _, s := range cc.Body { + f.stmt(s) + } + } + + case *ast.CommClause: + if s.Comm != nil { + f.stmt(s.Comm) + } + for _, s := range s.Body { + f.stmt(s) + } + + case *ast.ForStmt: + if s.Init != nil { + f.stmt(s.Init) + } + if s.Cond != nil { + f.expr(s.Cond) + } + if s.Post != nil { + f.stmt(s.Post) + } + f.stmt(s.Body) + + case *ast.RangeStmt: + x := f.expr(s.X) + // No conversions are involved when Tok==DEFINE. + if s.Tok == token.ASSIGN { + if s.Key != nil { + k := f.expr(s.Key) + var xelem types.Type + // Keys of array, *array, slice, string aren't interesting + // since the RHS key type is just an int. + switch ux := typeparams.CoreType(x).(type) { + case *types.Chan: + xelem = ux.Elem() + case *types.Map: + xelem = ux.Key() + } + if xelem != nil { + f.assign(k, xelem) + } + } + if s.Value != nil { + val := f.expr(s.Value) + var xelem types.Type + // Values of type strings aren't interesting because + // the RHS value type is just a rune. + switch ux := typeparams.CoreType(x).(type) { + case *types.Array: + xelem = ux.Elem() + case *types.Map: + xelem = ux.Elem() + case *types.Pointer: // *array + xelem = typeparams.CoreType(typeparams.Deref(ux)).(*types.Array).Elem() + case *types.Slice: + xelem = ux.Elem() + } + if xelem != nil { + f.assign(val, xelem) + } + } + } + f.stmt(s.Body) + + default: + panic(s) + } +} + +// -- Plundered from golang.org/x/tools/go/ssa ----------------- + +func instance(info *types.Info, expr ast.Expr) bool { + var id *ast.Ident + switch x := expr.(type) { + case *ast.Ident: + id = x + case *ast.SelectorExpr: + id = x.Sel + default: + return false + } + _, ok := info.Instances[id] + return ok +} diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 80b23723bc7..1c7bf37a3b4 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.20251114194111-59ff18ce4883 +# golang.org/x/tools v0.39.1-0.20251120214200-68724afed209 ## explicit; go 1.24.0 golang.org/x/tools/cmd/bisect golang.org/x/tools/cover @@ -149,6 +149,7 @@ 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 +golang.org/x/tools/refactor/satisfy # rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef ## explicit; go 1.20 rsc.io/markdown