code.go raw

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