From ced954c167af947a6d119cd125918ace69e9be23 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 27 Feb 2014 13:21:59 -0500 Subject: [PATCH] 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 --- cmd/vet/main.go | 3 +- cmd/vet/nilfunc.go | 4 +-- cmd/vet/shadow.go | 2 +- cmd/vet/types.go | 13 +++++--- go/loader/loader.go | 3 +- go/loader/pkginfo.go | 10 +++++- go/ssa/source_test.go | 60 +++++++++++++++++------------------- go/ssa/testdata/objlookup.go | 4 +-- go/types/api.go | 20 +++++++++--- go/types/assignments.go | 4 +-- go/types/builtins_test.go | 6 ++-- go/types/call.go | 3 +- go/types/check.go | 14 +++++++-- go/types/decl.go | 4 +-- go/types/expr.go | 2 +- go/types/issues_test.go | 8 ++--- go/types/labels.go | 6 ++-- go/types/resolver.go | 6 ++-- go/types/resolver_test.go | 49 +++++++++++++++++++++++------ go/types/stmt.go | 22 +++++++------ go/types/testdata/stmt0.src | 10 ++++++ go/types/typexpr.go | 36 ++++++++++++++++------ oracle/implements.go | 4 +-- oracle/referrers.go | 7 ++--- 24 files changed, 193 insertions(+), 107 deletions(-) diff --git a/cmd/vet/main.go b/cmd/vet/main.go index b9d80107..9039532d 100644 --- a/cmd/vet/main.go +++ b/cmd/vet/main.go @@ -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 diff --git a/cmd/vet/nilfunc.go b/cmd/vet/nilfunc.go index 3efb2de3..922ecc37 100644 --- a/cmd/vet/nilfunc.go +++ b/cmd/vet/nilfunc.go @@ -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 } diff --git a/cmd/vet/shadow.go b/cmd/vet/shadow.go index afb0fed0..1f3c4247 100644 --- a/cmd/vet/shadow.go +++ b/cmd/vet/shadow.go @@ -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 } diff --git a/cmd/vet/types.go b/cmd/vet/types.go index dce66dd5..2cf4de4a 100644 --- a/cmd/vet/types.go +++ b/cmd/vet/types.go @@ -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 diff --git a/go/loader/loader.go b/go/loader/loader.go index 16c330a3..0048be8e 100644 --- a/go/loader/loader.go +++ b/go/loader/loader.go @@ -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), diff --git a/go/loader/pkginfo.go b/go/loader/pkginfo.go index dba4f0c7..167da94d 100644 --- a/go/loader/pkginfo.go +++ b/go/loader/pkginfo.go @@ -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. diff --git a/go/ssa/source_test.go b/go/ssa/source_test.go index 2f3bcc9a..ee371c8b 100644 --- a/go/ssa/source_test.go +++ b/go/ssa/source_test.go @@ -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) } } diff --git a/go/ssa/testdata/objlookup.go b/go/ssa/testdata/objlookup.go index f747a2aa..d2862d33 100644 --- a/go/ssa/testdata/objlookup.go +++ b/go/ssa/testdata/objlookup.go @@ -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 { diff --git a/go/types/api.go b/go/types/api.go index 43db3ed6..c240dcef 100644 --- a/go/types/api.go +++ b/go/types/api.go @@ -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: diff --git a/go/types/assignments.go b/go/types/assignments.go index 8961f0f9..ac8aa2c2 100644 --- a/go/types/assignments.go +++ b/go/types/assignments.go @@ -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) diff --git a/go/types/builtins_test.go b/go/types/builtins_test.go index c33f63cb..ca3bac88 100644 --- a/go/types/builtins_test.go +++ b/go/types/builtins_test.go @@ -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 diff --git a/go/types/call.go b/go/types/call.go index 4cce318a..a5895106 100644 --- a/go/types/call.go +++ b/go/types/call.go @@ -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: diff --git a/go/types/check.go b/go/types/check.go index 3b708f3d..ef1e2c1a 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -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} diff --git a/go/types/decl.go b/go/types/decl.go index 326818bf..60a689c3 100644 --- a/go/types/decl.go +++ b/go/types/decl.go @@ -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. diff --git a/go/types/expr.go b/go/types/expr.go index e746825c..f0e0c0db 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -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) diff --git a/go/types/issues_test.go b/go/types/issues_test.go index 8bca1931..cd86ea0a 100644 --- a/go/types/issues_test.go +++ b/go/types/issues_test.go @@ -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) diff --git a/go/types/labels.go b/go/types/labels.go index 65b39e44..d854031d 100644 --- a/go/types/labels.go +++ b/go/types/labels.go @@ -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 { diff --git a/go/types/resolver.go b/go/types/resolver.go index 4b9dcfdf..ae27e4b1 100644 --- a/go/types/resolver.go +++ b/go/types/resolver.go @@ -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") diff --git a/go/types/resolver_test.go b/go/types/resolver_test.go index 5c8a97e9..9fc98931 100644 --- a/go/types/resolver_test.go +++ b/go/types/resolver_test.go @@ -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) } diff --git a/go/types/stmt.go b/go/types/stmt.go index f0faa823..d1f94caa 100644 --- a/go/types/stmt.go +++ b/go/types/stmt.go @@ -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) diff --git a/go/types/testdata/stmt0.src b/go/types/testdata/stmt0.src index c70ef3e5..255c171d 100644 --- a/go/types/testdata/stmt0.src +++ b/go/types/testdata/stmt0.src @@ -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" */: + } +} \ No newline at end of file diff --git a/go/types/typexpr.go b/go/types/typexpr.go index 3f881429..1cff6722 100644 --- a/go/types/typexpr.go +++ b/go/types/typexpr.go @@ -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 +} diff --git a/oracle/implements.go b/oracle/implements.go index 2ff69a7d..423de543 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -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()) } } diff --git a/oracle/referrers.go b/oracle/referrers.go index 24b5414a..ddab2361 100644 --- a/oracle/referrers.go +++ b/oracle/referrers.go @@ -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) } }