]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/vendor: update to x/tools@68724af
authorAlan Donovan <adonovan@google.com>
Thu, 20 Nov 2025 21:45:17 +0000 (16:45 -0500)
committerGopher Robot <gobot@golang.org>
Thu, 20 Nov 2025 22:44:01 +0000 (14:44 -0800)
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 <adonovan@google.com>
Commit-Queue: Alan Donovan <adonovan@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

12 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
src/cmd/vendor/golang.org/x/tools/internal/moreiters/iters.go
src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
src/cmd/vendor/golang.org/x/tools/refactor/satisfy/find.go [new file with mode: 0644]
src/cmd/vendor/modules.txt

index 090b2c943f3f4c333f4471dd8e0aca66f6bed3e9..2f13ca0dc9690ce6feffc16783ed190ca17c6078 100644 (file)
@@ -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 (
index e4955f224b405cd6f5eac5dbdaa9815a6737d458..d75d9339fbf2301aae95e69d967f8721c5cfc72c 100644 (file)
@@ -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=
index 1b3cb108c6d40489e5a7f841b205532e83513662..c0b75202589c9f5afecd32f59ab397f3b37ac825 100644 (file)
@@ -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
-
-               // 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),
-                       })
+                       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),
+                               })
+                       }
                }
+
                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
                        }},
                })
        }
index 2352c8b6088ad032e4a1ee2de80e4d71b047fce3..f97541d4b34490168827c225fcfc470fd748d3c4 100644 (file)
@@ -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)
+                                       }
                                }
                        }
                }
index 521c264c51e82d60a9f74b3821118d32ac763f8a..bc4ad677cd093c5792e748372c7fe489e0525dda 100644 (file)
@@ -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 {
index f04e44143412a4df50d9d6315c3217d033b98e94..a09bfd1c6c8b8331844e908d7144290b3489f71a 100644 (file)
 //     }
 //     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
index 1eac2589bfab9942dc6010a33f605b7875f2c90a..fd9fe16472338d3584f3f622c8c34c7749a7892c 100644 (file)
@@ -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,21 +184,42 @@ 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
-               )
+       for cur := range inspect.Root().Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil), (*ast.InterfaceType)(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
+                               // assigned to an anonymous function) is a potential
+                               // print or printf wrapper.
+                               //
+                               // Later processing will analyze the graph of potential
+                               // wrappers and their function bodies to pick out the
+                               // ones that are true wrappers.
+                               w := &wrapper{
+                                       obj:     obj,
+                                       curBody: curBody,
+                                       format:  format, // non-nil => printf
+                                       args:    args,
+                               }
+                               byObj[w.obj] = w
+                               wrappers = append(wrappers, w)
+                               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 {
-                               curBody = cur.ChildAt(edge.FuncDecl_Body, -1)
-                               obj = info.Defs[f.Name]
-                               sig = obj.Type().(*types.Signature)
+                               fn := info.Defs[f.Name].(*types.Func)
+                               addWrapper(fn, fn.Signature(), cur.ChildAt(edge.FuncDecl_Body, -1))
                        }
 
                case *ast.FuncLit:
@@ -210,8 +232,6 @@ func findPrintLike(pass *analysis.Pass, res *Result) {
                        // 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:
@@ -222,49 +242,89 @@ func findPrintLike(pass *analysis.Pass, res *Result) {
                                lhs = curLhs.Node().(ast.Expr)
                        }
 
+                       var v *types.Var
                        switch lhs := lhs.(type) {
                        case *ast.Ident:
                                // variable: wrapf = func(...)
-                               obj = info.ObjectOf(lhs).(*types.Var)
+                               v = 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)
+                                       v = sel.Obj().(*types.Var)
                                } else {
                                        // imported var: pkg.Wrapf = func(...)
-                                       obj = info.Uses[lhs.Sel].(*types.Var)
+                                       v = info.Uses[lhs.Sel].(*types.Var)
                                }
                        }
-               }
-               if obj != nil {
-                       format, args := formatArgsParams(sig)
-                       if args != nil {
-                               // obj (the symbol for a function/method, or variable
-                               // assigned to an anonymous function) is a potential
-                               // print or printf wrapper.
-                               //
-                               // Later processing will analyze the graph of potential
-                               // wrappers and their function bodies to pick out the
-                               // ones that are true wrappers.
-                               w := &wrapper{
-                                       obj:     obj,
-                                       curBody: curBody,
-                                       format:  format, // non-nil => printf
-                                       args:    args,
+                       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{})
                                }
-                               byObj[w.obj] = w
-                               wrappers = append(wrappers, w)
                        }
                }
        }
 
+       // 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
-       }
-
-       if !call.Ellipsis.IsValid() {
-               typ, ok := pass.TypesInfo.Types[call.Fun].Type.(*types.Signature)
-               if !ok {
+       // 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 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)
-                       //   }
+
+               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
                }
-               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
index 7a02fca21e239dd83bc8a5239236d65b3b2c7c4e..6986a51875b9be7788d75f3b71b6f779aaa14ec3 100644 (file)
@@ -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
index 69c76ccb9b6f80f472f8a24d8b17531913dcd61a..9e4aaf94855ade4e6c2c85d4f264202dd309c770 100644 (file)
@@ -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
+}
index 03ef0714e007b5a3e24a3387daabe5f7710384fa..af1252cee86c5aab999f299070be39982e66fc95 100644 (file)
@@ -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 (file)
index 0000000..6d23aa6
--- /dev/null
@@ -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
+}
index 80b23723bc780d94c12dd2cf35100e941c290b4c..1c7bf37a3b4818f646dc818921335e5ce19861f1 100644 (file)
@@ -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