--- /dev/null
+// 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.
+
+// This file implements a typechecker test harness. The packages specified
+// in tests are typechecked. Error messages reported by the typechecker are
+// compared against the error messages expected in the test files.
+//
+// Expected errors are indicated in the test files by putting a comment
+// of the form /* ERROR "rx" */ immediately following an offending token.
+// The harness will verify that an error matching the regular expression
+// rx is reported at that source position. Consecutive comments may be
+// used to indicate multiple errors for the same token position.
+//
+// For instance, the following test file indicates that a "not declared"
+// error should be reported for the undeclared variable x:
+//
+// package p
+// func f() {
+// _ = x /* ERROR "not declared" */ + 1
+// }
+
+package types
+
+import (
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "testing"
+)
+
+var listErrors = flag.Bool("list", false, "list errors")
+
+func init() {
+ // declare builtins for testing
+ def(ast.Fun, "assert").Type = &builtin{aType, _Assert, "assert", 1, false, true}
+ def(ast.Fun, "trace").Type = &builtin{aType, _Trace, "trace", 0, true, true}
+}
+
+// The test filenames do not end in .go so that they are invisible
+// to gofmt since they contain comments that must not change their
+// positions relative to surrounding tokens.
+
+var tests = []struct {
+ name string
+ files []string
+}{
+ {"decls0", []string{"testdata/decls0.src"}},
+ {"decls1", []string{"testdata/decls1.src"}},
+ {"decls2", []string{"testdata/decls2a.src", "testdata/decls2b.src"}},
+ {"const0", []string{"testdata/const0.src"}},
+ {"expr0", []string{"testdata/expr0.src"}},
+ {"expr1", []string{"testdata/expr1.src"}},
+ {"expr2", []string{"testdata/expr2.src"}},
+ {"expr3", []string{"testdata/expr3.src"}},
+ {"builtins", []string{"testdata/builtins.src"}},
+ {"conversions", []string{"testdata/conversions.src"}},
+ {"stmt0", []string{"testdata/stmt0.src"}},
+}
+
+var fset = token.NewFileSet()
+
+func getFile(filename string) (file *token.File) {
+ fset.Iterate(func(f *token.File) bool {
+ if f.Name() == filename {
+ file = f
+ return false // end iteration
+ }
+ return true
+ })
+ return file
+}
+
+func getPos(filename string, offset int) token.Pos {
+ if f := getFile(filename); f != nil {
+ return f.Pos(offset)
+ }
+ return token.NoPos
+}
+
+func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, error) {
+ files := make(map[string]*ast.File)
+ var errors scanner.ErrorList
+ for _, filename := range filenames {
+ if _, exists := files[filename]; exists {
+ t.Fatalf("%s: duplicate file %s", testname, filename)
+ }
+ file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors)
+ if file == nil {
+ t.Fatalf("%s: could not parse file %s", testname, filename)
+ }
+ files[filename] = file
+ if err != nil {
+ // if the parser returns a non-scanner.ErrorList error
+ // the file couldn't be read in the first place and
+ // file == nil; in that case we shouldn't reach here
+ errors = append(errors, err.(scanner.ErrorList)...)
+ }
+
+ }
+ return files, errors
+}
+
+// ERROR comments must be of the form /* ERROR "rx" */ and rx is
+// a regular expression that matches the expected error message.
+//
+var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)
+
+// expectedErrors collects the regular expressions of ERROR comments found
+// in files and returns them as a map of error positions to error messages.
+//
+func expectedErrors(t *testing.T, testname string, files map[string]*ast.File) map[token.Pos][]string {
+ errors := make(map[token.Pos][]string)
+
+ for filename := range files {
+ src, err := ioutil.ReadFile(filename)
+ if err != nil {
+ t.Fatalf("%s: could not read %s", testname, filename)
+ }
+
+ var s scanner.Scanner
+ // file was parsed already - do not add it again to the file
+ // set otherwise the position information returned here will
+ // not match the position information collected by the parser
+ s.Init(getFile(filename), src, nil, scanner.ScanComments)
+ var prev token.Pos // position of last non-comment, non-semicolon token
+
+ scanFile:
+ for {
+ pos, tok, lit := s.Scan()
+ switch tok {
+ case token.EOF:
+ break scanFile
+ case token.COMMENT:
+ s := errRx.FindStringSubmatch(lit)
+ if len(s) == 2 {
+ list := errors[prev]
+ errors[prev] = append(list, string(s[1]))
+ }
+ case token.SEMICOLON:
+ // ignore automatically inserted semicolon
+ if lit == "\n" {
+ break
+ }
+ fallthrough
+ default:
+ prev = pos
+ }
+ }
+ }
+
+ return errors
+}
+
+func eliminate(t *testing.T, expected map[token.Pos][]string, errors error) {
+ if *listErrors || errors == nil {
+ return
+ }
+ for _, error := range errors.(scanner.ErrorList) {
+ // error.Pos is a token.Position, but we want
+ // a token.Pos so we can do a map lookup
+ pos := getPos(error.Pos.Filename, error.Pos.Offset)
+ list := expected[pos]
+ index := -1 // list index of matching message, if any
+ // we expect one of the messages in list to match the error at pos
+ for i, msg := range list {
+ rx, err := regexp.Compile(msg)
+ if err != nil {
+ t.Errorf("%s: %v", error.Pos, err)
+ continue
+ }
+ if match := rx.MatchString(error.Msg); match {
+ index = i
+ break
+ }
+ }
+ if index >= 0 {
+ // eliminate from list
+ n := len(list) - 1
+ if n > 0 {
+ // not the last entry - swap in last element and shorten list by 1
+ list[index] = list[n]
+ expected[pos] = list[:n]
+ } else {
+ // last entry - remove list from map
+ delete(expected, pos)
+ }
+ } else {
+ t.Errorf("%s: no error expected: %q", error.Pos, error.Msg)
+ continue
+ }
+ }
+}
+
+func checkFiles(t *testing.T, testname string, testfiles []string) {
+ // TODO(gri) Eventually all these different phases should be
+ // subsumed into a single function call that takes
+ // a set of files and creates a fully resolved and
+ // type-checked AST.
+
+ files, err := parseFiles(t, testname, testfiles)
+
+ // we are expecting the following errors
+ // (collect these after parsing the files so that
+ // they are found in the file set)
+ errors := expectedErrors(t, testname, files)
+
+ // verify errors returned by the parser
+ eliminate(t, errors, err)
+
+ // verify errors returned after resolving identifiers
+ pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+ eliminate(t, errors, err)
+
+ // verify errors returned by the typechecker
+ var list scanner.ErrorList
+ errh := func(pos token.Pos, msg string) {
+ list.Add(fset.Position(pos), msg)
+ }
+ err = Check(fset, pkg, errh, nil)
+ eliminate(t, errors, list)
+
+ if *listErrors {
+ scanner.PrintError(os.Stdout, err)
+ return
+ }
+
+ // there should be no expected errors left
+ if len(errors) > 0 {
+ t.Errorf("%s: %d errors not reported:", testname, len(errors))
+ for pos, msg := range errors {
+ t.Errorf("%s: %s\n", fset.Position(pos), msg)
+ }
+ }
+}
+
+func TestCheck(t *testing.T) {
+ // For easy debugging w/o changing the testing code,
+ // if there is a local test file, only test that file.
+ const testfile = "testdata/test.go"
+ if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() {
+ fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\n", testfile)
+ checkFiles(t, testfile, []string{testfile})
+ return
+ }
+
+ // Otherwise, run all the tests.
+ for _, test := range tests {
+ checkFiles(t, test.name, test.files)
+ }
+}
--- /dev/null
+// 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
+
+import (
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/scanner"
+ "go/token"
+ "testing"
+)
+
+var 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"
+ func f() string {
+ return fmt.Sprintf("%d", g())
+ }
+ `,
+ `package p
+ import . "go/parser"
+ func g() Mode { return ImportsOnly }`,
+}
+
+var pkgnames = []string{
+ "fmt",
+ "go/parser",
+ "math",
+}
+
+// ResolveQualifiedIdents resolves the selectors of qualified
+// identifiers by associating the correct ast.Object with them.
+// TODO(gri): Eventually, this functionality should be subsumed
+// by Check.
+//
+func ResolveQualifiedIdents(fset *token.FileSet, pkg *ast.Package) error {
+ var errors scanner.ErrorList
+
+ findObj := func(pkg *ast.Object, name *ast.Ident) *ast.Object {
+ scope := pkg.Data.(*ast.Scope)
+ obj := scope.Lookup(name.Name)
+ if obj == nil {
+ errors.Add(fset.Position(name.Pos()), fmt.Sprintf("no %s in package %s", name.Name, pkg.Name))
+ }
+ return obj
+ }
+
+ ast.Inspect(pkg, func(n ast.Node) bool {
+ if s, ok := n.(*ast.SelectorExpr); ok {
+ if x, ok := s.X.(*ast.Ident); ok && x.Obj != nil && x.Obj.Kind == ast.Pkg {
+ // find selector in respective package
+ s.Sel.Obj = findObj(x.Obj, s.Sel)
+ }
+ return false
+ }
+ return true
+ })
+
+ return errors.Err()
+}
+
+func TestResolveQualifiedIdents(t *testing.T) {
+ // parse package files
+ fset := token.NewFileSet()
+ files := make(map[string]*ast.File)
+ for i, src := range sources {
+ filename := fmt.Sprintf("file%d", i)
+ f, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
+ if err != nil {
+ t.Fatal(err)
+ }
+ files[filename] = f
+ }
+
+ // resolve package AST
+ pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // check that all packages were imported
+ for _, name := range pkgnames {
+ if pkg.Imports[name] == nil {
+ t.Errorf("package %s not imported", name)
+ }
+ }
+
+ // check that there are no top-level unresolved identifiers
+ for _, f := range pkg.Files {
+ for _, x := range f.Unresolved {
+ t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
+ }
+ }
+
+ // resolve qualified identifiers
+ if err := ResolveQualifiedIdents(fset, pkg); err != nil {
+ t.Error(err)
+ }
+
+ // check that qualified identifiers are resolved
+ ast.Inspect(pkg, func(n ast.Node) bool {
+ if s, ok := n.(*ast.SelectorExpr); ok {
+ if x, ok := s.X.(*ast.Ident); ok {
+ if x.Obj == nil {
+ t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name)
+ return false
+ }
+ if x.Obj.Kind == ast.Pkg && s.Sel != nil && s.Sel.Obj == nil {
+ t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name)
+ return false
+ }
+ return false
+ }
+ return false
+ }
+ return true
+ })
+}
--- /dev/null
+// Copyright 2012 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.
+
+// This file contains tests verifying the types associated with an AST after
+// type checking.
+
+package types
+
+import (
+ "go/ast"
+ "go/parser"
+ "testing"
+)
+
+const filename = "<src>"
+
+func makePkg(t *testing.T, src string) (*ast.Package, error) {
+ file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
+ if err != nil {
+ return nil, err
+ }
+ files := map[string]*ast.File{filename: file}
+ pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
+ if err != nil {
+ return nil, err
+ }
+ if err := Check(fset, pkg, nil, nil); err != nil {
+ return nil, err
+ }
+ return pkg, nil
+}
+
+type testEntry struct {
+ src, str string
+}
+
+// dup returns a testEntry where both src and str are the same.
+func dup(s string) testEntry {
+ return testEntry{s, s}
+}
+
+var testTypes = []testEntry{
+ // basic types
+ dup("int"),
+ dup("float32"),
+ dup("string"),
+
+ // arrays
+ dup("[10]int"),
+
+ // slices
+ dup("[]int"),
+ dup("[][]int"),
+
+ // structs
+ dup("struct{}"),
+ dup("struct{x int}"),
+ {`struct {
+ x, y int
+ z float32 "foo"
+ }`, `struct{x int; y int; z float32 "foo"}`},
+ {`struct {
+ string
+ elems []T
+ }`, `struct{string; elems []T}`},
+
+ // pointers
+ dup("*int"),
+ dup("***struct{}"),
+ dup("*struct{a int; b float32}"),
+
+ // functions
+ dup("func()"),
+ dup("func(x int)"),
+ {"func(x, y int)", "func(x int, y int)"},
+ {"func(x, y int, z string)", "func(x int, y int, z string)"},
+ dup("func(int)"),
+ {"func(int, string, byte)", "func(int, string, byte)"},
+
+ dup("func() int"),
+ {"func() (string)", "func() string"},
+ dup("func() (u int)"),
+ {"func() (u, v int, w string)", "func() (u int, v int, w string)"},
+
+ dup("func(int) string"),
+ dup("func(x int) string"),
+ dup("func(x int) (u string)"),
+ {"func(x, y int) (u string)", "func(x int, y int) (u string)"},
+
+ dup("func(...int) string"),
+ dup("func(x ...int) string"),
+ dup("func(x ...int) (u string)"),
+ {"func(x, y ...int) (u string)", "func(x int, y ...int) (u string)"},
+
+ // interfaces
+ dup("interface{}"),
+ dup("interface{m()}"),
+ {`interface{
+ m(int) float32
+ String() string
+ }`, `interface{String() string; m(int) float32}`}, // methods are sorted
+ // TODO(gri) add test for interface w/ anonymous field
+
+ // maps
+ dup("map[string]int"),
+ {"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"},
+
+ // channels
+ dup("chan int"),
+ dup("chan<- func()"),
+ dup("<-chan []func() int"),
+}
+
+func TestTypes(t *testing.T) {
+ for _, test := range testTypes {
+ src := "package p; type T " + test.src
+ pkg, err := makePkg(t, src)
+ if err != nil {
+ t.Errorf("%s: %s", src, err)
+ continue
+ }
+ typ := underlying(pkg.Scope.Lookup("T").Type.(Type))
+ str := typeString(typ)
+ if str != test.str {
+ t.Errorf("%s: got %s, want %s", test.src, str, test.str)
+ }
+ }
+}
+
+var testExprs = []testEntry{
+ // basic type literals
+ dup("x"),
+ dup("true"),
+ dup("42"),
+ dup("3.1415"),
+ dup("2.71828i"),
+ dup(`'a'`),
+ dup(`"foo"`),
+ dup("`bar`"),
+
+ // arbitrary expressions
+ dup("&x"),
+ dup("*x"),
+ dup("(x)"),
+ dup("x + y"),
+ dup("x + y * 10"),
+ dup("s.foo"),
+ dup("s[0]"),
+ dup("s[x:y]"),
+ dup("s[:y]"),
+ dup("s[x:]"),
+ dup("s[:]"),
+ dup("f(1, 2.3)"),
+ dup("-f(10, 20)"),
+ dup("f(x + y, +3.1415)"),
+ {"func(a, b int) {}", "(func literal)"},
+ {"func(a, b int) []int {}()[x]", "(func literal)()[x]"},
+ {"[]int{1, 2, 3}", "(composite literal)"},
+ {"[]int{1, 2, 3}[x:]", "(composite literal)[x:]"},
+ {"x.([]string)", "x.(...)"},
+}
+
+func TestExprs(t *testing.T) {
+ for _, test := range testExprs {
+ src := "package p; var _ = " + test.src + "; var (x, y int; s []string; f func(int, float32))"
+ pkg, err := makePkg(t, src)
+ if err != nil {
+ t.Errorf("%s: %s", src, err)
+ continue
+ }
+ expr := pkg.Files[filename].Decls[0].(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values[0]
+ str := exprString(expr)
+ if str != test.str {
+ t.Errorf("%s: got %s, want %s", test.src, str, test.str)
+ }
+ }
+}