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 (
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=
"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"
"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
(*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)
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),
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
}},
})
}
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)
+ }
}
}
}
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 {
// }
// 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
"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() {
case KindErrorf:
return "errorf"
}
- return ""
+ return "(none)"
}
// Result is the printf analyzer's result type. Clients may query the result
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"
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:
// 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:
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),
(*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.
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)
}
}
}
}
}
+// 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,
"(*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
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
}
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
+}
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
// 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
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 {
// 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 {
--- /dev/null
+// 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
+}
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
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