src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

code.go (19557B)


      1 // Package code answers structural and type questions about Go code.
      2 package code
      3 
      4 import (
      5 	"fmt"
      6 	"go/ast"
      7 	"go/build/constraint"
      8 	"go/constant"
      9 	"go/token"
     10 	"go/types"
     11 	"go/version"
     12 	"path/filepath"
     13 	"slices"
     14 	"strings"
     15 
     16 	"honnef.co/go/tools/analysis/facts/generated"
     17 	"honnef.co/go/tools/analysis/facts/purity"
     18 	"honnef.co/go/tools/analysis/facts/tokenfile"
     19 	"honnef.co/go/tools/go/types/typeutil"
     20 	"honnef.co/go/tools/knowledge"
     21 	"honnef.co/go/tools/pattern"
     22 
     23 	"golang.org/x/tools/go/analysis"
     24 )
     25 
     26 type Positioner interface {
     27 	Pos() token.Pos
     28 }
     29 
     30 func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool {
     31 	typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice)
     32 	if !ok {
     33 		return false
     34 	}
     35 	elem := types.Unalias(typ.Elem())
     36 	if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 {
     37 		// Before Go 1.18, one could not directly convert from []T (where 'type T byte')
     38 		// to string. See also https://github.com/golang/go/issues/23536.
     39 		elem = elem.Underlying()
     40 	}
     41 	return types.Identical(elem, types.Typ[types.Byte])
     42 }
     43 
     44 func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
     45 	ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer)
     46 	if !ok {
     47 		return false
     48 	}
     49 	return typeutil.IsTypeWithName(ptr.Elem(), name)
     50 }
     51 
     52 func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
     53 	return typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(expr), name)
     54 }
     55 
     56 func IsInTest(pass *analysis.Pass, node Positioner) bool {
     57 	// FIXME(dh): this doesn't work for global variables with
     58 	// initializers
     59 	f := pass.Fset.File(node.Pos())
     60 	return f != nil && strings.HasSuffix(f.Name(), "_test.go")
     61 }
     62 
     63 // IsMain reports whether the package being processed is a package
     64 // main.
     65 func IsMain(pass *analysis.Pass) bool {
     66 	return pass.Pkg.Name() == "main"
     67 }
     68 
     69 // IsMainLike reports whether the package being processed is a
     70 // main-like package. A main-like package is a package that is
     71 // package main, or that is intended to be used by a tool framework
     72 // such as cobra to implement a command.
     73 //
     74 // Note that this function errs on the side of false positives; it may
     75 // return true for packages that aren't main-like. IsMainLike is
     76 // intended for analyses that wish to suppress diagnostics for
     77 // main-like packages to avoid false positives.
     78 func IsMainLike(pass *analysis.Pass) bool {
     79 	if pass.Pkg.Name() == "main" {
     80 		return true
     81 	}
     82 	for _, imp := range pass.Pkg.Imports() {
     83 		if imp.Path() == "github.com/spf13/cobra" {
     84 			return true
     85 		}
     86 	}
     87 	return false
     88 }
     89 
     90 func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
     91 	info := pass.TypesInfo
     92 	sel := info.Selections[expr]
     93 	if sel == nil {
     94 		switch x := expr.X.(type) {
     95 		case *ast.Ident:
     96 			pkg, ok := info.ObjectOf(x).(*types.PkgName)
     97 			if !ok {
     98 				return fmt.Sprintf("(%s).%s", info.TypeOf(x), expr.Sel.Name)
     99 			}
    100 			return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
    101 		case *ast.SelectorExpr:
    102 			return fmt.Sprintf("(%s).%s", SelectorName(pass, x), expr.Sel.Name)
    103 		default:
    104 			panic(fmt.Sprintf("unsupported selector: %v", expr))
    105 		}
    106 	}
    107 	if v, ok := sel.Obj().(*types.Var); ok && v.IsField() {
    108 		return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name())
    109 	} else {
    110 		return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
    111 	}
    112 }
    113 
    114 func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
    115 	return pass.TypesInfo.Types[expr].IsNil()
    116 }
    117 
    118 func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
    119 	val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
    120 	return constant.BoolVal(val)
    121 }
    122 
    123 func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
    124 	// We explicitly don't support typed bools because more often than
    125 	// not, custom bool types are used as binary enums and the explicit
    126 	// comparison is desired. We err on the side of false negatives and
    127 	// treat aliases like other custom types.
    128 
    129 	ident, ok := expr.(*ast.Ident)
    130 	if !ok {
    131 		return false
    132 	}
    133 	obj := pass.TypesInfo.ObjectOf(ident)
    134 	c, ok := obj.(*types.Const)
    135 	if !ok {
    136 		return false
    137 	}
    138 	basic, ok := c.Type().(*types.Basic)
    139 	if !ok {
    140 		return false
    141 	}
    142 	if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
    143 		return false
    144 	}
    145 	return true
    146 }
    147 
    148 func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
    149 	tv := pass.TypesInfo.Types[expr]
    150 	if tv.Value == nil {
    151 		return 0, false
    152 	}
    153 	if tv.Value.Kind() != constant.Int {
    154 		return 0, false
    155 	}
    156 	return constant.Int64Val(tv.Value)
    157 }
    158 
    159 func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
    160 	val := pass.TypesInfo.Types[expr].Value
    161 	if val == nil {
    162 		return "", false
    163 	}
    164 	if val.Kind() != constant.String {
    165 		return "", false
    166 	}
    167 	return constant.StringVal(val), true
    168 }
    169 
    170 func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
    171 	// See the comment in typeutil.FuncName for why this doesn't require special handling
    172 	// of aliases.
    173 
    174 	fun := ast.Unparen(call.Fun)
    175 
    176 	// Instantiating a function cannot return another generic function, so doing this once is enough
    177 	switch idx := fun.(type) {
    178 	case *ast.IndexExpr:
    179 		fun = idx.X
    180 	case *ast.IndexListExpr:
    181 		fun = idx.X
    182 	}
    183 
    184 	// (foo)[T] is not a valid instantiation, so no need to unparen again.
    185 
    186 	switch fun := fun.(type) {
    187 	case *ast.SelectorExpr:
    188 		fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
    189 		if !ok {
    190 			return ""
    191 		}
    192 		return typeutil.FuncName(fn)
    193 	case *ast.Ident:
    194 		obj := pass.TypesInfo.ObjectOf(fun)
    195 		switch obj := obj.(type) {
    196 		case *types.Func:
    197 			return typeutil.FuncName(obj)
    198 		case *types.Builtin:
    199 			return obj.Name()
    200 		default:
    201 			return ""
    202 		}
    203 	default:
    204 		return ""
    205 	}
    206 }
    207 
    208 func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
    209 	// See the comment in typeutil.FuncName for why this doesn't require special handling
    210 	// of aliases.
    211 
    212 	call, ok := node.(*ast.CallExpr)
    213 	if !ok {
    214 		return false
    215 	}
    216 	return CallName(pass, call) == name
    217 }
    218 
    219 func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
    220 	// See the comment in typeutil.FuncName for why this doesn't require special handling
    221 	// of aliases.
    222 
    223 	call, ok := node.(*ast.CallExpr)
    224 	if !ok {
    225 		return false
    226 	}
    227 	q := CallName(pass, call)
    228 	return slices.Contains(names, q)
    229 }
    230 
    231 func File(pass *analysis.Pass, node Positioner) *ast.File {
    232 	m := pass.ResultOf[tokenfile.Analyzer].(map[*token.File]*ast.File)
    233 	return m[pass.Fset.File(node.Pos())]
    234 }
    235 
    236 // BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as
    237 // GOOS and GOARCH in file names.
    238 func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) {
    239 	var expr constraint.Expr
    240 	for _, cmt := range f.Comments {
    241 		if len(cmt.List) == 0 {
    242 			continue
    243 		}
    244 		for _, el := range cmt.List {
    245 			if el.Pos() > f.Package {
    246 				break
    247 			}
    248 			if line := el.Text; strings.HasPrefix(line, "//go:build") {
    249 				var err error
    250 				expr, err = constraint.Parse(line)
    251 				if err != nil {
    252 					expr = nil
    253 				}
    254 				break
    255 			}
    256 		}
    257 	}
    258 
    259 	name := pass.Fset.PositionFor(f.Pos(), false).Filename
    260 	oexpr := constraintsFromName(name)
    261 	if oexpr != nil {
    262 		if expr == nil {
    263 			expr = oexpr
    264 		} else {
    265 			expr = &constraint.AndExpr{X: expr, Y: oexpr}
    266 		}
    267 	}
    268 
    269 	return expr, expr != nil
    270 }
    271 
    272 func constraintsFromName(name string) constraint.Expr {
    273 	name = filepath.Base(name)
    274 	name = strings.TrimSuffix(name, ".go")
    275 	name = strings.TrimSuffix(name, "_test")
    276 	var goos, goarch string
    277 	switch strings.Count(name, "_") {
    278 	case 0:
    279 		// No GOOS or GOARCH in the file name.
    280 	case 1:
    281 		_, c, _ := strings.Cut(name, "_")
    282 		if _, ok := knowledge.KnownGOOS[c]; ok {
    283 			goos = c
    284 		} else if _, ok := knowledge.KnownGOARCH[c]; ok {
    285 			goarch = c
    286 		}
    287 	default:
    288 		n := strings.LastIndex(name, "_")
    289 		if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok {
    290 			// The file name is *_stuff_GOOS.go
    291 			goos = name[n+1:]
    292 		} else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok {
    293 			// The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go
    294 			goarch = name[n+1:]
    295 			_, c, _ := strings.Cut(name[:n], "_")
    296 			if _, ok := knowledge.KnownGOOS[c]; ok {
    297 				// The file name is *_GOOS_GOARCH.go
    298 				goos = c
    299 			}
    300 		} else {
    301 			// The file name could also be something like foo_windows_nonsense.go — and because nonsense
    302 			// isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either.
    303 		}
    304 	}
    305 
    306 	var expr constraint.Expr
    307 	if goos != "" {
    308 		expr = &constraint.TagExpr{Tag: goos}
    309 	}
    310 	if goarch != "" {
    311 		if expr == nil {
    312 			expr = &constraint.TagExpr{Tag: goarch}
    313 		} else {
    314 			expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}}
    315 		}
    316 	}
    317 	return expr
    318 }
    319 
    320 // IsGenerated reports whether pos is in a generated file. It ignores
    321 // //line directives.
    322 func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
    323 	_, ok := Generator(pass, pos)
    324 	return ok
    325 }
    326 
    327 // Generator returns the generator that generated the file containing
    328 // pos. It ignores //line directives.
    329 func Generator(pass *analysis.Pass, pos token.Pos) (generated.Generator, bool) {
    330 	file := pass.Fset.PositionFor(pos, false).Filename
    331 	m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
    332 	g, ok := m[file]
    333 	return g, ok
    334 }
    335 
    336 // MayHaveSideEffects reports whether expr may have side effects. If
    337 // the purity argument is nil, this function implements a purely
    338 // syntactic check, meaning that any function call may have side
    339 // effects, regardless of the called function's body. Otherwise,
    340 // purity will be consulted to determine the purity of function calls.
    341 func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity purity.Result) bool {
    342 	switch expr := expr.(type) {
    343 	case *ast.BadExpr:
    344 		return true
    345 	case *ast.Ellipsis:
    346 		return MayHaveSideEffects(pass, expr.Elt, purity)
    347 	case *ast.FuncLit:
    348 		// the literal itself cannot have side effects, only calling it
    349 		// might, which is handled by CallExpr.
    350 		return false
    351 	case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
    352 		// types cannot have side effects
    353 		return false
    354 	case *ast.BasicLit:
    355 		return false
    356 	case *ast.BinaryExpr:
    357 		return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
    358 	case *ast.CallExpr:
    359 		if purity == nil {
    360 			return true
    361 		}
    362 		switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
    363 		case *types.Func:
    364 			if _, ok := purity[obj]; !ok {
    365 				return true
    366 			}
    367 		case *types.Builtin:
    368 			switch obj.Name() {
    369 			case "len", "cap":
    370 			default:
    371 				return true
    372 			}
    373 		default:
    374 			return true
    375 		}
    376 		for _, arg := range expr.Args {
    377 			if MayHaveSideEffects(pass, arg, purity) {
    378 				return true
    379 			}
    380 		}
    381 		return false
    382 	case *ast.CompositeLit:
    383 		if MayHaveSideEffects(pass, expr.Type, purity) {
    384 			return true
    385 		}
    386 		for _, elt := range expr.Elts {
    387 			if MayHaveSideEffects(pass, elt, purity) {
    388 				return true
    389 			}
    390 		}
    391 		return false
    392 	case *ast.Ident:
    393 		return false
    394 	case *ast.IndexExpr:
    395 		return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
    396 	case *ast.IndexListExpr:
    397 		// In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in
    398 		// being safe.
    399 		if MayHaveSideEffects(pass, expr.X, purity) {
    400 			return true
    401 		}
    402 		for _, idx := range expr.Indices {
    403 			if MayHaveSideEffects(pass, idx, purity) {
    404 				return true
    405 			}
    406 		}
    407 		return false
    408 	case *ast.KeyValueExpr:
    409 		return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
    410 	case *ast.SelectorExpr:
    411 		return MayHaveSideEffects(pass, expr.X, purity)
    412 	case *ast.SliceExpr:
    413 		return MayHaveSideEffects(pass, expr.X, purity) ||
    414 			MayHaveSideEffects(pass, expr.Low, purity) ||
    415 			MayHaveSideEffects(pass, expr.High, purity) ||
    416 			MayHaveSideEffects(pass, expr.Max, purity)
    417 	case *ast.StarExpr:
    418 		return MayHaveSideEffects(pass, expr.X, purity)
    419 	case *ast.TypeAssertExpr:
    420 		return MayHaveSideEffects(pass, expr.X, purity)
    421 	case *ast.UnaryExpr:
    422 		if MayHaveSideEffects(pass, expr.X, purity) {
    423 			return true
    424 		}
    425 		return expr.Op == token.ARROW || expr.Op == token.AND
    426 	case *ast.ParenExpr:
    427 		return MayHaveSideEffects(pass, expr.X, purity)
    428 	case nil:
    429 		return false
    430 	default:
    431 		panic(fmt.Sprintf("internal error: unhandled type %T", expr))
    432 	}
    433 }
    434 
    435 // LanguageVersion returns the version of the Go language that node has access to. This
    436 // might differ from the version of the Go standard library.
    437 func LanguageVersion(pass *analysis.Pass, node Positioner) string {
    438 	// As of Go 1.21, two places can specify the minimum Go version:
    439 	// - 'go' directives in go.mod and go.work files
    440 	// - individual files by using '//go:build'
    441 	//
    442 	// Individual files can upgrade to a higher version than the module version. Individual files
    443 	// can also downgrade to a lower version, but only if the module version is at least Go 1.21.
    444 	//
    445 	// The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will
    446 	// not type-check on versions that are too old, and thus never reach our analyzes. In practice,
    447 	// such ineffective downgrading will always be useless, as the compiler will not restrict the
    448 	// language features used, and doesn't ever rely on minimum versions to restrict the use of the
    449 	// standard library. However, for us, both choices (respecting or ignoring ineffective
    450 	// downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing
    451 	// noisy positives.
    452 	//
    453 	// The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion.
    454 	// ast.File's version is populated by the parser, whereas types.Package's version is populated
    455 	// from the Go version specified in the types.Config, which is set by our package loader, based
    456 	// on the module information provided by go/packages, via 'go list -json'.
    457 	//
    458 	// As of Go 1.21, standard library packages do not present themselves as modules, and thus do
    459 	// not have a version set on their types.Package. In this case, we fall back to the version
    460 	// provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to
    461 	// the Go version that Staticcheck was built with when no module information exists. In the
    462 	// future, the standard library will hopefully be a proper module (see
    463 	// https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version
    464 	// of standard library packages will match that of the used Go version. At that point,
    465 	// Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting
    466 	// code due to language changes.
    467 	//
    468 	// We also lack module information when building in GOPATH mode. In this case, the implied
    469 	// language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We
    470 	// don't handle this yet, and it will not matter until Go 1.22.
    471 	//
    472 	// It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module
    473 	// version at all is provided, which should preclude per-file downgrading. On the other hand,
    474 	// https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21
    475 	// in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all
    476 	// relevant language changes before Go 1.22 will lead to type-checking failures and never reach
    477 	// us.
    478 	//
    479 	// Per-file upgrading is permitted in GOPATH mode.
    480 
    481 	// If the file has its own Go version, we will return that. Otherwise, we default to
    482 	// the type checker's GoVersion, which is populated from either the Go module, or from
    483 	// our '-go' flag.
    484 	return pass.TypesInfo.FileVersions[File(pass, node)]
    485 }
    486 
    487 // StdlibVersion returns the version of the Go standard library that node can expect to
    488 // have access to. This might differ from the language version for versions of Go older
    489 // than 1.21.
    490 func StdlibVersion(pass *analysis.Pass, node Positioner) string {
    491 	// The Go version as specified in go.mod or via the '-go' flag
    492 	n := pass.Pkg.GoVersion()
    493 
    494 	f := File(pass, node)
    495 	if f == nil {
    496 		panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false)))
    497 	}
    498 
    499 	if nf := f.GoVersion; nf != "" {
    500 		if version.Compare(n, "go1.21") == -1 {
    501 			// Before Go 1.21, the Go version set in go.mod specified the maximum language
    502 			// version available to the module. It wasn't uncommon to set the version to
    503 			// Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib)
    504 			// to files tagged for 1.20, and supporting a lower version overall. As such,
    505 			// a file tagged lower than the module version couldn't expect to have access
    506 			// to the standard library of the version set in go.mod.
    507 			//
    508 			// At the same time, a file tagged higher than the module version, while not
    509 			// able to use newer language features, would still have been able to use a
    510 			// newer standard library.
    511 			//
    512 			// While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users'
    513 			// expectations have not.
    514 			return nf
    515 		} else {
    516 			// Go 1.21 and newer refuse to build modules that depend on versions newer
    517 			// than the used version of the Go toolchain. This means that in a 1.22 module
    518 			// with a file tagged as 1.17, the file can expect to have access to 1.22's
    519 			// standard library (but not to 1.22 language features). A file tagged with a
    520 			// version higher than the minimum version has access to the newer standard
    521 			// library (and language features.)
    522 			//
    523 			// Do note that strictly speaking we're conflating the Go version and the
    524 			// module version in our check. Nothing is stopping a user from using Go 1.17
    525 			// (which didn't implement the new rules for versions in go.mod) to build a Go
    526 			// 1.22 module, in which case a file tagged with go1.17 will not have access to the 1.22
    527 			// standard library. However, we believe that if a module requires 1.21 or
    528 			// newer, then the author clearly expects the new behavior, and doesn't care
    529 			// for the old one. Otherwise they would've specified an older version.
    530 			//
    531 			// In other words, the module version also specifies what it itself actually means, with
    532 			// >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for
    533 			// the language.
    534 
    535 			if version.Compare(nf, n) == 1 {
    536 				return nf
    537 			}
    538 		}
    539 	}
    540 
    541 	return n
    542 }
    543 
    544 var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`)
    545 
    546 func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) {
    547 	m, ok := Match(pass, integerLiteralQ, node)
    548 	if !ok {
    549 		return types.TypeAndValue{}, false
    550 	}
    551 	return m.State["tv"].(types.TypeAndValue), true
    552 }
    553 
    554 func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool {
    555 	tv, ok := IntegerLiteral(pass, node)
    556 	if !ok {
    557 		return false
    558 	}
    559 	return constant.Compare(tv.Value, token.EQL, value)
    560 }
    561 
    562 // IsMethod reports whether expr is a method call of a named method with signature meth.
    563 // If name is empty, it is not checked.
    564 // For now, method expressions (Type.Method(recv, ..)) are not considered method calls.
    565 func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool {
    566 	if name != "" && expr.Sel.Name != name {
    567 		return false
    568 	}
    569 	sel, ok := pass.TypesInfo.Selections[expr]
    570 	if !ok || sel.Kind() != types.MethodVal {
    571 		return false
    572 	}
    573 	return types.Identical(sel.Type(), meth)
    574 }
    575 
    576 func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
    577 	found := false
    578 	fn := func(node ast.Node) bool {
    579 		ident2, ok := node.(*ast.Ident)
    580 		if !ok {
    581 			return true
    582 		}
    583 		if ident == pass.TypesInfo.ObjectOf(ident2) {
    584 			found = true
    585 			return false
    586 		}
    587 		return true
    588 	}
    589 	ast.Inspect(expr, fn)
    590 	return found
    591 }