| // Copyright 2011 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package types_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/importer" |
| "go/parser" |
| "go/token" |
| "internal/testenv" |
| "sort" |
| "testing" |
| |
| . "go/types" |
| ) |
| |
| type resolveTestImporter struct { |
| importer ImporterFrom |
| imported map[string]bool |
| } |
| |
| func (imp *resolveTestImporter) Import(string) (*Package, error) { |
| panic("should not be called") |
| } |
| |
| func (imp *resolveTestImporter) ImportFrom(path, srcDir string, mode ImportMode) (*Package, error) { |
| if mode != 0 { |
| panic("mode must be 0") |
| } |
| if imp.importer == nil { |
| imp.importer = importer.Default().(ImporterFrom) |
| imp.imported = make(map[string]bool) |
| } |
| pkg, err := imp.importer.ImportFrom(path, srcDir, mode) |
| if err != nil { |
| return nil, err |
| } |
| imp.imported[path] = true |
| return pkg, nil |
| } |
| |
| func TestResolveIdents(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| sources := []string{ |
| ` |
| package p |
| import "fmt" |
| import "math" |
| const pi = math.Pi |
| func sin(x float64) float64 { |
| return math.Sin(x) |
| } |
| var Println = fmt.Println |
| `, |
| ` |
| package p |
| import "fmt" |
| type errorStringer struct { fmt.Stringer; error } |
| func f() string { |
| _ = "foo" |
| return fmt.Sprintf("%d", g()) |
| } |
| func g() (x int) { return } |
| `, |
| ` |
| package p |
| import . "go/parser" |
| import "sync" |
| func h() Mode { return ImportsOnly } |
| var _, x int = 1, 2 |
| func init() {} |
| type T struct{ *sync.Mutex; a, b, c int} |
| type I interface{ m() } |
| var _ = T{a: 1, b: 2, c: 3} |
| func (_ T) m() {} |
| func (T) _() {} |
| var i I |
| var _ = i.m |
| func _(s []int) { for i, x := range s { _, _ = i, x } } |
| func _(x interface{}) { |
| switch x := x.(type) { |
| case int: |
| _ = x |
| } |
| switch {} // implicit 'true' tag |
| } |
| `, |
| ` |
| package p |
| type S struct{} |
| func (T) _() {} |
| func (T) _() {} |
| `, |
| ` |
| package p |
| func _() { |
| L0: |
| L1: |
| goto L0 |
| for { |
| goto L1 |
| } |
| if true { |
| goto L2 |
| } |
| L2: |
| } |
| `, |
| } |
| |
| pkgnames := []string{ |
| "fmt", |
| "math", |
| } |
| |
| // parse package files |
| fset := token.NewFileSet() |
| var files []*ast.File |
| for i, src := range sources { |
| f, err := parser.ParseFile(fset, fmt.Sprintf("sources[%d]", i), src, parser.DeclarationErrors) |
| if err != nil { |
| t.Fatal(err) |
| } |
| files = append(files, f) |
| } |
| |
| // resolve and type-check package AST |
| importer := new(resolveTestImporter) |
| conf := Config{Importer: importer} |
| 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) |
| } |
| |
| // check that all packages were imported |
| for _, name := range pkgnames { |
| if !importer.imported[name] { |
| t.Errorf("package %s not imported", name) |
| } |
| } |
| |
| // check that qualified identifiers are resolved |
| for _, f := range files { |
| ast.Inspect(f, func(n ast.Node) bool { |
| if s, ok := n.(*ast.SelectorExpr); ok { |
| if x, ok := s.X.(*ast.Ident); ok { |
| 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 && uses[s.Sel] == nil { |
| t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name) |
| return false |
| } |
| return false |
| } |
| return false |
| } |
| return true |
| }) |
| } |
| |
| 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 { |
| 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 |
| } |
| return true |
| }) |
| } |
| |
| // 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 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) |
| } |
| |
| // TODO(gri) add tests to check ImplicitObj callbacks |
| } |