]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1] go/ast: multiple "blank" imports are permitted
authorRobert Griesemer <gri@golang.org>
Fri, 21 Sep 2012 19:53:40 +0000 (05:53 +1000)
committerRobert Griesemer <gri@golang.org>
Fri, 21 Sep 2012 19:53:40 +0000 (05:53 +1000)
««« backport 2033e1b11a20
go/ast: multiple "blank" imports are permitted

R=rsc, dsymonds
CC=golang-dev
https://golang.org/cl/6303099

»»»

src/pkg/exp/types/check_test.go [new file with mode: 0644]
src/pkg/exp/types/testdata/test0.src [new file with mode: 0644]
src/pkg/go/ast/resolve.go

diff --git a/src/pkg/exp/types/check_test.go b/src/pkg/exp/types/check_test.go
new file mode 100644 (file)
index 0000000..03cc743
--- /dev/null
@@ -0,0 +1,217 @@
+// 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 (
+       "fmt"
+       "go/ast"
+       "go/parser"
+       "go/scanner"
+       "go/token"
+       "io/ioutil"
+       "os"
+       "regexp"
+       "testing"
+)
+
+// 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
+}{
+       {"test0", []string{"testdata/test0.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 {
+                                       errors[prev] = 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 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)
+               if msg, found := expected[pos]; found {
+                       // we expect a message at pos; check if it matches
+                       rx, err := regexp.Compile(msg)
+                       if err != nil {
+                               t.Errorf("%s: %v", error.Pos, err)
+                               continue
+                       }
+                       if match := rx.MatchString(error.Msg); !match {
+                               t.Errorf("%s: %q does not match %q", error.Pos, error.Msg, msg)
+                               continue
+                       }
+                       // we have a match - eliminate this error
+                       delete(expected, pos)
+               } else {
+                       // To keep in mind when analyzing failed test output:
+                       // If the same error position occurs multiple times in errors,
+                       // this message will be triggered (because the first error at
+                       // the position removes this position from the expected errors).
+                       t.Errorf("%s: no (multiple?) error expected, but found: %s", error.Pos, error.Msg)
+               }
+       }
+}
+
+func check(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
+       _, err = Check(fset, pkg)
+       eliminate(t, errors, err)
+
+       // 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)
+               check(t, testfile, []string{testfile})
+               return
+       }
+
+       // Otherwise, run all the tests.
+       for _, test := range tests {
+               check(t, test.name, test.files)
+       }
+}
diff --git a/src/pkg/exp/types/testdata/test0.src b/src/pkg/exp/types/testdata/test0.src
new file mode 100644 (file)
index 0000000..8765734
--- /dev/null
@@ -0,0 +1,159 @@
+// 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.
+
+// type declarations
+
+package test0
+
+import (
+       "unsafe"
+       // we can have multiple blank imports (was bug)
+       _ "math"
+       _ "net/rpc"
+)
+
+const pi = 3.1415
+
+type (
+       N undeclared /* ERROR "undeclared" */
+       B bool
+       I int32
+       A [10]P
+       T struct {
+               x, y P
+       }
+       P *T
+       R (*R)
+       F func(A) I
+       Y interface {
+               f(A) I
+       }
+       S [](((P)))
+       M map[I]F
+       C chan<- I
+)
+
+
+type (
+       p1 pi /* ERROR "not a package" */ .foo
+       p2 unsafe.Pointer
+)
+
+
+type (
+       Pi pi /* ERROR "not a type" */
+
+       a /* DISABLED "illegal cycle" */ a
+       a /* ERROR "redeclared" */ int
+
+       // where the cycle error appears depends on the
+       // order in which declarations are processed
+       // (which depends on the order in which a map
+       // is iterated through)
+       b c
+       c /* DISABLED "illegal cycle" */ d
+       d e
+       e b
+
+       t *t
+
+       U V
+       V *W
+       W U
+
+       P1 *S2
+       P2 P1
+
+       S0 struct {
+       }
+       S1 struct {
+               a, b, c int
+               u, v, a /* ERROR "redeclared" */ float32
+       }
+       S2 struct {
+               U // anonymous field
+               // TODO(gri) recognize double-declaration below
+               // U /* ERROR "redeclared" */ int
+       }
+       S3 struct {
+               x S2
+       }
+       S4/* DISABLED "illegal cycle" */ struct {
+               S4
+       }
+       S5 struct {
+               S6
+       }
+       S6 /* DISABLED "illegal cycle" */ struct {
+               field S7
+       }
+       S7 struct {
+               S5
+       }
+
+       L1 []L1
+       L2 []int
+
+       A1 [10]int
+       A2 /* DISABLED "illegal cycle" */ [10]A2
+       A3 /* DISABLED "illegal cycle" */ [10]struct {
+               x A4
+       }
+       A4 [10]A3
+
+       F1 func()
+       F2 func(x, y, z float32)
+       F3 func(x, y, x /* ERROR "redeclared" */ float32)
+       F4 func() (x, y, x /* ERROR "redeclared" */ float32)
+       F5 func(x int) (x /* ERROR "redeclared" */ float32)
+       F6 func(x ...int)
+
+       I1 interface{}
+       I2 interface {
+               m1()
+       }
+       I3 interface {
+               m1()
+               m1 /* ERROR "redeclared" */ ()
+       }
+       I4 interface {
+               m1(x, y, x /* ERROR "redeclared" */ float32)
+               m2() (x, y, x /* ERROR "redeclared" */ float32)
+               m3(x int) (x /* ERROR "redeclared" */ float32)
+       }
+       I5 interface {
+               m1(I5)
+       }
+       I6 interface {
+               S0 /* ERROR "non-interface" */
+       }
+       I7 interface {
+               I1
+               I1
+       }
+       I8 /* DISABLED "illegal cycle" */ interface {
+               I8
+       }
+       I9 /* DISABLED "illegal cycle" */ interface {
+               I10
+       }
+       I10 interface {
+               I11
+       }
+       I11 interface {
+               I9
+       }
+
+       C1 chan int
+       C2 <-chan int
+       C3 chan<- C3
+       C4 chan C5
+       C5 chan C6
+       C6 chan C4
+
+       M1 map[Last]string
+       M2 map[string]M2
+
+       Last int
+)
index 908e61c5da01d71560449ebb777d5d3bd1405154..54b5d73252d1b8d417e21bf37aedb9c66e831b23 100644 (file)
@@ -136,7 +136,7 @@ func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer,
                                for _, obj := range pkg.Data.(*Scope).Objects {
                                        p.declare(fileScope, pkgScope, obj)
                                }
-                       } else {
+                       } else if name != "_" {
                                // declare imported package object in file scope
                                // (do not re-use pkg in the file scope but create
                                // a new object instead; the Decl field is different