func (f *useFinder) visitSelector(sel *ast.SelectorExpr) ast.Visitor {
	debugf("pos(sel): %v", f.position(sel.Pos()))
	debugf("sel.X: %v", sel.X)
	debugf("sel.Sel: %v", sel.Sel)
	rcvrIdent, ok := sel.X.(*ast.Ident)
	if !ok {
		debugf("nil rcvrIdent, type=%T", sel.X)
		return nil
	}
	rcvrObj := f.typeInfo.ObjectOf(rcvrIdent)
	switch rcvr := rcvrObj.(type) {
	case *types.PkgName:
		if rcvr.Imported() == f.target.pkg {
			selObj := f.typeInfo.ObjectOf(sel.Sel)
			discovered := f.target.discover(selObj)
			debugf("discovered: %v", discovered)
			return f
		} else {
			debugf("wrong pkg")
			return nil
		}
	case *types.Var:
		debugf("rcvrObj is a var")
		found := f.target.discover(rcvrObj)
		if !found {
		}
	default:
		debugf("unexpected rcvrObj.(type): %T", rcvrObj)
	}
	selObj := f.typeInfo.ObjectOf(sel.Sel)
	debugf("sel obj=%v", selObj)
	f.target.discover(selObj)
	return nil
}
visitSelector visits a selector expression. This is one of the main chances
we have to identify uses of the query targets.
package main
import (
	"go/ast"
	"go/token"
	"go/types"
	"strconv"
	"strings"
	"golang.org/x/tools/go/loader"
)
type queryTarget struct {
	all     []types.Object
	pkg     *types.Package
	unfound map[types.Object]bool
}
func newTarget(objs []types.Object) queryTarget {
	tgt := queryTarget{
		all:     objs,
		pkg:     objs[0].Pkg(),
		unfound: make(map[types.Object]bool, len(objs)),
	}
	for _, o := range objs {
		tgt.unfound[o] = true
	}
	return tgt
}
func (t queryTarget) allFound() bool {
	return len(t.unfound) == 0
}
func (t queryTarget) isTarget(o types.Object) bool {
	for _, targetO := range t.all {
		if o == targetO {
			return true
		}
	}
	return false
}
func (t queryTarget) discover(o types.Object) bool {
	if o == nil {
		return false
	}
	for lookingFor := range t.unfound {
		debugf("(%v) == (%v): %t", o, lookingFor, o == lookingFor)
		if o == lookingFor {
			debugf("found %v", o.Name())
			delete(t.unfound, o)
			return true
		}
		if tn, ok := lookingFor.(*types.TypeName); ok {
			debugf("type name: (%v) (%T) == (%v): %t", o.Type(), o.Type(), tn.Type(), o.Type() == tn.Type())
			if tn.Type() == o.Type() {
				delete(t.unfound, lookingFor)
				return true
			}
			ot := o.Type()
			for true {
				ptr, ok := ot.(*types.Pointer)
				if !ok {
					break
				}
				ot = ptr.Elem()
				if tn.Type() == ot {
					delete(t.unfound, lookingFor)
					return true
				}
			}
			if _, ok := o.Type().(*types.Pointer); ok {
			}
		}
	}
	return false
}
type useFinder struct {
	target queryTarget
	queryPkgLocalName string
	skipVendor bool
	typeInfo types.Info
	position func(token.Pos) token.Position
}
func findUnused(prog *loader.Program, targets []types.Object) (unusedObjs []types.Object) {
	finder := &useFinder{
		target:     newTarget(targets),
		skipVendor: true,
		position:   prog.Fset.Position,
	}
	for _, pkgInfo := range prog.AllPackages {
		if pkgInfo.Pkg == finder.target.pkg {
			continue
		}
		if !strings.Contains(pkgInfo.Pkg.Path(), ".") {
			continue
		}
		if finder.skipVendor && strings.Contains(pkgInfo.Pkg.Path(), "vendor") {
			continue
		}
		debugf("checking in %v for %d targets", pkgInfo, len(targets))
		finder.typeInfo = pkgInfo.Info
		for _, file := range pkgInfo.Files {
			ast.Walk(finder, file)
			debugf("after walking %v: %v", file, finder.target.unfound)
			if finder.target.allFound() {
				return
			}
		}
		if finder.target.allFound() {
			return
		}
	}
	for o := range finder.target.unfound {
		unusedObjs = append(unusedObjs, o)
	}
	return unusedObjs
}
func (f *useFinder) Visit(node ast.Node) ast.Visitor {
	if node != nil {
		debugf("node=%v\ttype=%T", f.position(node.Pos()), node)
	}
	switch v := node.(type) {
	case *ast.File:
		return f.visitFile(v)
	case nil:
		return nil
	case *ast.ImportSpec:
		return f.visitImportSpec(v)
	case *ast.SelectorExpr:
		return f.visitSelector(v)
	case *ast.Ident:
		return f.visitIdent(v)
	case *ast.Field, *ast.FieldList:
		return f
	case *ast.Comment, *ast.CommentGroup, *ast.BadExpr:
		return nil
	default:
		return f
	}
}
func (f *useFinder) visitFile(file *ast.File) ast.Visitor {
	f.queryPkgLocalName = ""
	for _, importSpec := range file.Imports {
		path, _ := strconv.Unquote(importSpec.Path.Value)
		if path == f.target.pkg.Path() {
			debugf("file %v imports pkg %v, entering", f.position(file.Name.Pos()), f.target.pkg.Path())
			return f
		}
	}
	return nil
}
func (f *useFinder) visitImportSpec(spec *ast.ImportSpec) ast.Visitor {
	path, _ := strconv.Unquote(spec.Path.Value)
	if path == f.target.pkg.Path() {
		debugf("found import of pkg %v", path)
		if spec.Name != nil {
			debugf("pkg alias used: %v", spec.Name.Name)
			f.queryPkgLocalName = spec.Name.Name
		} else {
			debugf("no pkg alias used: %v", f.target.pkg.Name())
			f.queryPkgLocalName = f.target.pkg.Name()
		}
	}
	return nil
}