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:
Alan Donovan 2014-02-27 13:21:59 -05:00
parent aaca3a4f95
commit ced954c167
24 changed files with 193 additions and 107 deletions

View File

@ -204,7 +204,8 @@ func doPackageDir(directory string) {
type Package struct { type Package struct {
path string 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 types map[ast.Expr]types.TypeAndValue
spans map[types.Object]Span spans map[types.Object]Span
files []*File files []*File

View File

@ -41,9 +41,9 @@ func (f *File) checkNilFuncComparison(e *ast.BinaryExpr) {
var obj types.Object var obj types.Object
switch v := e2.(type) { switch v := e2.(type) {
case *ast.Ident: case *ast.Ident:
obj = f.pkg.idents[v] obj = f.pkg.uses[v]
case *ast.SelectorExpr: case *ast.SelectorExpr:
obj = f.pkg.idents[v.Sel] obj = f.pkg.uses[v.Sel]
default: default:
return return
} }

View File

@ -189,7 +189,7 @@ func (f *File) checkShadowDecl(d *ast.GenDecl) {
// checkShadowing checks whether the identifier shadows an identifier in an outer scope. // checkShadowing checks whether the identifier shadows an identifier in an outer scope.
func (f *File) checkShadowing(ident *ast.Ident) { func (f *File) checkShadowing(ident *ast.Ident) {
obj := f.pkg.idents[ident] obj := f.pkg.defs[ident]
if obj == nil { if obj == nil {
return return
} }

View File

@ -14,7 +14,8 @@ import (
) )
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error { 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.spans = make(map[types.Object]Span)
pkg.types = make(map[ast.Expr]types.TypeAndValue) pkg.types = make(map[ast.Expr]types.TypeAndValue)
// By providing a Config with our own error function, it will continue // By providing a Config with our own error function, it will continue
@ -24,12 +25,16 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
} }
info := &types.Info{ info := &types.Info{
Types: pkg.types, Types: pkg.types,
Objects: pkg.idents, Defs: pkg.defs,
Uses: pkg.uses,
} }
typesPkg, err := config.Check(pkg.path, fs, astFiles, info) typesPkg, err := config.Check(pkg.path, fs, astFiles, info)
pkg.typesPkg = typesPkg pkg.typesPkg = typesPkg
// update spans // 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) pkg.growSpan(id, obj)
} }
return err return err

View File

@ -683,7 +683,8 @@ func (imp *importer) createPackage(path string, files ...*ast.File) *PackageInfo
Files: files, Files: files,
Info: types.Info{ Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue), 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), Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope), Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection), Selections: make(map[*ast.SelectorExpr]*types.Selection),

View File

