go.tools/go/types: split Info.Objects map into Defs and Uses.
An identifier X in anonymous struct field struct{X} is both a
definition of a field (*Var) and reference to a type
(*TypeName). Now that we have split the map, we can capture
both of these aspects.
Interestingly, every client but one was going to extra effort
to iterate over just the uses or just the defs; this
simplifies them.
Also, fix two bug related to tagless switches:
- An entry was being recorded in the Object map for a piece of
synthetic syntax.
- The "true" identifier was being looked up in the current scope,
which allowed perverse users to locally redefine it. Now
we use the bool (not untyped boolean) constant true, per the
consequent clarification of the spec (issue 7404).
+ tests.
Fixes golang/go#7276
LGTM=gri
R=gri
CC=golang-codereviews
https://golang.org/cl/68270044
This commit is contained in:
parent
aaca3a4f95
commit
ced954c167
|
|
@ -204,7 +204,8 @@ func doPackageDir(directory string) {
|
|||
|
||||
type Package struct {
|
||||
path string
|
||||
idents map[*ast.Ident]types.Object
|
||||
defs map[*ast.Ident]types.Object
|
||||
uses map[*ast.Ident]types.Object
|
||||
types map[ast.Expr]types.TypeAndValue
|
||||
spans map[types.Object]Span
|
||||
files []*File
|
||||
|
|
|
|||
|
|
@ -41,9 +41,9 @@ func (f *File) checkNilFuncComparison(e *ast.BinaryExpr) {
|
|||
var obj types.Object
|
||||
switch v := e2.(type) {
|
||||
case *ast.Ident:
|
||||
obj = f.pkg.idents[v]
|
||||
obj = f.pkg.uses[v]
|
||||
case *ast.SelectorExpr:
|
||||
obj = f.pkg.idents[v.Sel]
|
||||
obj = f.pkg.uses[v.Sel]
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ func (f *File) checkShadowDecl(d *ast.GenDecl) {
|
|||
|
||||
// checkShadowing checks whether the identifier shadows an identifier in an outer scope.
|
||||
func (f *File) checkShadowing(ident *ast.Ident) {
|
||||
obj := f.pkg.idents[ident]
|
||||
obj := f.pkg.defs[ident]
|
||||
if obj == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ import (
|
|||
)
|
||||
|
||||
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
|
||||
pkg.idents = make(map[*ast.Ident]types.Object)
|
||||
pkg.defs = make(map[*ast.Ident]types.Object)
|
||||
pkg.uses = make(map[*ast.Ident]types.Object)
|
||||
pkg.spans = make(map[types.Object]Span)
|
||||
pkg.types = make(map[ast.Expr]types.TypeAndValue)
|
||||
// By providing a Config with our own error function, it will continue
|
||||
|
|
@ -23,13 +24,17 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
|
|||
Error: func(error) {},
|
||||
}
|
||||
info := &types.Info{
|
||||
Types: pkg.types,
|
||||
Objects: pkg.idents,
|
||||
Types: pkg.types,
|
||||
Defs: pkg.defs,
|
||||
Uses: pkg.uses,
|
||||
}
|
||||
typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
|
||||
pkg.typesPkg = typesPkg
|
||||
// update spans
|
||||
for id, obj := range pkg.idents {
|
||||
for id, obj := range pkg.defs {
|
||||
pkg.growSpan(id, obj)
|
||||
}
|
||||
for id, obj := range pkg.uses {
|
||||
pkg.growSpan(id, obj)
|
||||
}
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -683,7 +683,8 @@ func (imp *importer) createPackage(path string, files ...*ast.File) *PackageInfo
|
|||
Files: files,
|
||||
Info: types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Objects: make(map[*ast.Ident]types.Object),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
|
|
|
|||
|
|
@ -54,10 +54,18 @@ func (info *PackageInfo) ValueOf(e ast.Expr) exact.Value {
|
|||
}
|
||||
|
||||
// ObjectOf returns the typechecker object denoted by the specified id.
|
||||
//
|
||||
// If id is an anonymous struct field, the field (*types.Var) is
|
||||
// returned, not the type (*types.TypeName).
|
||||
//
|
||||
// Precondition: id belongs to the package's ASTs.
|
||||
//
|
||||
func (info *PackageInfo) ObjectOf(id *ast.Ident) types.Object {
|
||||
return info.Objects[id]
|
||||
obj, ok := info.Defs[id]
|
||||
if ok {
|
||||
return obj
|
||||
}
|
||||
return info.Uses[id]
|
||||
}
|
||||
|
||||
// IsType returns true iff expression e denotes a type.
|
||||
|
|
|
|||
|
|
@ -60,51 +60,49 @@ func TestObjValueLookup(t *testing.T) {
|
|||
mainPkg.SetDebugMode(true)
|
||||
mainPkg.Build()
|
||||
|
||||
// Gather all idents and objects in file.
|
||||
objs := make(map[types.Object]bool)
|
||||
var ids []*ast.Ident
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if id, ok := n.(*ast.Ident); ok {
|
||||
ids = append(ids, id)
|
||||
if obj := mainInfo.ObjectOf(id); obj != nil {
|
||||
objs[obj] = true
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
// Check invariants for func and const objects.
|
||||
for obj := range objs {
|
||||
var varIds []*ast.Ident
|
||||
var varObjs []*types.Var
|
||||
for id, obj := range mainInfo.Defs {
|
||||
// Check invariants for func and const objects.
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
checkFuncValue(t, prog, obj)
|
||||
|
||||
case *types.Const:
|
||||
checkConstValue(t, prog, obj)
|
||||
|
||||
case *types.Var:
|
||||
if id.Name == "_" {
|
||||
continue
|
||||
}
|
||||
varIds = append(varIds, id)
|
||||
varObjs = append(varObjs, obj)
|
||||
}
|
||||
}
|
||||
for id, obj := range mainInfo.Uses {
|
||||
if obj, ok := obj.(*types.Var); ok {
|
||||
varIds = append(varIds, id)
|
||||
varObjs = append(varObjs, obj)
|
||||
}
|
||||
}
|
||||
|
||||
// Check invariants for var objects.
|
||||
// The result varies based on the specific Ident.
|
||||
for _, id := range ids {
|
||||
if id.Name == "_" {
|
||||
for i, id := range varIds {
|
||||
obj := varObjs[i]
|
||||
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
||||
pos := prog.Fset.Position(id.Pos())
|
||||
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
|
||||
if exp == "" {
|
||||
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
|
||||
continue
|
||||
}
|
||||
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
|
||||
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
||||
pos := prog.Fset.Position(id.Pos())
|
||||
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
|
||||
if exp == "" {
|
||||
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
|
||||
continue
|
||||
}
|
||||
wantAddr := false
|
||||
if exp[0] == '&' {
|
||||
wantAddr = true
|
||||
exp = exp[1:]
|
||||
}
|
||||
checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
|
||||
wantAddr := false
|
||||
if exp[0] == '&' {
|
||||
wantAddr = true
|
||||
exp = exp[1:]
|
||||
}
|
||||
checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ func main() {
|
|||
// of (*J).method, so it doesn't help us locate the specific
|
||||
// ssa.Values here: a bound-method closure and a promotion
|
||||
// wrapper.
|
||||
_ = v11.method // v11::Const
|
||||
_ = (*struct{ J }).method
|
||||
_ = v11.method // v11::Const
|
||||
_ = (*struct{ J }).method // J::nil
|
||||
|
||||
// These vars are optimised away.
|
||||
if false {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
//
|
||||
// Name resolution maps each identifier (ast.Ident) in the program to the
|
||||
// language object (Object) it denotes.
|
||||
// Use Info.Objects, Info.Implicits for the results of name resolution.
|
||||
// Use Info.{Defs,Uses,Implicits} for the results of name resolution.
|
||||
//
|
||||
// Constant folding computes the exact constant value (exact.Value) for
|
||||
// every expression (ast.Expr) that is a compile-time constant.
|
||||
|
|
@ -130,8 +130,7 @@ type TypeAndValue struct {
|
|||
type Info struct {
|
||||
// Types maps expressions to their types, and for constant
|
||||
// expressions, their values.
|
||||
// Identifiers on the lhs of declarations are collected in
|
||||
// Objects, not Types.
|
||||
// Identifiers are collected in Defs and Uses, not Types.
|
||||
//
|
||||
// For an expression denoting a predeclared built-in function
|
||||
// the recorded signature is call-site specific. If the call
|
||||
|
|
@ -139,13 +138,24 @@ type Info struct {
|
|||
// specific signature. Otherwise, the recorded type is invalid.
|
||||
Types map[ast.Expr]TypeAndValue
|
||||
|
||||
// Objects maps identifiers to their corresponding objects (including
|
||||
// Defs maps identifiers to the objects they define (including
|
||||
// package names, dots "." of dot-imports, and blank "_" identifiers).
|
||||
// For identifiers that do not denote objects (e.g., the package name
|
||||
// in package clauses, blank identifiers on the lhs of assignments, or
|
||||
// symbolic variables t in t := x.(type) of type switch headers), the
|
||||
// corresponding objects are nil.
|
||||
Objects map[*ast.Ident]Object
|
||||
//
|
||||
// For an anonymous field, Defs returns the field *Var it defines.
|
||||
//
|
||||
// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
|
||||
Defs map[*ast.Ident]Object
|
||||
|
||||
// Uses maps identifiers to the objects they denote.
|
||||
//
|
||||
// For an anonymous field, Uses returns the *TypeName it denotes.
|
||||
//
|
||||
// Invariant: Uses[id].Pos() != id.Pos()
|
||||
Uses map[*ast.Ident]Object
|
||||
|
||||
// Implicits maps nodes to their implicitly declared objects, if any.
|
||||
// The following node and object types may appear:
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) Type {
|
|||
|
||||
// Don't evaluate lhs if it is the blank identifier.
|
||||
if ident != nil && ident.Name == "_" {
|
||||
check.recordObject(ident, nil)
|
||||
check.recordDef(ident, nil)
|
||||
if !check.assignment(x, nil) {
|
||||
assert(x.mode == invalid)
|
||||
x.typ = nil
|
||||
|
|
@ -285,7 +285,7 @@ func (check *checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) {
|
|||
newVars = append(newVars, obj)
|
||||
}
|
||||
if obj != nil {
|
||||
check.recordObject(ident, obj)
|
||||
check.recordDef(ident, obj)
|
||||
}
|
||||
} else {
|
||||
check.errorf(lhs.Pos(), "cannot declare %s", lhs)
|
||||
|
|
|
|||
|
|
@ -133,9 +133,9 @@ func testBuiltinSignature(t *testing.T, name, src0, want string) {
|
|||
}
|
||||
|
||||
var conf Config
|
||||
objects := make(map[*ast.Ident]Object)
|
||||
uses := make(map[*ast.Ident]Object)
|
||||
types := make(map[ast.Expr]TypeAndValue)
|
||||
_, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Objects: objects, Types: types})
|
||||
_, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Uses: uses, Types: types})
|
||||
if err != nil {
|
||||
t.Errorf("%s: %s", src0, err)
|
||||
return
|
||||
|
|
@ -172,7 +172,7 @@ func testBuiltinSignature(t *testing.T, name, src0, want string) {
|
|||
// identifier denoting the expected built-in
|
||||
switch p := fun.(type) {
|
||||
case *ast.Ident:
|
||||
obj := objects[p]
|
||||
obj := uses[p]
|
||||
if obj == nil {
|
||||
t.Errorf("%s: no object found for %s", src0, p)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
|
|||
// selector expressions.
|
||||
if ident, ok := e.X.(*ast.Ident); ok {
|
||||
if pkg, _ := check.scope.LookupParent(ident.Name).(*PkgName); pkg != nil {
|
||||
check.recordObject(ident, pkg)
|
||||
check.recordUse(ident, pkg)
|
||||
pkg.used = true
|
||||
exp := pkg.pkg.scope.Lookup(sel)
|
||||
if exp == nil {
|
||||
|
|
@ -268,7 +268,6 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
|
|||
}
|
||||
check.recordSelection(e, PackageObj, nil, exp, nil, false)
|
||||
// Simplified version of the code for *ast.Idents:
|
||||
// - imported packages use types.Scope and types.Objects
|
||||
// - imported objects are always fully initialized
|
||||
switch exp := exp.(type) {
|
||||
case *Const:
|
||||
|
|
|
|||
|
|
@ -258,9 +258,17 @@ func (check *checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
|
|||
}
|
||||
}
|
||||
|
||||
func (check *checker) recordObject(id *ast.Ident, obj Object) {
|
||||
func (check *checker) recordDef(id *ast.Ident, obj Object) {
|
||||
assert(id != nil)
|
||||
if m := check.Objects; m != nil {
|
||||
if m := check.Defs; m != nil {
|
||||
m[id] = obj
|
||||
}
|
||||
}
|
||||
|
||||
func (check *checker) recordUse(id *ast.Ident, obj Object) {
|
||||
assert(id != nil)
|
||||
assert(obj != nil)
|
||||
if m := check.Uses; m != nil {
|
||||
m[id] = obj
|
||||
}
|
||||
}
|
||||
|
|
@ -274,7 +282,7 @@ func (check *checker) recordImplicit(node ast.Node, obj Object) {
|
|||
|
||||
func (check *checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) {
|
||||
assert(obj != nil && (recv == nil || len(index) > 0))
|
||||
check.recordObject(x.Sel, obj)
|
||||
check.recordUse(x.Sel, obj)
|
||||
// TODO(gri) Should we also call recordTypeAndValue?
|
||||
if m := check.Selections; m != nil {
|
||||
m[x] = &Selection{kind, recv, obj, index, indirect}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
|
|||
return
|
||||
}
|
||||
if id != nil {
|
||||
check.recordObject(id, obj)
|
||||
check.recordDef(id, obj)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,7 +253,7 @@ func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, path []*
|
|||
continue
|
||||
}
|
||||
}
|
||||
check.recordObject(check.objMap[m].fdecl.Name, m)
|
||||
check.recordDef(check.objMap[m].fdecl.Name, m)
|
||||
check.objDecl(m, nil, nil)
|
||||
// Methods with blank _ names cannot be found.
|
||||
// Don't add them to the method list.
|
||||
|
|
|
|||
|
|
@ -1023,7 +1023,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
|
|||
continue
|
||||
}
|
||||
fld := fields[i]
|
||||
check.recordObject(key, fld)
|
||||
check.recordUse(key, fld)
|
||||
// 0 <= i < len(fields)
|
||||
if visited[i] {
|
||||
check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name)
|
||||
|
|
|
|||
|
|
@ -130,15 +130,15 @@ type T struct{} // receiver type after method declaration
|
|||
}
|
||||
|
||||
var conf Config
|
||||
objects := make(map[*ast.Ident]Object)
|
||||
_, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Objects: objects})
|
||||
defs := make(map[*ast.Ident]Object)
|
||||
_, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
m := f.Decls[0].(*ast.FuncDecl)
|
||||
res1 := objects[m.Name].(*Func).Type().(*Signature).Results().At(0)
|
||||
res2 := objects[m.Type.Results.List[0].Names[0]].(*Var)
|
||||
res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0)
|
||||
res2 := defs[m.Type.Results.List[0].Names[0]].(*Var)
|
||||
|
||||
if res1 != res2 {
|
||||
t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2)
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ func (check *checker) blockBranches(all *Scope, parent *block, lstmt *ast.Labele
|
|||
// ok to continue
|
||||
} else {
|
||||
b.insert(s)
|
||||
check.recordObject(s.Label, lbl)
|
||||
check.recordDef(s.Label, lbl)
|
||||
}
|
||||
// resolve matching forward jumps and remove them from fwdJumps
|
||||
i := 0
|
||||
|
|
@ -146,7 +146,7 @@ func (check *checker) blockBranches(all *Scope, parent *block, lstmt *ast.Labele
|
|||
if jmp.Label.Name == name {
|
||||
// match
|
||||
lbl.used = true
|
||||
check.recordObject(jmp.Label, lbl)
|
||||
check.recordUse(jmp.Label, lbl)
|
||||
if jumpsOverVarDecl(jmp) {
|
||||
check.errorf(
|
||||
jmp.Label.Pos(),
|
||||
|
|
@ -220,7 +220,7 @@ func (check *checker) blockBranches(all *Scope, parent *block, lstmt *ast.Labele
|
|||
// record label use
|
||||
obj := all.Lookup(name)
|
||||
obj.(*Label).used = true
|
||||
check.recordObject(s.Label, obj)
|
||||
check.recordUse(s.Label, obj)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
if s.Tok == token.DEFINE {
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||
for _, file := range files {
|
||||
// The package identifier denotes the current package,
|
||||
// but there is no corresponding package object.
|
||||
check.recordObject(file.Name, nil)
|
||||
check.recordDef(file.Name, nil)
|
||||
|
||||
fileScope := NewScope(pkg.scope)
|
||||
check.recordScope(file, fileScope)
|
||||
|
|
@ -188,7 +188,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||
obj := NewPkgName(s.Pos(), imp, name)
|
||||
if s.Name != nil {
|
||||
// in a dot-import, the dot represents the package
|
||||
check.recordObject(s.Name, obj)
|
||||
check.recordDef(s.Name, obj)
|
||||
} else {
|
||||
check.recordImplicit(s, obj)
|
||||
}
|
||||
|
|
@ -308,7 +308,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
|||
if name == "init" {
|
||||
// don't declare init functions in the package scope - they are invisible
|
||||
obj.parent = pkg.scope
|
||||
check.recordObject(d.Name, obj)
|
||||
check.recordDef(d.Name, obj)
|
||||
// init functions must have a body
|
||||
if d.Body == nil {
|
||||
check.errorf(obj.pos, "missing function body")
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
_ "code.google.com/p/go.tools/go/gcimporter"
|
||||
|
|
@ -29,6 +30,7 @@ var sources = []string{
|
|||
`
|
||||
package p
|
||||
import "fmt"
|
||||
type errorStringer struct { fmt.Stringer; error }
|
||||
func f() string {
|
||||
_ = "foo"
|
||||
return fmt.Sprintf("%d", g())
|
||||
|
|
@ -42,7 +44,7 @@ var sources = []string{
|
|||
func h() Mode { return ImportsOnly }
|
||||
var _, x int = 1, 2
|
||||
func init() {}
|
||||
type T struct{ sync.Mutex; a, b, c int}
|
||||
type T struct{ *sync.Mutex; a, b, c int}
|
||||
type I interface{ m() }
|
||||
var _ = T{a: 1, b: 2, c: 3}
|
||||
func (_ T) m() {}
|
||||
|
|
@ -55,6 +57,7 @@ var sources = []string{
|
|||
case int:
|
||||
_ = x
|
||||
}
|
||||
switch {} // implicit 'true' tag
|
||||
}
|
||||
`,
|
||||
`
|
||||
|
|
@ -99,8 +102,9 @@ func TestResolveIdents(t *testing.T) {
|
|||
|
||||
// resolve and type-check package AST
|
||||
var conf Config
|
||||
idents := make(map[*ast.Ident]Object)
|
||||
_, err := conf.Check("testResolveIdents", fset, files, &Info{Objects: idents})
|
||||
uses := make(map[*ast.Ident]Object)
|
||||
defs := make(map[*ast.Ident]Object)
|
||||
_, err := conf.Check("testResolveIdents", fset, files, &Info{Defs: defs, Uses: uses})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -117,12 +121,12 @@ func TestResolveIdents(t *testing.T) {
|
|||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if s, ok := n.(*ast.SelectorExpr); ok {
|
||||
if x, ok := s.X.(*ast.Ident); ok {
|
||||
obj := idents[x]
|
||||
obj := uses[x]
|
||||
if obj == nil {
|
||||
t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name)
|
||||
return false
|
||||
}
|
||||
if _, ok := obj.(*PkgName); ok && idents[s.Sel] == nil {
|
||||
if _, ok := obj.(*PkgName); ok && uses[s.Sel] == nil {
|
||||
t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name)
|
||||
return false
|
||||
}
|
||||
|
|
@ -134,14 +138,30 @@ func TestResolveIdents(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// check that each identifier in the source is found in the idents map
|
||||
for id, obj := range uses {
|
||||
if obj == nil {
|
||||
t.Errorf("%s: Uses[%s] == nil", fset.Position(id.Pos()), id.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// check that each identifier in the source is found in uses or defs or both
|
||||
var both []string
|
||||
for _, f := range files {
|
||||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if x, ok := n.(*ast.Ident); ok {
|
||||
if _, found := idents[x]; found {
|
||||
delete(idents, x)
|
||||
} else {
|
||||
var objects int
|
||||
if _, found := uses[x]; found {
|
||||
objects |= 1
|
||||
delete(uses, x)
|
||||
}
|
||||
if _, found := defs[x]; found {
|
||||
objects |= 2
|
||||
delete(defs, x)
|
||||
}
|
||||
if objects == 0 {
|
||||
t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name)
|
||||
} else if objects == 3 {
|
||||
both = append(both, x.Name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -149,8 +169,17 @@ func TestResolveIdents(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// check the expected set of idents that are simultaneously uses and defs
|
||||
sort.Strings(both)
|
||||
if got, want := fmt.Sprint(both), "[Mutex Stringer error]"; got != want {
|
||||
t.Errorf("simultaneous uses/defs = %s, want %s", got, want)
|
||||
}
|
||||
|
||||
// any left-over identifiers didn't exist in the source
|
||||
for x := range idents {
|
||||
for x := range uses {
|
||||
t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name)
|
||||
}
|
||||
for x := range defs {
|
||||
t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import (
|
|||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"code.google.com/p/go.tools/go/exact"
|
||||
)
|
||||
|
||||
func (check *checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt) {
|
||||
|
|
@ -388,14 +390,16 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
|||
|
||||
check.initStmt(s.Init)
|
||||
var x operand
|
||||
tag := s.Tag
|
||||
if tag == nil {
|
||||
// use fake true tag value and position it at the opening { of the switch
|
||||
ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"}
|
||||
check.recordObject(ident, Universe.Lookup("true"))
|
||||
tag = ident
|
||||
if s.Tag != nil {
|
||||
check.expr(&x, s.Tag)
|
||||
} else {
|
||||
// spec: "A missing switch expression is
|
||||
// equivalent to the boolean value true."
|
||||
x.mode = constant
|
||||
x.typ = Typ[Bool]
|
||||
x.val = exact.MakeBool(true)
|
||||
x.expr = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"}
|
||||
}
|
||||
check.expr(&x, tag)
|
||||
|
||||
check.multipleDefaults(s.Body.List)
|
||||
|
||||
|
|
@ -448,7 +452,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
|||
check.invalidAST(s.Pos(), "incorrect form of type switch guard")
|
||||
return
|
||||
}
|
||||
check.recordObject(lhs, nil) // lhs variable is implicitly declared in each cause clause
|
||||
check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause
|
||||
|
||||
rhs = guard.Rhs[0]
|
||||
|
||||
|
|
@ -673,7 +677,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
|||
// declare new variable
|
||||
name := ident.Name
|
||||
obj = NewVar(ident.Pos(), check.pkg, name, nil)
|
||||
check.recordObject(ident, obj)
|
||||
check.recordDef(ident, obj)
|
||||
// _ variables don't count as new variables
|
||||
if name != "_" {
|
||||
vars = append(vars, obj)
|
||||
|
|
|
|||
|
|
@ -742,3 +742,13 @@ func expression_statements(ch chan int) {
|
|||
cap /* ERROR "not used" */ (ch)
|
||||
println /* ERROR "must be called" */
|
||||
}
|
||||
|
||||
func _() {
|
||||
true := "false"
|
||||
_ = true
|
||||
// A tagless switch is equivalent to the bool
|
||||
// constant true, not the identifier 'true'.
|
||||
switch {
|
||||
case "false" /* ERROR "cannot convert" */:
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
|
|||
}
|
||||
return
|
||||
}
|
||||
check.recordObject(e, obj)
|
||||
check.recordUse(e, obj)
|
||||
|
||||
check.objDecl(obj, def, path)
|
||||
typ := obj.Type()
|
||||
|
|
@ -480,7 +480,7 @@ func (check *checker) interfaceType(iface *Interface, ityp *ast.InterfaceType, d
|
|||
iface.methods = append(iface.methods, m)
|
||||
iface.allMethods = append(iface.allMethods, m)
|
||||
signatures = append(signatures, f.Type)
|
||||
check.recordObject(name, m)
|
||||
check.recordDef(name, m)
|
||||
}
|
||||
} else {
|
||||
// embedded type
|
||||
|
|
@ -604,7 +604,8 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
|
|||
// current field typ and tag
|
||||
var typ Type
|
||||
var tag string
|
||||
add := func(field *ast.Field, ident *ast.Ident, name string, anonymous bool, pos token.Pos) {
|
||||
// anonymous != nil indicates an anonymous field.
|
||||
add := func(field *ast.Field, ident *ast.Ident, anonymous *TypeName, pos token.Pos) {
|
||||
if tag != "" && tags == nil {
|
||||
tags = make([]string, len(fields))
|
||||
}
|
||||
|
|
@ -612,13 +613,15 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
|
|||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
fld := NewField(pos, check.pkg, name, typ, anonymous)
|
||||
name := ident.Name
|
||||
fld := NewField(pos, check.pkg, name, typ, anonymous != nil)
|
||||
// spec: "Within a struct, non-blank field names must be unique."
|
||||
if name == "_" || check.declareInSet(&fset, pos, fld) {
|
||||
fields = append(fields, fld)
|
||||
if ident != nil {
|
||||
check.recordObject(ident, fld)
|
||||
}
|
||||
check.recordDef(ident, fld)
|
||||
}
|
||||
if anonymous != nil {
|
||||
check.recordUse(ident, anonymous)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -628,10 +631,11 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
|
|||
if len(f.Names) > 0 {
|
||||
// named fields
|
||||
for _, name := range f.Names {
|
||||
add(f, name, name.Name, false, name.Pos())
|
||||
add(f, name, nil, name.Pos())
|
||||
}
|
||||
} else {
|
||||
// anonymous field
|
||||
name := anonymousFieldIdent(f.Type)
|
||||
pos := f.Type.Pos()
|
||||
t, isPtr := deref(typ)
|
||||
switch t := t.(type) {
|
||||
|
|
@ -645,7 +649,7 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
|
|||
check.errorf(pos, "anonymous field type cannot be unsafe.Pointer")
|
||||
continue
|
||||
}
|
||||
add(f, nil, t.name, true, pos)
|
||||
add(f, name, Universe.Lookup(t.name).(*TypeName), pos)
|
||||
|
||||
case *Named:
|
||||
// spec: "An embedded type must be specified as a type name
|
||||
|
|
@ -667,7 +671,7 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
|
|||
continue
|
||||
}
|
||||
}
|
||||
add(f, nil, t.obj.name, true, pos)
|
||||
add(f, name, t.obj, pos)
|
||||
|
||||
default:
|
||||
check.invalidAST(pos, "anonymous field type %s must be named", typ)
|
||||
|
|
@ -678,3 +682,15 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
|
|||
styp.fields = fields
|
||||
styp.tags = tags
|
||||
}
|
||||
|
||||
func anonymousFieldIdent(e ast.Expr) *ast.Ident {
|
||||
switch e := e.(type) {
|
||||
case *ast.Ident:
|
||||
return e
|
||||
case *ast.StarExpr:
|
||||
return anonymousFieldIdent(e.X)
|
||||
case *ast.SelectorExpr:
|
||||
return e.Sel
|
||||
}
|
||||
return nil // invalid anonymous field
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
//
|
||||
var allNamed []types.Type
|
||||
for _, info := range o.typeInfo {
|
||||
for id, obj := range info.Objects {
|
||||
if obj, ok := obj.(*types.TypeName); ok && obj.Pos() == id.Pos() {
|
||||
for _, obj := range info.Defs {
|
||||
if obj, ok := obj.(*types.TypeName); ok {
|
||||
allNamed = append(allNamed, obj.Type())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,11 @@ func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
|||
return nil, fmt.Errorf("no object for identifier")
|
||||
}
|
||||
|
||||
// Iterate over all go/types' resolver facts for the entire program.
|
||||
// Iterate over all go/types' Uses facts for the entire program.
|
||||
var refs []*ast.Ident
|
||||
for _, info := range o.typeInfo {
|
||||
for id2, obj2 := range info.Objects {
|
||||
for id2, obj2 := range info.Uses {
|
||||
if sameObj(obj, obj2) {
|
||||
if id2.NamePos == obj.Pos() {
|
||||
continue // skip defining ident
|
||||
}
|
||||
refs = append(refs, id2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue