api/vendor/honnef.co/go/tools/unused/unused.go

1066 lines
24 KiB
Go

package unused // import "honnef.co/go/tools/unused"
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"io"
"path/filepath"
"strings"
"honnef.co/go/tools/lint"
. "honnef.co/go/tools/lint/lintdsl"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types/typeutil"
)
func NewLintChecker(c *Checker) *LintChecker {
l := &LintChecker{
c: c,
}
return l
}
type LintChecker struct {
c *Checker
}
func (*LintChecker) Name() string { return "unused" }
func (*LintChecker) Prefix() string { return "U" }
func (l *LintChecker) Init(*lint.Program) {}
func (l *LintChecker) Funcs() map[string]lint.Func {
return map[string]lint.Func{
"U1000": l.Lint,
}
}
func typString(obj types.Object) string {
switch obj := obj.(type) {
case *types.Func:
return "func"
case *types.Var:
if obj.IsField() {
return "field"
}
return "var"
case *types.Const:
return "const"
case *types.TypeName:
return "type"
default:
// log.Printf("%T", obj)
return "identifier"
}
}
func (l *LintChecker) Lint(j *lint.Job) {
unused := l.c.Check(j.Program.Prog)
for _, u := range unused {
name := u.Obj.Name()
if sig, ok := u.Obj.Type().(*types.Signature); ok && sig.Recv() != nil {
switch sig.Recv().Type().(type) {
case *types.Named, *types.Pointer:
typ := types.TypeString(sig.Recv().Type(), func(*types.Package) string { return "" })
if len(typ) > 0 && typ[0] == '*' {
name = fmt.Sprintf("(%s).%s", typ, u.Obj.Name())
} else if len(typ) > 0 {
name = fmt.Sprintf("%s.%s", typ, u.Obj.Name())
}
}
}
j.Errorf(u.Obj, "%s %s is unused", typString(u.Obj), name)
}
}
type graph struct {
roots []*graphNode
nodes map[interface{}]*graphNode
}
func (g *graph) markUsedBy(obj, usedBy interface{}) {
objNode := g.getNode(obj)
usedByNode := g.getNode(usedBy)
if objNode.obj == usedByNode.obj {
return
}
usedByNode.uses[objNode] = struct{}{}
}
var labelCounter = 1
func (g *graph) getNode(obj interface{}) *graphNode {
for {
if pt, ok := obj.(*types.Pointer); ok {
obj = pt.Elem()
} else {
break
}
}
_, ok := g.nodes[obj]
if !ok {
g.addObj(obj)
}
return g.nodes[obj]
}
func (g *graph) addObj(obj interface{}) {
if pt, ok := obj.(*types.Pointer); ok {
obj = pt.Elem()
}
node := &graphNode{obj: obj, uses: make(map[*graphNode]struct{}), n: labelCounter}
g.nodes[obj] = node
labelCounter++
if obj, ok := obj.(*types.Struct); ok {
n := obj.NumFields()
for i := 0; i < n; i++ {
field := obj.Field(i)
g.markUsedBy(obj, field)
}
}
}
type graphNode struct {
obj interface{}
uses map[*graphNode]struct{}
used bool
quiet bool
n int
}
type CheckMode int
const (
CheckConstants CheckMode = 1 << iota
CheckFields
CheckFunctions
CheckTypes
CheckVariables
CheckAll = CheckConstants | CheckFields | CheckFunctions | CheckTypes | CheckVariables
)
type Unused struct {
Obj types.Object
Position token.Position
}
type Checker struct {
Mode CheckMode
WholeProgram bool
ConsiderReflection bool
Debug io.Writer
graph *graph
msCache typeutil.MethodSetCache
lprog *loader.Program
topmostCache map[*types.Scope]*types.Scope
interfaces []*types.Interface
}
func NewChecker(mode CheckMode) *Checker {
return &Checker{
Mode: mode,
graph: &graph{
nodes: make(map[interface{}]*graphNode),
},
topmostCache: make(map[*types.Scope]*types.Scope),
}
}
func (c *Checker) checkConstants() bool { return (c.Mode & CheckConstants) > 0 }
func (c *Checker) checkFields() bool { return (c.Mode & CheckFields) > 0 }
func (c *Checker) checkFunctions() bool { return (c.Mode & CheckFunctions) > 0 }
func (c *Checker) checkTypes() bool { return (c.Mode & CheckTypes) > 0 }
func (c *Checker) checkVariables() bool { return (c.Mode & CheckVariables) > 0 }
func (c *Checker) markFields(typ types.Type) {
structType, ok := typ.Underlying().(*types.Struct)
if !ok {
return
}
n := structType.NumFields()
for i := 0; i < n; i++ {
field := structType.Field(i)
c.graph.markUsedBy(field, typ)
}
}
type Error struct {
Errors map[string][]error
}
func (e Error) Error() string {
return fmt.Sprintf("errors in %d packages", len(e.Errors))
}
func (c *Checker) Check(lprog *loader.Program) []Unused {
var unused []Unused
c.lprog = lprog
if c.WholeProgram {
c.findExportedInterfaces()
}
for _, pkg := range c.lprog.InitialPackages() {
c.processDefs(pkg)
c.processUses(pkg)
c.processTypes(pkg)
c.processSelections(pkg)
c.processAST(pkg)
}
for _, node := range c.graph.nodes {
obj, ok := node.obj.(types.Object)
if !ok {
continue
}
typNode, ok := c.graph.nodes[obj.Type()]
if !ok {
continue
}
node.uses[typNode] = struct{}{}
}
roots := map[*graphNode]struct{}{}
for _, root := range c.graph.roots {
roots[root] = struct{}{}
}
markNodesUsed(roots)
c.markNodesQuiet()
if c.Debug != nil {
c.printDebugGraph(c.Debug)
}
for _, node := range c.graph.nodes {
if node.used || node.quiet {
continue
}
obj, ok := node.obj.(types.Object)
if !ok {
continue
}
found := false
if !false {
for _, pkg := range c.lprog.InitialPackages() {
if pkg.Pkg == obj.Pkg() {
found = true
break
}
}
}
if !found {
continue
}
pos := c.lprog.Fset.Position(obj.Pos())
if pos.Filename == "" || filepath.Base(pos.Filename) == "C" {
continue
}
generated := false
for _, file := range c.lprog.Package(obj.Pkg().Path()).Files {
if c.lprog.Fset.Position(file.Pos()).Filename != pos.Filename {
continue
}
if len(file.Comments) > 0 {
generated = isGenerated(file.Comments[0].Text())
}
break
}
if generated {
continue
}
unused = append(unused, Unused{Obj: obj, Position: pos})
}
return unused
}
// isNoCopyType reports whether a type represents the NoCopy sentinel
// type. The NoCopy type is a named struct with no fields and exactly
// one method `func Lock()` that is empty.
//
// FIXME(dh): currently we're not checking that the function body is
// empty.
func isNoCopyType(typ types.Type) bool {
st, ok := typ.Underlying().(*types.Struct)
if !ok {
return false
}
if st.NumFields() != 0 {
return false
}
named, ok := typ.(*types.Named)
if !ok {
return false
}
if named.NumMethods() != 1 {
return false
}
meth := named.Method(0)
if meth.Name() != "Lock" {
return false
}
sig := meth.Type().(*types.Signature)
if sig.Params().Len() != 0 || sig.Results().Len() != 0 {
return false
}
return true
}
func (c *Checker) useNoCopyFields(typ types.Type) {
if st, ok := typ.Underlying().(*types.Struct); ok {
n := st.NumFields()
for i := 0; i < n; i++ {
field := st.Field(i)
if isNoCopyType(field.Type()) {
c.graph.markUsedBy(field, typ)
c.graph.markUsedBy(field.Type().(*types.Named).Method(0), field.Type())
}
}
}
}
func (c *Checker) useExportedFields(typ types.Type) {
if st, ok := typ.Underlying().(*types.Struct); ok {
n := st.NumFields()
for i := 0; i < n; i++ {
field := st.Field(i)
if field.Exported() {
c.graph.markUsedBy(field, typ)
}
}
}
}
func (c *Checker) useExportedMethods(typ types.Type) {
named, ok := typ.(*types.Named)
if !ok {
return
}
ms := typeutil.IntuitiveMethodSet(named, &c.msCache)
for i := 0; i < len(ms); i++ {
meth := ms[i].Obj()
if meth.Exported() {
c.graph.markUsedBy(meth, typ)
}
}
st, ok := named.Underlying().(*types.Struct)
if !ok {
return
}
n := st.NumFields()
for i := 0; i < n; i++ {
field := st.Field(i)
if !field.Anonymous() {
continue
}
ms := typeutil.IntuitiveMethodSet(field.Type(), &c.msCache)
for j := 0; j < len(ms); j++ {
if ms[j].Obj().Exported() {
c.graph.markUsedBy(field, typ)
break
}
}
}
}
func (c *Checker) processDefs(pkg *loader.PackageInfo) {
for _, obj := range pkg.Defs {
if obj == nil {
continue
}
c.graph.getNode(obj)
if obj, ok := obj.(*types.TypeName); ok {
c.graph.markUsedBy(obj.Type().Underlying(), obj.Type())
c.graph.markUsedBy(obj.Type(), obj) // TODO is this needed?
c.graph.markUsedBy(obj, obj.Type())
// We mark all exported fields as used. For normal
// operation, we have to. The user may use these fields
// without us knowing.
//
// TODO(dh): In whole-program mode, however, we mark them
// as used because of reflection (such as JSON
// marshaling). Strictly speaking, we would only need to
// mark them used if an instance of the type was
// accessible via an interface value.
if !c.WholeProgram || c.ConsiderReflection {
c.useExportedFields(obj.Type())
}
// TODO(dh): Traditionally we have not marked all exported
// methods as exported, even though they're strictly
// speaking accessible through reflection. We've done that
// because using methods just via reflection is rare, and
// not worth the false negatives. With the new -reflect
// flag, however, we should reconsider that choice.
if !c.WholeProgram {
c.useExportedMethods(obj.Type())
}
}
switch obj := obj.(type) {
case *types.Var, *types.Const, *types.Func, *types.TypeName:
if obj.Exported() {
// Exported variables and constants use their types,
// even if there's no expression using them in the
// checked program.
//
// Also operates on funcs and type names, but that's
// irrelevant/redundant.
c.graph.markUsedBy(obj.Type(), obj)
}
if obj.Name() == "_" {
node := c.graph.getNode(obj)
node.quiet = true
scope := c.topmostScope(pkg.Pkg.Scope().Innermost(obj.Pos()), pkg.Pkg)
if scope == pkg.Pkg.Scope() {
c.graph.roots = append(c.graph.roots, node)
} else {
c.graph.markUsedBy(obj, scope)
}
} else {
// Variables declared in functions are used. This is
// done so that arguments and return parameters are
// always marked as used.
if _, ok := obj.(*types.Var); ok {
if obj.Parent() != obj.Pkg().Scope() && obj.Parent() != nil {
c.graph.markUsedBy(obj, c.topmostScope(obj.Parent(), obj.Pkg()))
c.graph.markUsedBy(obj.Type(), obj)
}
}
}
}
if fn, ok := obj.(*types.Func); ok {
// A function uses its signature
c.graph.markUsedBy(fn, fn.Type())
// A function uses its return types
sig := fn.Type().(*types.Signature)
res := sig.Results()
n := res.Len()
for i := 0; i < n; i++ {
c.graph.markUsedBy(res.At(i).Type(), fn)
}
}
if obj, ok := obj.(interface {
Scope() *types.Scope
Pkg() *types.Package
}); ok {
scope := obj.Scope()
c.graph.markUsedBy(c.topmostScope(scope, obj.Pkg()), obj)
}
if c.isRoot(obj) {
node := c.graph.getNode(obj)
c.graph.roots = append(c.graph.roots, node)
if obj, ok := obj.(*types.PkgName); ok {
scope := obj.Pkg().Scope()
c.graph.markUsedBy(scope, obj)
}
}
}
}
func (c *Checker) processUses(pkg *loader.PackageInfo) {
for ident, usedObj := range pkg.Uses {
if _, ok := usedObj.(*types.PkgName); ok {
continue
}
pos := ident.Pos()
scope := pkg.Pkg.Scope().Innermost(pos)
scope = c.topmostScope(scope, pkg.Pkg)
if scope != pkg.Pkg.Scope() {
c.graph.markUsedBy(usedObj, scope)
}
switch usedObj.(type) {
case *types.Var, *types.Const:
c.graph.markUsedBy(usedObj.Type(), usedObj)
}
}
}
func (c *Checker) findExportedInterfaces() {
c.interfaces = []*types.Interface{types.Universe.Lookup("error").Type().(*types.Named).Underlying().(*types.Interface)}
var pkgs []*loader.PackageInfo
if c.WholeProgram {
for _, pkg := range c.lprog.AllPackages {
pkgs = append(pkgs, pkg)
}
} else {
pkgs = c.lprog.InitialPackages()
}
for _, pkg := range pkgs {
for _, tv := range pkg.Types {
iface, ok := tv.Type.(*types.Interface)
if !ok {
continue
}
if iface.NumMethods() == 0 {
continue
}
c.interfaces = append(c.interfaces, iface)
}
}
}
func (c *Checker) processTypes(pkg *loader.PackageInfo) {
named := map[*types.Named]*types.Pointer{}
var interfaces []*types.Interface
for _, tv := range pkg.Types {
if typ, ok := tv.Type.(interface {
Elem() types.Type
}); ok {
c.graph.markUsedBy(typ.Elem(), typ)
}
switch obj := tv.Type.(type) {
case *types.Named:
named[obj] = types.NewPointer(obj)
c.graph.markUsedBy(obj, obj.Underlying())
c.graph.markUsedBy(obj.Underlying(), obj)
case *types.Interface:
if obj.NumMethods() > 0 {
interfaces = append(interfaces, obj)
}
case *types.Struct:
c.useNoCopyFields(obj)
if pkg.Pkg.Name() != "main" && !c.WholeProgram {
c.useExportedFields(obj)
}
}
}
// Pretend that all types are meant to implement as many
// interfaces as possible.
//
// TODO(dh): For normal operations, that's the best we can do, as
// we have no idea what external users will do with our types. In
// whole-program mode, we could be more conservative, in two ways:
// 1) Only consider interfaces if a type has been assigned to one
// 2) Use SSA and flow analysis and determine the exact set of
// interfaces that is relevant.
fn := func(iface *types.Interface) {
for obj, objPtr := range named {
if !types.Implements(obj, iface) && !types.Implements(objPtr, iface) {
continue
}
ifaceMethods := make(map[string]struct{}, iface.NumMethods())
n := iface.NumMethods()
for i := 0; i < n; i++ {
meth := iface.Method(i)
ifaceMethods[meth.Name()] = struct{}{}
}
for _, obj := range []types.Type{obj, objPtr} {
ms := c.msCache.MethodSet(obj)
n := ms.Len()
for i := 0; i < n; i++ {
sel := ms.At(i)
meth := sel.Obj().(*types.Func)
_, found := ifaceMethods[meth.Name()]
if !found {
continue
}
c.graph.markUsedBy(meth.Type().(*types.Signature).Recv().Type(), obj) // embedded receiver
if len(sel.Index()) > 1 {
f := getField(obj, sel.Index()[0])
c.graph.markUsedBy(f, obj) // embedded receiver
}
c.graph.markUsedBy(meth, obj)
}
}
}
}
for _, iface := range interfaces {
fn(iface)
}
for _, iface := range c.interfaces {
fn(iface)
}
}
func (c *Checker) processSelections(pkg *loader.PackageInfo) {
fn := func(expr *ast.SelectorExpr, sel *types.Selection, offset int) {
scope := pkg.Pkg.Scope().Innermost(expr.Pos())
c.graph.markUsedBy(expr.X, c.topmostScope(scope, pkg.Pkg))
c.graph.markUsedBy(sel.Obj(), expr.X)
if len(sel.Index()) > 1 {
typ := sel.Recv()
indices := sel.Index()
for _, idx := range indices[:len(indices)-offset] {
obj := getField(typ, idx)
typ = obj.Type()
c.graph.markUsedBy(obj, expr.X)
}
}
}
for expr, sel := range pkg.Selections {
switch sel.Kind() {
case types.FieldVal:
fn(expr, sel, 0)
case types.MethodVal:
fn(expr, sel, 1)
}
}
}
func dereferenceType(typ types.Type) types.Type {
if typ, ok := typ.(*types.Pointer); ok {
return typ.Elem()
}
return typ
}
// processConversion marks fields as used if they're part of a type conversion.
func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) {
if node, ok := node.(*ast.CallExpr); ok {
callTyp := pkg.TypeOf(node.Fun)
var typDst *types.Struct
var ok bool
switch typ := callTyp.(type) {
case *types.Named:
typDst, ok = typ.Underlying().(*types.Struct)
case *types.Pointer:
typDst, ok = typ.Elem().Underlying().(*types.Struct)
default:
return
}
if !ok {
return
}
if typ, ok := pkg.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer {
// This is an unsafe conversion. Assume that all the
// fields are relevant (they are, because of memory
// layout)
n := typDst.NumFields()
for i := 0; i < n; i++ {
c.graph.markUsedBy(typDst.Field(i), typDst)
}
return
}
typSrc, ok := dereferenceType(pkg.TypeOf(node.Args[0])).Underlying().(*types.Struct)
if !ok {
return
}
// When we convert from type t1 to t2, were t1 and t2 are
// structs, all fields are relevant, as otherwise the
// conversion would fail.
//
// We mark t2's fields as used by t1's fields, and vice
// versa. That way, if no code actually refers to a field
// in either type, it's still correctly marked as unused.
// If a field is used in either struct, it's implicitly
// relevant in the other one, too.
//
// It works in a similar way for conversions between types
// of two packages, only that the extra information in the
// graph is redundant unless we're in whole program mode.
n := typDst.NumFields()
for i := 0; i < n; i++ {
fDst := typDst.Field(i)
fSrc := typSrc.Field(i)
c.graph.markUsedBy(fDst, fSrc)
c.graph.markUsedBy(fSrc, fDst)
}
}
}
// processCompositeLiteral marks fields as used if the struct is used
// in a composite literal.
func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node) {
// XXX how does this actually work? wouldn't it match t{}?
if node, ok := node.(*ast.CompositeLit); ok {
typ := pkg.TypeOf(node)
if _, ok := typ.(*types.Named); ok {
typ = typ.Underlying()
}
if _, ok := typ.(*types.Struct); !ok {
return
}
if isBasicStruct(node.Elts) {
c.markFields(typ)
}
}
}
// processCgoExported marks functions as used if they're being
// exported to cgo.
func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) {
if node, ok := node.(*ast.FuncDecl); ok {
if node.Doc == nil {
return
}
for _, cmt := range node.Doc.List {
if !strings.HasPrefix(cmt.Text, "//go:cgo_export_") {
return
}
obj := pkg.ObjectOf(node.Name)
c.graph.roots = append(c.graph.roots, c.graph.getNode(obj))
}
}
}
func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.Node) {
if decl, ok := node.(*ast.GenDecl); ok {
for _, spec := range decl.Specs {
spec, ok := spec.(*ast.ValueSpec)
if !ok {
continue
}
for i, name := range spec.Names {
if i >= len(spec.Values) {
break
}
value := spec.Values[i]
fn := func(node ast.Node) bool {
if node3, ok := node.(*ast.Ident); ok {
obj := pkg.ObjectOf(node3)
if _, ok := obj.(*types.PkgName); ok {
return true
}
c.graph.markUsedBy(obj, pkg.ObjectOf(name))
}
return true
}
ast.Inspect(value, fn)
}
}
}
}
func (c *Checker) processArrayConstants(pkg *loader.PackageInfo, node ast.Node) {
if decl, ok := node.(*ast.ArrayType); ok {
ident, ok := decl.Len.(*ast.Ident)
if !ok {
return
}
c.graph.markUsedBy(pkg.ObjectOf(ident), pkg.TypeOf(decl))
}
}
func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node ast.Node) {
call, ok := node.(*ast.CallExpr)
if !ok {
return
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return
}
if !IsType(pkg.TypeOf(sel.X), "*net/rpc.Server") {
x, ok := sel.X.(*ast.Ident)
if !ok {
return
}
pkgname, ok := pkg.ObjectOf(x).(*types.PkgName)
if !ok {
return
}
if pkgname.Imported().Path() != "net/rpc" {
return
}
}
var arg ast.Expr
switch sel.Sel.Name {
case "Register":
if len(call.Args) != 1 {
return
}
arg = call.Args[0]
case "RegisterName":
if len(call.Args) != 2 {
return
}
arg = call.Args[1]
}
typ := pkg.TypeOf(arg)
ms := types.NewMethodSet(typ)
for i := 0; i < ms.Len(); i++ {
c.graph.markUsedBy(ms.At(i).Obj(), typ)
}
}
func (c *Checker) processAST(pkg *loader.PackageInfo) {
fn := func(node ast.Node) bool {
c.processConversion(pkg, node)
c.processKnownReflectMethodCallers(pkg, node)
c.processCompositeLiteral(pkg, node)
c.processCgoExported(pkg, node)
c.processVariableDeclaration(pkg, node)
c.processArrayConstants(pkg, node)
return true
}
for _, file := range pkg.Files {
ast.Inspect(file, fn)
}
}
func isBasicStruct(elts []ast.Expr) bool {
for _, elt := range elts {
if _, ok := elt.(*ast.KeyValueExpr); !ok {
return true
}
}
return false
}
func isPkgScope(obj types.Object) bool {
return obj.Parent() == obj.Pkg().Scope()
}
func isMain(obj types.Object) bool {
if obj.Pkg().Name() != "main" {
return false
}
if obj.Name() != "main" {
return false
}
if !isPkgScope(obj) {
return false
}
if !isFunction(obj) {
return false
}
if isMethod(obj) {
return false
}
return true
}
func isFunction(obj types.Object) bool {
_, ok := obj.(*types.Func)
return ok
}
func isMethod(obj types.Object) bool {
if !isFunction(obj) {
return false
}
return obj.(*types.Func).Type().(*types.Signature).Recv() != nil
}
func isVariable(obj types.Object) bool {
_, ok := obj.(*types.Var)
return ok
}
func isConstant(obj types.Object) bool {
_, ok := obj.(*types.Const)
return ok
}
func isType(obj types.Object) bool {
_, ok := obj.(*types.TypeName)
return ok
}
func isField(obj types.Object) bool {
if obj, ok := obj.(*types.Var); ok && obj.IsField() {
return true
}
return false
}
func (c *Checker) checkFlags(v interface{}) bool {
obj, ok := v.(types.Object)
if !ok {
return false
}
if isFunction(obj) && !c.checkFunctions() {
return false
}
if isVariable(obj) && !c.checkVariables() {
return false
}
if isConstant(obj) && !c.checkConstants() {
return false
}
if isType(obj) && !c.checkTypes() {
return false
}
if isField(obj) && !c.checkFields() {
return false
}
return true
}
func (c *Checker) isRoot(obj types.Object) bool {
// - in local mode, main, init, tests, and non-test, non-main exported are roots
// - in global mode (not yet implemented), main, init and tests are roots
if _, ok := obj.(*types.PkgName); ok {
return true
}
if isMain(obj) || (isFunction(obj) && !isMethod(obj) && obj.Name() == "init") {
return true
}
if obj.Exported() {
f := c.lprog.Fset.Position(obj.Pos()).Filename
if strings.HasSuffix(f, "_test.go") {
return strings.HasPrefix(obj.Name(), "Test") ||
strings.HasPrefix(obj.Name(), "Benchmark") ||
strings.HasPrefix(obj.Name(), "Example")
}
// Package-level are used, except in package main
if isPkgScope(obj) && obj.Pkg().Name() != "main" && !c.WholeProgram {
return true
}
}
return false
}
func markNodesUsed(nodes map[*graphNode]struct{}) {
for node := range nodes {
wasUsed := node.used
node.used = true
if !wasUsed {
markNodesUsed(node.uses)
}
}
}
func (c *Checker) markNodesQuiet() {
for _, node := range c.graph.nodes {
if node.used {
continue
}
if obj, ok := node.obj.(types.Object); ok && !c.checkFlags(obj) {
node.quiet = true
continue
}
c.markObjQuiet(node.obj)
}
}
func (c *Checker) markObjQuiet(obj interface{}) {
switch obj := obj.(type) {
case *types.Named:
n := obj.NumMethods()
for i := 0; i < n; i++ {
meth := obj.Method(i)
node := c.graph.getNode(meth)
node.quiet = true
c.markObjQuiet(meth.Scope())
}
case *types.Struct:
n := obj.NumFields()
for i := 0; i < n; i++ {
field := obj.Field(i)
c.graph.nodes[field].quiet = true
}
case *types.Func:
c.markObjQuiet(obj.Scope())
case *types.Scope:
if obj == nil {
return
}
if obj.Parent() == types.Universe {
return
}
for _, name := range obj.Names() {
v := obj.Lookup(name)
if n, ok := c.graph.nodes[v]; ok {
n.quiet = true
}
}
n := obj.NumChildren()
for i := 0; i < n; i++ {
c.markObjQuiet(obj.Child(i))
}
}
}
func getField(typ types.Type, idx int) *types.Var {
switch obj := typ.(type) {
case *types.Pointer:
return getField(obj.Elem(), idx)
case *types.Named:
switch v := obj.Underlying().(type) {
case *types.Struct:
return v.Field(idx)
case *types.Pointer:
return getField(v.Elem(), idx)
default:
panic(fmt.Sprintf("unexpected type %s", typ))
}
case *types.Struct:
return obj.Field(idx)
}
return nil
}
func (c *Checker) topmostScope(scope *types.Scope, pkg *types.Package) (ret *types.Scope) {
if top, ok := c.topmostCache[scope]; ok {
return top
}
defer func() {
c.topmostCache[scope] = ret
}()
if scope == pkg.Scope() {
return scope
}
if scope.Parent().Parent() == pkg.Scope() {
return scope
}
return c.topmostScope(scope.Parent(), pkg)
}
func (c *Checker) printDebugGraph(w io.Writer) {
fmt.Fprintln(w, "digraph {")
fmt.Fprintln(w, "n0 [label = roots]")
for _, node := range c.graph.nodes {
s := fmt.Sprintf("%s (%T)", node.obj, node.obj)
s = strings.Replace(s, "\n", "", -1)
s = strings.Replace(s, `"`, "", -1)
fmt.Fprintf(w, `n%d [label = %q]`, node.n, s)
color := "black"
switch {
case node.used:
color = "green"
case node.quiet:
color = "orange"
case !c.checkFlags(node.obj):
color = "purple"
default:
color = "red"
}
fmt.Fprintf(w, "[color = %s]", color)
fmt.Fprintln(w)
}
for _, node1 := range c.graph.nodes {
for node2 := range node1.uses {
fmt.Fprintf(w, "n%d -> n%d\n", node1.n, node2.n)
}
}
for _, root := range c.graph.roots {
fmt.Fprintf(w, "n0 -> n%d\n", root.n)
}
fmt.Fprintln(w, "}")
}
func isGenerated(comment string) bool {
return strings.Contains(comment, "Code generated by") ||
strings.Contains(comment, "DO NOT EDIT")
}