@ -54,10 +54,18 @@ func (info *PackageInfo) ValueOf(e ast.Expr) exact.Value {
} }
// ObjectOf returns the typechecker object denoted by the specified id. // 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. // Precondition: id belongs to the package's ASTs.
// //
func (info *PackageInfo) ObjectOf(id *ast.Ident) types.Object { 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. // IsType returns true iff expression e denotes a type.

View File

@ -60,37 +60,36 @@ func TestObjValueLookup(t *testing.T) {
mainPkg.SetDebugMode(true) mainPkg.SetDebugMode(true)
mainPkg.Build() mainPkg.Build()
// Gather all idents and objects in file. var varIds []*ast.Ident
objs := make(map[types.Object]bool) var varObjs []*types.Var
var ids []*ast.Ident for id, obj := range mainInfo.Defs {
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. // Check invariants for func and const objects.
for obj := range objs {
switch obj := obj.(type) { switch obj := obj.(type) {
case *types.Func: case *types.Func:
checkFuncValue(t, prog, obj) checkFuncValue(t, prog, obj)
case *types.Const: case *types.Const:
checkConstValue(t, prog, obj) 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. // Check invariants for var objects.
// The result varies based on the specific Ident. // The result varies based on the specific Ident.
for _, id := range ids { for i, id := range varIds {
if id.Name == "_" { obj := varObjs[i]
continue
}
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos()) ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := prog.Fset.Position(id.Pos()) pos := prog.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)] exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
@ -106,7 +105,6 @@ func TestObjValueLookup(t *testing.T) {
checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr) checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
} }
} }
}
func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) { func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) {
fn := prog.FuncValue(obj) fn := prog.FuncValue(obj)

View File

@ -110,7 +110,7 @@ func main() {
// ssa.Values here: a bound-method closure and a promotion // ssa.Values here: a bound-method closure and a promotion
// wrapper. // wrapper.
_ = v11.method // v11::Const _ = v11.method // v11::Const
_ = (*struct{ J }).method _ = (*struct{ J }).method // J::nil
// These vars are optimised away. // These vars are optimised away.
if false { if false {

View File

@ -10,7 +10,7 @@
// //
// Name resolution maps each identifier (ast.Ident) in the program to the // Name resolution maps each identifier (ast.Ident) in the program to the
// language object (Object) it denotes. // 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 // Constant folding computes the exact constant value (exact.Value) for
// every expression (ast.Expr) that is a compile-time constant. // every expression (ast.Expr) that is a compile-time constant.
@ -130,8 +130,7 @@ type TypeAndValue struct {
type Info struct { type Info struct {
// Types maps expressions to their types, and for constant // Types maps expressions to their types, and for constant
// expressions, their values. // expressions, their values.
// Identifiers on the lhs of declarations are collected in // Identifiers are collected in Defs and Uses, not Types.
// Objects, not Types.
// //
// For an expression denoting a predeclared built-in function // For an expression denoting a predeclared built-in function
// the recorded signature is call-site specific. If the call // 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. // specific signature. Otherwise, the recorded type is invalid.
Types map[ast.Expr]TypeAndValue 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). // package names, dots "." of dot-imports, and blank "_" identifiers).
// For identifiers that do not denote objects (e.g., the package name // For identifiers that do not denote objects (e.g., the package name
// in package clauses, blank identifiers on the lhs of assignments, or // in package clauses, blank identifiers on the lhs of assignments, or
// symbolic variables t in t := x.(type) of type switch headers), the // symbolic variables t in t := x.(type) of type switch headers), the
// corresponding objects are nil. // 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. // Implicits maps nodes to their implicitly declared objects, if any.
// The following node and object types may appear: // The following node and object types may appear:

View File

@ -144,7 +144,7 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) Type {
// Don't evaluate lhs if it is the blank identifier. // Don't evaluate lhs if it is the blank identifier.
if ident != nil && ident.Name == "_" { if ident != nil && ident.Name == "_" {
check.recordObject(ident, nil) check.recordDef(ident, nil)
if !check.assignment(x, nil) { if !check.assignment(x, nil) {
assert(x.mode == invalid) assert(x.mode == invalid)
x.typ = nil x.typ = nil
@ -285,7 +285,7 @@ func (check *checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) {
newVars = append(newVars, obj) newVars = append(newVars, obj)
} }
if obj != nil { if obj != nil {
check.recordObject(ident, obj) check.recordDef(ident, obj)
} }
} else { } else {
check.errorf(lhs.Pos(), "cannot declare %s", lhs) check.errorf(lhs.Pos(), "cannot declare %s", lhs)

View File

@ -133,9 +133,9 @@ func testBuiltinSignature(t *testing.T, name, src0, want string) {
} }
var conf Config var conf Config
objects := make(map[*ast.Ident]Object) uses := make(map[*ast.Ident]Object)
types := make(map[ast.Expr]TypeAndValue) 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 { if err != nil {
t.Errorf("%s: %s", src0, err) t.Errorf("%s: %s", src0, err)
return return
@ -172,7 +172,7 @@ func testBuiltinSignature(t *testing.T, name, src0, want string) {
// identifier denoting the expected built-in // identifier denoting the expected built-in
switch p := fun.(type) { switch p := fun.(type) {
case *ast.Ident: case *ast.Ident:
obj := objects[p] obj := uses[p]
if obj == nil { if obj == nil {
t.Errorf("%s: no object found for %s", src0, p) t.Errorf("%s: no object found for %s", src0, p)
return return

View File

@ -253,7 +253,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
// selector expressions. // selector expressions.
if ident, ok := e.X.(*ast.Ident); ok { if ident, ok := e.X.(*ast.Ident); ok {
if pkg, _ := check.scope.LookupParent(ident.Name).(*PkgName); pkg != nil { if pkg, _ := check.scope.LookupParent(ident.Name).(*PkgName); pkg != nil {
check.recordObject(ident, pkg) check.recordUse(ident, pkg)
pkg.used = true pkg.used = true
exp := pkg.pkg.scope.Lookup(sel) exp := pkg.pkg.scope.Lookup(sel)
if exp == nil { if exp == nil {
@ -268,7 +268,6 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
} }
check.recordSelection(e, PackageObj, nil, exp, nil, false) check.recordSelection(e, PackageObj, nil, exp, nil, false)
// Simplified version of the code for *ast.Idents: // Simplified version of the code for *ast.Idents:
// - imported packages use types.Scope and types.Objects
// - imported objects are always fully initialized // - imported objects are always fully initialized
switch exp := exp.(type) { switch exp := exp.(type) {
case *Const: case *Const:

View File

@ -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) 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 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) { 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)) 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? // TODO(gri) Should we also call recordTypeAndValue?
if m := check.Selections; m != nil { if m := check.Selections; m != nil {
m[x] = &Selection{kind, recv, obj, index, indirect} m[x] = &Selection{kind, recv, obj, index, indirect}

View File

@ -27,7 +27,7 @@ func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
return return
} }
if id != nil { 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 continue
} }
} }
check.recordObject(check.objMap[m].fdecl.Name, m) check.recordDef(check.objMap[m].fdecl.Name, m)
check.objDecl(m, nil, nil) check.objDecl(m, nil, nil)
// Methods with blank _ names cannot be found. // Methods with blank _ names cannot be found.
// Don't add them to the method list. // Don't add them to the method list.

View File

@ -1023,7 +1023,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
continue continue
} }
fld := fields[i] fld := fields[i]
check.recordObject(key, fld) check.recordUse(key, fld)
// 0 <= i < len(fields) // 0 <= i < len(fields)
if visited[i] { if visited[i] {
check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name)

View File

@ -130,15 +130,15 @@ type T struct{} // receiver type after method declaration
} }
var conf Config var conf Config
objects := make(map[*ast.Ident]Object) defs := make(map[*ast.Ident]Object)
_, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Objects: objects}) _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
m := f.Decls[0].(*ast.FuncDecl) m := f.Decls[0].(*ast.FuncDecl)
res1 := objects[m.Name].(*Func).Type().(*Signature).Results().At(0) res1 := defs[m.Name].(*Func).Type().(*Signature).Results().At(0)
res2 := objects[m.Type.Results.List[0].Names[0]].(*Var) res2 := defs[m.Type.Results.List[0].Names[0]].(*Var)
if res1 != res2 { if res1 != res2 {
t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2) t.Errorf("got %s (%p) != %s (%p)", res1, res2, res1, res2)

View File

@ -138,7 +138,7 @@ func (check *checker) blockBranches(all *Scope, parent *block, lstmt *ast.Labele
// ok to continue // ok to continue
} else { } else {
b.insert(s) b.insert(s)
check.recordObject(s.Label, lbl) check.recordDef(s.Label, lbl)
} }
// resolve matching forward jumps and remove them from fwdJumps // resolve matching forward jumps and remove them from fwdJumps
i := 0 i := 0
@ -146,7 +146,7 @@ func (check *checker) blockBranches(all *Scope, parent *block, lstmt *ast.Labele
if jmp.Label.Name == name { if jmp.Label.Name == name {
// match // match
lbl.used = true lbl.used = true
check.recordObject(jmp.Label, lbl) check.recordUse(jmp.Label, lbl)
if jumpsOverVarDecl(jmp) { if jumpsOverVarDecl(jmp) {
check.errorf( check.errorf(
jmp.Label.Pos(), jmp.Label.Pos(),
@ -220,7 +220,7 @@ func (check *checker) blockBranches(all *Scope, parent *block, lstmt *ast.Labele
// record label use // record label use
obj := all.Lookup(name) obj := all.Lookup(name)
obj.(*Label).used = true obj.(*Label).used = true
check.recordObject(s.Label, obj) check.recordUse(s.Label, obj)
case *ast.AssignStmt: case *ast.AssignStmt:
if s.Tok == token.DEFINE { if s.Tok == token.DEFINE {

View File

@ -125,7 +125,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
for _, file := range files { for _, file := range files {
// The package identifier denotes the current package, // The package identifier denotes the current package,
// but there is no corresponding package object. // but there is no corresponding package object.
check.recordObject(file.Name, nil) check.recordDef(file.Name, nil)
fileScope := NewScope(pkg.scope) fileScope := NewScope(pkg.scope)
check.recordScope(file, fileScope) check.recordScope(file, fileScope)
@ -188,7 +188,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
obj := NewPkgName(s.Pos(), imp, name) obj := NewPkgName(s.Pos(), imp, name)
if s.Name != nil { if s.Name != nil {
// in a dot-import, the dot represents the package // in a dot-import, the dot represents the package
check.recordObject(s.Name, obj) check.recordDef(s.Name, obj)
} else { } else {
check.recordImplicit(s, obj) check.recordImplicit(s, obj)
} }
@ -308,7 +308,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
if name == "init" { if name == "init" {
// don't declare init functions in the package scope - they are invisible // don't declare init functions in the package scope - they are invisible
obj.parent = pkg.scope obj.parent = pkg.scope
check.recordObject(d.Name, obj) check.recordDef(d.Name, obj)
// init functions must have a body // init functions must have a body
if d.Body == nil { if d.Body == nil {
check.errorf(obj.pos, "missing function body") check.errorf(obj.pos, "missing function body")

View File

@ -9,6 +9,7 @@ import (
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
"sort"
"testing" "testing"
_ "code.google.com/p/go.tools/go/gcimporter" _ "code.google.com/p/go.tools/go/gcimporter"
@ -29,6 +30,7 @@ var sources = []string{
` `
package p package p
import "fmt" import "fmt"
type errorStringer struct { fmt.Stringer; error }
func f() string { func f() string {
_ = "foo" _ = "foo"
return fmt.Sprintf("%d", g()) return fmt.Sprintf("%d", g())
@ -42,7 +44,7 @@ var sources = []string{
func h() Mode { return ImportsOnly } func h() Mode { return ImportsOnly }
var _, x int = 1, 2 var _, x int = 1, 2
func init() {} func init() {}
type T struct{ sync.Mutex; a, b, c int} type T struct{ *sync.Mutex; a, b, c int}
type I interface{ m() } type I interface{ m() }
var _ = T{a: 1, b: 2, c: 3} var _ = T{a: 1, b: 2, c: 3}
func (_ T) m() {} func (_ T) m() {}
@ -55,6 +57,7 @@ var sources = []string{
case int: case int:
_ = x _ = x
} }
switch {} // implicit 'true' tag
} }
`, `,
` `
@ -99,8 +102,9 @@ func TestResolveIdents(t *testing.T) {
// resolve and type-check package AST // resolve and type-check package AST
var conf Config var conf Config
idents := make(map[*ast.Ident]Object) uses := make(map[*ast.Ident]Object)
_, err := conf.Check("testResolveIdents", fset, files, &Info{Objects: idents}) defs := make(map[*ast.Ident]Object)
_, err := conf.Check("testResolveIdents", fset, files, &Info{Defs: defs, Uses: uses})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -117,12 +121,12 @@ func TestResolveIdents(t *testing.T) {
ast.Inspect(f, func(n ast.Node) bool { ast.Inspect(f, func(n ast.Node) bool {
if s, ok := n.(*ast.SelectorExpr); ok { if s, ok := n.(*ast.SelectorExpr); ok {
if x, ok := s.X.(*ast.Ident); ok { if x, ok := s.X.(*ast.Ident); ok {
obj := idents[x] obj := uses[x]
if obj == nil { if obj == nil {
t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name) t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name)
return false 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) t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name)
return false 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 { for _, f := range files {
ast.Inspect(f, func(n ast.Node) bool { ast.Inspect(f, func(n ast.Node) bool {
if x, ok := n.(*ast.Ident); ok { if x, ok := n.(*ast.Ident); ok {
if _, found := idents[x]; found { var objects int
delete(idents, x) if _, found := uses[x]; found {
} else { 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) t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name)
} else if objects == 3 {
both = append(both, x.Name)
} }
return false 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 // 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) t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name)
} }

View File

@ -10,6 +10,8 @@ import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"code.google.com/p/go.tools/go/exact"
) )
func (check *checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt) { 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) check.initStmt(s.Init)
var x operand var x operand
tag := s.Tag if s.Tag != nil {
if tag == nil { check.expr(&x, s.Tag)
// use fake true tag value and position it at the opening { of the switch } else {
ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} // spec: "A missing switch expression is
check.recordObject(ident, Universe.Lookup("true")) // equivalent to the boolean value true."
tag = ident 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) 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") check.invalidAST(s.Pos(), "incorrect form of type switch guard")
return 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] rhs = guard.Rhs[0]
@ -673,7 +677,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
// declare new variable // declare new variable
name := ident.Name name := ident.Name
obj = NewVar(ident.Pos(), check.pkg, name, nil) obj = NewVar(ident.Pos(), check.pkg, name, nil)
check.recordObject(ident, obj) check.recordDef(ident, obj)
// _ variables don't count as new variables // _ variables don't count as new variables
if name != "_" { if name != "_" {
vars = append(vars, obj) vars = append(vars, obj)

View File

@ -742,3 +742,13 @@ func expression_statements(ch chan int) {
cap /* ERROR "not used" */ (ch) cap /* ERROR "not used" */ (ch)
println /* ERROR "must be called" */ 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" */:
}
}

View File

@ -32,7 +32,7 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
} }
return return
} }
check.recordObject(e, obj) check.recordUse(e, obj)
check.objDecl(obj, def, path) check.objDecl(obj, def, path)
typ := obj.Type() typ := obj.Type()
@ -480,7 +480,7 @@ func (check *checker) interfaceType(iface *Interface, ityp *ast.InterfaceType, d
iface.methods = append(iface.methods, m) iface.methods = append(iface.methods, m)
iface.allMethods = append(iface.allMethods, m) iface.allMethods = append(iface.allMethods, m)
signatures = append(signatures, f.Type) signatures = append(signatures, f.Type)
check.recordObject(name, m) check.recordDef(name, m)
} }
} else { } else {
// embedded type // embedded type
@ -604,7 +604,8 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
// current field typ and tag // current field typ and tag
var typ Type var typ Type
var tag string 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 { if tag != "" && tags == nil {
tags = make([]string, len(fields)) tags = make([]string, len(fields))
} }
@ -612,13 +613,15 @@ func (check *checker) structType(styp *Struct, e *ast.StructType, path []*TypeNa
tags = append(tags, tag) 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." // spec: "Within a struct, non-blank field names must be unique."
if name == "_" || check.declareInSet(&fset, pos, fld) { if name == "_" || check.declareInSet(&fset, pos, fld) {
fields = append(fields, fld) fields = append(fields, fld)
if ident != nil { check.recordDef(ident, fld)
check.recordObject(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 { if len(f.Names) > 0 {
// named fields // named fields
for _, name := range f.Names { for _, name := range f.Names {
add(f, name, name.Name, false, name.Pos()) add(f, name, nil, name.Pos())
} }
} else { } else {
// anonymous field // anonymous field
name := anonymousFieldIdent(f.Type)
pos := f.Type.Pos() pos := f.Type.Pos()
t, isPtr := deref(typ) t, isPtr := deref(typ)
switch t := t.(type) { 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") check.errorf(pos, "anonymous field type cannot be unsafe.Pointer")
continue continue
} }
add(f, nil, t.name, true, pos) add(f, name, Universe.Lookup(t.name).(*TypeName), pos)
case *Named: case *Named:
// spec: "An embedded type must be specified as a type name // 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 continue
} }
} }
add(f, nil, t.obj.name, true, pos) add(f, name, t.obj, pos)
default: default:
check.invalidAST(pos, "anonymous field type %s must be named", typ) 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.fields = fields
styp.tags = tags 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
}

View File

@ -39,8 +39,8 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
// //
var allNamed []types.Type var allNamed []types.Type
for _, info := range o.typeInfo { for _, info := range o.typeInfo {
for id, obj := range info.Objects { for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok && obj.Pos() == id.Pos() { if obj, ok := obj.(*types.TypeName); ok {
allNamed = append(allNamed, obj.Type()) allNamed = append(allNamed, obj.Type())
} }
} }

View File

@ -29,14 +29,11 @@ func referrers(o *Oracle, qpos *QueryPos) (queryResult, error) {
return nil, fmt.Errorf("no object for identifier") 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 var refs []*ast.Ident
for _, info := range o.typeInfo { for _, info := range o.typeInfo {
for id2, obj2 := range info.Objects { for id2, obj2 := range info.Uses {
if sameObj(obj, obj2) { if sameObj(obj, obj2) {
if id2.NamePos == obj.Pos() {
continue // skip defining ident
}
refs = append(refs, id2) refs = append(refs, id2)
} }
} }