func exprName(typ interface{}) *ast.Object {
	switch t := noParens(typ).(type) {
	case *ast.Ident:
		return t.Obj
	case *ast.Object:
		return t
	}
	return nil
}
If n represents a single identifier, exprName returns its object.
package types
import (
	"bytes"
	"container/list"
	"fmt"
	"go/build"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"github.com/rogpeppe/godef/go/ast"
	"github.com/rogpeppe/godef/go/parser"
	"github.com/rogpeppe/godef/go/printer"
	"github.com/rogpeppe/godef/go/scanner"
	"github.com/rogpeppe/godef/go/token"
)
type Type struct {
	Node ast.Node
	Kind ast.ObjKind
	Pkg string
	ctxt *exprTypeContext
}
type MultiValue struct {
	Types []ast.Expr
}
func (MultiValue) Pos() token.Pos {
	return token.NoPos
}
func (MultiValue) End() token.Pos {
	return token.NoPos
}
var badType = Type{Kind: ast.Bad}
var makeIdent = predecl("make")
var newIdent = predecl("new")
var falseIdent = predecl("false")
var trueIdent = predecl("true")
var iotaIdent = predecl("iota")
var boolIdent = predecl("bool")
var intIdent = predecl("int")
var floatIdent = predecl("float")
var stringIdent = predecl("string")
func predecl(name string) *ast.Ident {
	return &ast.Ident{Name: name, Obj: parser.Universe.Lookup(name)}
}
type Importer func(path string, srcDir string) *ast.Package
var FileSet = token.NewFileSet()
func DefaultImporter(path string, srcDir string) *ast.Package {
	bpkg, err := build.Default.Import(path, srcDir, 0)
	if err != nil {
		return nil
	}
	goFiles := make(map[string]bool)
	for _, f := range bpkg.GoFiles {
		goFiles[f] = true
	}
	for _, f := range bpkg.CgoFiles {
		goFiles[f] = true
	}
	shouldInclude := func(d os.FileInfo) bool {
		return goFiles[d.Name()]
	}
	pkgs, err := parser.ParseDir(FileSet, bpkg.Dir, shouldInclude, 0, DefaultImportPathToName)
	if err != nil {
		if Debug {
			switch err := err.(type) {
			case scanner.ErrorList:
				for _, e := range err {
					debugp("\t%v: %s", e.Pos, e.Msg)
				}
			default:
				debugp("\terror parsing %s: %v", bpkg.Dir, err)
			}
		}
		return nil
	}
	if pkg := pkgs[bpkg.Name]; pkg != nil {
		return pkg
	}
	if Debug {
		debugp("package not found by ParseDir!")
	}
	return nil
}
func DefaultImportPathToName(path, srcDir string) (string, error) {
	if path == "C" {
		return "C", nil
	}
	pkg, err := build.Default.Import(path, srcDir, 0)
	return pkg.Name, err
}
func isGoFile(d os.FileInfo) bool {
	return strings.HasSuffix(d.Name(), ".go") &&
		!strings.HasSuffix(d.Name(), "_test.go") &&
		!strings.HasPrefix(d.Name(), ".") &&
		goodOSArch(d.Name())
}
var Debug = false
func (t Type) String() string {
	return fmt.Sprintf("Type{%v %q %T %v}", t.Kind, t.Pkg, t.Node, pretty{t.Node})
}
var Panic = true
func (t Type) Member(name string) *ast.Object {
	debugp("member %v '%s' {", t, name)
	if t.Pkg != "" && !ast.IsExported(name) {
		return nil
	}
	c := make(chan *ast.Object)
	go func() {
		if !Panic {
			defer func() {
				if err := recover(); err != nil {
					log.Printf("panic: %v", err)
					c <- nil
				}
			}()
		}
		doMembers(t, name, func(obj *ast.Object) {
			if obj.Name == name {
				c <- obj
				runtime.Goexit()
			}
		})
		c <- nil
	}()
	m := <-c
	debugp("} -> %v", m)
	return m
}
func (t Type) Iter() <-chan *ast.Object {
	c := make(chan *ast.Object)
	go func() {
		internal := t.Pkg == ""
		doMembers(t, "", func(obj *ast.Object) {
			if internal || ast.IsExported(obj.Name) {
				c <- obj
			}
		})
		close(c)
	}()
	return c
}
func ExprType(e ast.Expr, importer Importer, fs *token.FileSet) (obj *ast.Object, typ Type) {
	ctxt := &exprTypeContext{
		importer: importer,
		fileSet:  fs,
	}
	return ctxt.exprType(e, false, "")
}
type exprTypeContext struct {
	importer Importer
	fileSet  *token.FileSet
}
func (ctxt *exprTypeContext) exprType(n ast.Node, expectTuple bool, pkg string) (xobj *ast.Object, typ Type) {
	debugp("exprType tuple:%v pkg:%s %T %v [", expectTuple, pkg, n, pretty{n})
	defer func() {
		debugp("] -> %p, %v", xobj, typ)
	}()
	switch n := n.(type) {
	case nil:
	case *ast.Ident:
		obj := n.Obj
		if obj == nil || obj.Kind == ast.Bad {
			break
		}
		if obj.Kind == ast.Typ && !isTypeAlias(obj) {
			if parser.Universe.Lookup(obj.Name) == obj {
				pkg = ""
			}
			return obj, ctxt.newType(n, obj.Kind, pkg)
		}
		expr, typ := splitDecl(obj, n)
		switch {
		case typ != nil:
			_, t := ctxt.exprType(typ, false, pkg)
			if t.Kind != ast.Bad {
				t.Kind = obj.Kind
			}
			return obj, t
		case expr != nil:
			_, t := ctxt.exprType(expr, false, pkg)
			if t.Kind == ast.Typ {
				debugp("expected value, got type %v", t)
				t = badType
			}
			return obj, t
		default:
			switch n.Obj {
			case falseIdent.Obj, trueIdent.Obj:
				return obj, ctxt.newType(boolIdent, ast.Con, "")
			case iotaIdent.Obj:
				return obj, ctxt.newType(intIdent, ast.Con, "")
			default:
				return obj, Type{}
			}
		}
	case *ast.LabeledStmt:
		return n.Label.Obj, ctxt.newType(n, ast.Lbl, pkg)
	case *ast.ImportSpec:
		return nil, ctxt.newType(n, ast.Pkg, "")
	case *ast.ParenExpr:
		return ctxt.exprType(n.X, expectTuple, pkg)
	case *ast.CompositeLit:
		return nil, ctxt.certify(n.Type, ast.Var, pkg)
	case *ast.FuncLit:
		return nil, ctxt.certify(n.Type, ast.Var, pkg)
	case *ast.SelectorExpr:
		_, t := ctxt.exprType(n.X, false, pkg)
		if t.Kind == ast.Bad {
			break
		}
		obj := t.Member(n.Sel.Name)
		if obj == nil {
			return nil, badType
		}
		if t.Kind == ast.Pkg {
			eobj, et := ctxt.exprType(&ast.Ident{Name: obj.Name, Obj: obj}, false, t.Pkg)
			et.Pkg = litToString(t.Node.(*ast.ImportSpec).Path)
			return eobj, et
		}
		if fd, ismethod := obj.Decl.(*ast.FuncDecl); ismethod {
			if t.Kind == ast.Typ {
				return obj, ctxt.certify(methodExpr(fd), ast.Fun, t.Pkg)
			}
			return obj, ctxt.certify(fd.Type, ast.Fun, t.Pkg)
		} else if obj.Kind == ast.Typ {
			return obj, ctxt.certify(&ast.Ident{Name: obj.Name, Obj: obj}, ast.Typ, t.Pkg)
		}
		_, typ := splitDecl(obj, nil)
		return obj, ctxt.certify(typ, obj.Kind, t.Pkg)
	case *ast.FuncDecl:
		return nil, ctxt.certify(methodExpr(n), ast.Fun, pkg)
	case *ast.IndexExpr:
		_, t0 := ctxt.exprType(n.X, false, pkg)
		t := t0.Underlying(true)
		switch n := t.Node.(type) {
		case *ast.ArrayType:
			return nil, ctxt.certify(n.Elt, ast.Var, t.Pkg)
		case *ast.MapType:
			t := ctxt.certify(n.Value, ast.Var, t.Pkg)
			if expectTuple && t.Kind != badType.Kind {
				return nil, ctxt.newType(MultiValue{[]ast.Expr{t.Node.(ast.Expr), predecl("bool")}}, ast.Var, t.Pkg)
			}
			return nil, t
		}
	case *ast.SliceExpr:
		_, typ := ctxt.exprType(n.X, false, pkg)
		return nil, typ
	case *ast.CallExpr:
		switch exprName(n.Fun) {
		case makeIdent.Obj:
			if len(n.Args) > 0 {
				return nil, ctxt.certify(n.Args[0], ast.Var, pkg)
			}
		case newIdent.Obj:
			if len(n.Args) > 0 {
				t := ctxt.certify(n.Args[0], ast.Var, pkg)
				if t.Kind != ast.Bad {
					return nil, ctxt.newType(&ast.StarExpr{n.Pos(), t.Node.(ast.Expr)}, ast.Var, t.Pkg)
				}
			}
		default:
			if _, fntype := ctxt.exprType(n.Fun, false, pkg); fntype.Kind != ast.Bad {
				if fntype.Kind == ast.Typ {
					fntype.Kind = ast.Var
					if len(n.Args) == 1 {
						_, argtype := ctxt.exprType(n.Args[0], false, pkg)
						if argtype.Kind == ast.Con {
							fntype.Kind = ast.Con
						}
					}
					return nil, fntype
				}
				t := fntype.Underlying(true)
				if fn, ok := t.Node.(*ast.FuncType); ok {
					return nil, ctxt.certify(fields2type(fn.Results), ast.Var, t.Pkg)
				}
			}
		}
	case *ast.StarExpr:
		if _, t := ctxt.exprType(n.X, false, pkg); t.Kind != ast.Bad {
			if t.Kind == ast.Typ {
				return nil, ctxt.newType(&ast.StarExpr{n.Pos(), t.Node.(ast.Expr)}, ast.Typ, t.Pkg)
			}
			if n, ok := t.Node.(*ast.StarExpr); ok {
				return nil, ctxt.certify(n.X, ast.Var, t.Pkg)
			}
		}
	case *ast.TypeAssertExpr:
		t := ctxt.certify(n.Type, ast.Var, pkg)
		if expectTuple && t.Kind != ast.Bad {
			return nil, ctxt.newType(MultiValue{[]ast.Expr{t.Node.(ast.Expr), predecl("bool")}}, ast.Var, t.Pkg)
		}
		return nil, t
	case *ast.UnaryExpr:
		if _, t := ctxt.exprType(n.X, false, pkg); t.Kind != ast.Bad {
			u := t.Underlying(true)
			switch n.Op {
			case token.ARROW:
				if ct, ok := u.Node.(*ast.ChanType); ok {
					t := ctxt.certify(ct.Value, ast.Var, u.Pkg)
					if expectTuple && t.Kind != ast.Bad {
						return nil, ctxt.newType(MultiValue{[]ast.Expr{t.Node.(ast.Expr), predecl("bool")}}, ast.Var, t.Pkg)
					}
					return nil, ctxt.certify(ct.Value, ast.Var, u.Pkg)
				}
			case token.RANGE:
				switch n := u.Node.(type) {
				case *ast.ArrayType:
					if expectTuple {
						return nil, ctxt.newType(MultiValue{[]ast.Expr{predecl("int"), n.Elt}}, ast.Var, u.Pkg)
					}
					return nil, ctxt.newType(predecl("bool"), ast.Var, "")
				case *ast.MapType:
					if expectTuple {
						return nil, ctxt.newType(MultiValue{[]ast.Expr{n.Key, n.Value}}, ast.Var, u.Pkg)
					}
					return nil, ctxt.certify(n.Key, ast.Var, u.Pkg)
				case *ast.ChanType:
					return nil, ctxt.certify(n.Value, ast.Var, u.Pkg)
				}
			case token.AND:
				if t.Kind == ast.Var {
					return nil, ctxt.newType(&ast.StarExpr{n.Pos(), t.Node.(ast.Expr)}, ast.Var, t.Pkg)
				}
			case token.NOT:
				return nil, ctxt.newType(predecl("bool"), t.Kind, "")
			default:
				return nil, t
			}
		}
	case *ast.BinaryExpr:
		switch n.Op {
		case token.LSS, token.EQL, token.GTR, token.NEQ, token.LEQ, token.GEQ, token.ARROW, token.LOR, token.LAND:
			_, t := ctxt.exprType(n.X, false, pkg)
			if t.Kind == ast.Con {
				_, t = ctxt.exprType(n.Y, false, pkg)
			}
			return nil, ctxt.newType(predecl("bool"), t.Kind, "")
		case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.AND_NOT, token.XOR:
			_, tx := ctxt.exprType(n.X, false, pkg)
			_, ty := ctxt.exprType(n.Y, false, pkg)
			switch {
			case tx.Kind == ast.Bad || ty.Kind == ast.Bad:
			case !isNamedType(tx):
				return nil, ty
			case !isNamedType(ty):
				return nil, tx
			}
			return nil, tx
		case token.SHL, token.SHR:
			_, typ := ctxt.exprType(n.X, false, pkg)
			return nil, typ
		}
	case *ast.BasicLit:
		var id *ast.Ident
		switch n.Kind {
		case token.STRING:
			id = stringIdent
		case token.INT, token.CHAR:
			id = intIdent
		case token.FLOAT:
			id = floatIdent
		default:
			debugp("unknown constant type %v", n.Kind)
		}
		if id != nil {
			return nil, ctxt.newType(id, ast.Con, "")
		}
	case *ast.StructType, *ast.ChanType, *ast.MapType, *ast.ArrayType, *ast.InterfaceType, *ast.FuncType:
		return nil, ctxt.newType(n.(ast.Node), ast.Typ, pkg)
	case MultiValue:
		return nil, ctxt.newType(n, ast.Typ, pkg)
	case *exprIndex:
		_, t := ctxt.exprType(n.x, true, pkg)
		if t.Kind != ast.Bad {
			if ts, ok := t.Node.(MultiValue); ok {
				if n.i < len(ts.Types) {
					return nil, ctxt.certify(ts.Types[n.i], ast.Var, t.Pkg)
				}
			}
		}
	case *ast.Ellipsis:
		t := ctxt.certify(n.Elt, ast.Var, pkg)
		if t.Kind != ast.Bad {
			return nil, ctxt.newType(&ast.ArrayType{n.Pos(), nil, t.Node.(ast.Expr)}, ast.Var, t.Pkg)
		}
	default:
		panic(fmt.Sprintf("unknown type %T", n))
	}
	return nil, badType
}
func (ctxt *exprTypeContext) newType(n ast.Node, kind ast.ObjKind, pkg string) Type {
	return Type{
		Node: n,
		Kind: kind,
		Pkg:  pkg,
		ctxt: ctxt,
	}
}
func litToString(lit *ast.BasicLit) (v string) {
	if lit.Kind != token.STRING {
		panic("expected string")
	}
	v, err := strconv.Unquote(string(lit.Value))
	if err != nil {
		panic("cannot unquote")
	}
	return v
}
func doMembers(typ Type, name string, fn func(*ast.Object)) {
	switch t := typ.Node.(type) {
	case nil:
		return
	case *ast.ImportSpec:
		path := litToString(t.Path)
		pos := typ.ctxt.fileSet.Position(typ.Node.Pos())
		if pkg := typ.ctxt.importer(path, filepath.Dir(pos.Filename)); pkg != nil {
			doScope(pkg.Scope, name, fn, path)
		}
		return
	}
	q := list.New()
	q.PushBack(typ)
	for e := q.Front(); e != nil; e = q.Front() {
		doTypeMembers(e.Value.(Type), name, fn, q)
		q.Remove(e)
	}
}
func doTypeMembers(t Type, name string, fn func(*ast.Object), q *list.List) {
	if u, ok := t.Node.(*ast.StarExpr); ok {
		_, t = t.ctxt.exprType(u.X, false, t.Pkg)
	}
	if id, _ := t.Node.(*ast.Ident); id != nil && id.Obj != nil {
		if scope, ok := id.Obj.Type.(*ast.Scope); ok {
			doScope(scope, name, fn, t.Pkg)
		}
	}
	u := t.Underlying(true)
	switch n := u.Node.(type) {
	case *ast.StructType:
		t.ctxt.doStructMembers(n.Fields.List, t.Pkg, fn, q)
	case *ast.InterfaceType:
		t.ctxt.doInterfaceMembers(n.Methods.List, t.Pkg, fn)
	}
}
func (ctxt *exprTypeContext) doInterfaceMembers(fields []*ast.Field, pkg string, fn func(*ast.Object)) {
	for _, f := range fields {
		if len(f.Names) > 0 {
			for _, fname := range f.Names {
				fn(fname.Obj)
			}
		} else {
			_, typ := ctxt.exprType(f.Type, false, pkg)
			typ = typ.Underlying(true)
			switch n := typ.Node.(type) {
			case *ast.InterfaceType:
				ctxt.doInterfaceMembers(n.Methods.List, typ.Pkg, fn)
			default:
				debugp("unknown anon type in interface: %T\n", n)
			}
		}
	}
}
func (ctxt *exprTypeContext) doStructMembers(fields []*ast.Field, pkg string, fn func(*ast.Object), q *list.List) {
	for _, f := range fields {
		if len(f.Names) > 0 {
			for _, fname := range f.Names {
				fn(fname.Obj)
			}
		} else {
			m := unnamedFieldName(f.Type)
			fn(m.Obj)
			_, typeNode := splitDecl(m.Obj, nil)
			obj, typ := ctxt.exprType(typeNode, false, pkg)
			if typ.Kind == ast.Typ {
				q.PushBack(typ)
			} else {
				debugp("unnamed field kind %v (obj %v) not a type; %v", typ.Kind, obj, typ.Node)
			}
		}
	}
}
func unnamedFieldName(t ast.Node) *ast.Ident {
	switch t := t.(type) {
	case *ast.Ident:
		return t
	case *ast.SelectorExpr:
		return t.Sel
	case *ast.StarExpr:
		return unnamedFieldName(t.X)
	}
	panic("no name found for unnamed field")
}
func doScope(s *ast.Scope, name string, fn func(*ast.Object), pkg string) {
	if s == nil {
		return
	}
	if name != "" {
		if obj := s.Lookup(name); obj != nil {
			fn(obj)
		}
		return
	}
	for _, obj := range s.Objects {
		if obj.Kind == ast.Bad || pkg != "" && !ast.IsExported(obj.Name) {
			continue
		}
		fn(obj)
	}
}
func (typ Type) Underlying(all bool) Type {
	for {
		id, _ := typ.Node.(*ast.Ident)
		if id == nil || id.Obj == nil {
			break
		}
		_, typNode := splitDecl(id.Obj, id)
		_, t := typ.ctxt.exprType(typNode, false, typ.Pkg)
		if t.Kind != ast.Typ {
			return badType
		}
		typ.Node = t.Node
		typ.Pkg = t.Pkg
		if !all {
			break
		}
	}
	return typ
}
func noParens(typ interface{}) interface{} {
	for {
		if n, ok := typ.(*ast.ParenExpr); ok {
			typ = n.X
		} else {
			break
		}
	}
	return typ
}
func (ctxt *exprTypeContext) certify(typ ast.Node, kind ast.ObjKind, pkg string) Type {
	_, t := ctxt.exprType(typ, false, pkg)
	if t.Kind == ast.Typ {
		return ctxt.newType(t.Node, kind, t.Pkg)
	}
	return badType
}