diff options
author | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2015-10-31 00:59:47 +0000 |
---|---|---|
committer | ian <ian@138bc75d-0d04-0410-961f-82ee72b054a4> | 2015-10-31 00:59:47 +0000 |
commit | 4a3da3a8a45d5496118798146de1fa4e5798ed5a (patch) | |
tree | 13beeaed3698c61903fe93fb1ce70bd9b18d4e7f /libgo/go/go | |
parent | cd529f4d86a17a3e8959f2cb5ac7132a841ab6f1 (diff) | |
download | gcc-4a3da3a8a45d5496118798146de1fa4e5798ed5a.tar.gz |
runtime: Remove now unnecessary pad field from ParFor.
It is not needed due to the removal of the ctx field.
Reviewed-on: https://go-review.googlesource.com/16525
git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@229616 138bc75d-0d04-0410-961f-82ee72b054a4
Diffstat (limited to 'libgo/go/go')
101 files changed, 19265 insertions, 426 deletions
diff --git a/libgo/go/go/ast/ast.go b/libgo/go/go/ast/ast.go index 312e3d1b989..5ab4283826b 100644 --- a/libgo/go/go/ast/ast.go +++ b/libgo/go/go/ast/ast.go @@ -486,7 +486,7 @@ func (x *MapType) End() token.Pos { return x.Value.End() } func (x *ChanType) End() token.Pos { return x.Value.End() } // exprNode() ensures that only expression/type nodes can be -// assigned to an ExprNode. +// assigned to an Expr. // func (*BadExpr) exprNode() {} func (*Ident) exprNode() {} @@ -562,10 +562,11 @@ type ( // An EmptyStmt node represents an empty statement. // The "position" of the empty statement is the position - // of the immediately preceding semicolon. + // of the immediately following (explicit or implicit) semicolon. // EmptyStmt struct { - Semicolon token.Pos // position of preceding ";" + Semicolon token.Pos // position of following ";" + Implicit bool // if set, ";" was omitted in the source } // A LabeledStmt node represents a labeled statement. @@ -734,6 +735,9 @@ func (s *RangeStmt) Pos() token.Pos { return s.For } func (s *BadStmt) End() token.Pos { return s.To } func (s *DeclStmt) End() token.Pos { return s.Decl.End() } func (s *EmptyStmt) End() token.Pos { + if s.Implicit { + return s.Semicolon + } return s.Semicolon + 1 /* len(";") */ } func (s *LabeledStmt) End() token.Pos { return s.Stmt.End() } @@ -783,7 +787,7 @@ func (s *ForStmt) End() token.Pos { return s.Body.End() } func (s *RangeStmt) End() token.Pos { return s.Body.End() } // stmtNode() ensures that only statement nodes can be -// assigned to a StmtNode. +// assigned to a Stmt. // func (*BadStmt) stmtNode() {} func (*DeclStmt) stmtNode() {} @@ -947,7 +951,7 @@ func (d *FuncDecl) End() token.Pos { } // declNode() ensures that only declaration nodes can be -// assigned to a DeclNode. +// assigned to a Decl. // func (*BadDecl) declNode() {} func (*GenDecl) declNode() {} diff --git a/libgo/go/go/ast/filter.go b/libgo/go/go/ast/filter.go index fc3eeb4a1db..bb571166f47 100644 --- a/libgo/go/go/ast/filter.go +++ b/libgo/go/go/ast/filter.go @@ -23,8 +23,7 @@ func exportFilter(name string) bool { // body) are removed. Non-exported fields and methods of exported types are // stripped. The File.Comments list is not changed. // -// FileExports returns true if there are exported declarations; -// it returns false otherwise. +// FileExports reports whether there are exported declarations. // func FileExports(src *File) bool { return filterFile(src, exportFilter, true) @@ -34,7 +33,7 @@ func FileExports(src *File) bool { // only exported nodes remain. The pkg.Files list is not changed, so that // file names and top-level package comments don't get lost. // -// PackageExports returns true if there are exported declarations; +// PackageExports reports whether there are exported declarations; // it returns false otherwise. // func PackageExports(pkg *Package) bool { @@ -199,8 +198,8 @@ func filterSpecList(list []Spec, f Filter, export bool) []Spec { // all names (including struct field and interface method names, but // not from parameter lists) that don't pass through the filter f. // -// FilterDecl returns true if there are any declared names left after -// filtering; it returns false otherwise. +// FilterDecl reports whether there are any declared names left after +// filtering. // func FilterDecl(decl Decl, f Filter) bool { return filterDecl(decl, f, false) @@ -221,11 +220,11 @@ func filterDecl(decl Decl, f Filter, export bool) bool { // names from top-level declarations (including struct field and // interface method names, but not from parameter lists) that don't // pass through the filter f. If the declaration is empty afterwards, -// the declaration is removed from the AST. The File.Comments list -// is not changed. +// the declaration is removed from the AST. Import declarations are +// always removed. The File.Comments list is not changed. // -// FilterFile returns true if there are any top-level declarations -// left after filtering; it returns false otherwise. +// FilterFile reports whether there are any top-level declarations +// left after filtering. // func FilterFile(src *File, f Filter) bool { return filterFile(src, f, false) @@ -251,8 +250,8 @@ func filterFile(src *File, f Filter, export bool) bool { // changed, so that file names and top-level package comments don't get // lost. // -// FilterPackage returns true if there are any top-level declarations -// left after filtering; it returns false otherwise. +// FilterPackage reports whether there are any top-level declarations +// left after filtering. // func FilterPackage(pkg *Package, f Filter) bool { return filterPackage(pkg, f, false) diff --git a/libgo/go/go/ast/scope.go b/libgo/go/go/ast/scope.go index df1529d1819..1ce5e2e84b5 100644 --- a/libgo/go/go/ast/scope.go +++ b/libgo/go/go/ast/scope.go @@ -38,7 +38,7 @@ func (s *Scope) Lookup(name string) *Object { // Insert attempts to insert a named object obj into the scope s. // If the scope already contains an object alt with the same name, // Insert leaves the scope unchanged and returns alt. Otherwise -// it inserts obj and returns nil." +// it inserts obj and returns nil. // func (s *Scope) Insert(obj *Object) (alt *Object) { if alt = s.Objects[obj.Name]; alt == nil { diff --git a/libgo/go/go/ast/walk.go b/libgo/go/go/ast/walk.go index 73ac38647a2..8ca21959b11 100644 --- a/libgo/go/go/ast/walk.go +++ b/libgo/go/go/ast/walk.go @@ -361,8 +361,7 @@ func Walk(v Visitor, node Node) { } default: - fmt.Printf("ast.Walk: unexpected node type %T", n) - panic("ast.Walk") + panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) } v.Visit(nil) @@ -379,7 +378,8 @@ func (f inspector) Visit(node Node) Visitor { // Inspect traverses an AST in depth-first order: It starts by calling // f(node); node must not be nil. If f returns true, Inspect invokes f -// for all the non-nil children of node, recursively. +// recursively for each of the non-nil children of node, followed by a +// call of f(nil). // func Inspect(node Node, f func(Node) bool) { Walk(inspector(f), node) diff --git a/libgo/go/go/build/build.go b/libgo/go/go/build/build.go index e9247274b6e..42f11655b39 100644 --- a/libgo/go/go/build/build.go +++ b/libgo/go/go/build/build.go @@ -256,10 +256,12 @@ func (ctxt *Context) SrcDirs() []string { // if set, or else the compiled code's GOARCH, GOOS, and GOROOT. var Default Context = defaultContext() +// Also known to cmd/dist/build.go. var cgoEnabled = map[string]bool{ "darwin/386": true, "darwin/amd64": true, - "dragonfly/386": true, + "darwin/arm": true, + "darwin/arm64": true, "dragonfly/amd64": true, "freebsd/386": true, "freebsd/amd64": true, @@ -282,6 +284,7 @@ var cgoEnabled = map[string]bool{ "netbsd/arm": true, "openbsd/386": true, "openbsd/amd64": true, + "solaris/amd64": true, "windows/386": true, "windows/amd64": true, } @@ -300,11 +303,7 @@ func defaultContext() Context { // in all releases >= Go 1.x. Code that requires Go 1.x or later should // say "+build go1.x", and code that should only be built before Go 1.x // (perhaps it is the stub to use in that case) should say "+build !go1.x". - // - // When we reach Go 1.5 the line will read - // c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3", "go1.4", "go1.5"} - // and so on. - c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3", "go1.4"} + c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3", "go1.4", "go1.5"} switch os.Getenv("CGO_ENABLED") { case "1": @@ -361,6 +360,7 @@ type Package struct { Root string // root of Go tree where this package lives SrcRoot string // package source root directory ("" if unknown) PkgRoot string // package install root directory ("" if unknown) + PkgTargetRoot string // architecture dependent install root directory ("" if unknown) BinDir string // command install directory ("" if unknown) Goroot bool // package found in Go root PkgObj string // installed .a file @@ -469,18 +469,21 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa return p, fmt.Errorf("import %q: invalid import path", path) } + var pkgtargetroot string var pkga string var pkgerr error + suffix := "" + if ctxt.InstallSuffix != "" { + suffix = "_" + ctxt.InstallSuffix + } switch ctxt.Compiler { case "gccgo": + pkgtargetroot = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix dir, elem := pathpkg.Split(p.ImportPath) - pkga = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + "/" + dir + "lib" + elem + ".a" + pkga = pkgtargetroot + "/" + dir + "lib" + elem + ".a" case "gc": - suffix := "" - if ctxt.InstallSuffix != "" { - suffix = "_" + ctxt.InstallSuffix - } - pkga = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix + "/" + p.ImportPath + ".a" + pkgtargetroot = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + suffix + pkga = pkgtargetroot + "/" + p.ImportPath + ".a" default: // Save error for end of function. pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler) @@ -496,9 +499,13 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa p.Dir = ctxt.joinPath(srcDir, path) } // Determine canonical import path, if any. + // Exclude results where the import path would include /testdata/. + inTestdata := func(sub string) bool { + return strings.Contains(sub, "/testdata/") || strings.HasSuffix(sub, "/testdata") || strings.HasPrefix(sub, "testdata/") || sub == "testdata" + } if ctxt.GOROOT != "" { root := ctxt.joinPath(ctxt.GOROOT, "src") - if sub, ok := ctxt.hasSubdir(root, p.Dir); ok { + if sub, ok := ctxt.hasSubdir(root, p.Dir); ok && !inTestdata(sub) { p.Goroot = true p.ImportPath = sub p.Root = ctxt.GOROOT @@ -508,7 +515,7 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa all := ctxt.gopath() for i, root := range all { rootsrc := ctxt.joinPath(root, "src") - if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok { + if sub, ok := ctxt.hasSubdir(rootsrc, p.Dir); ok && !inTestdata(sub) { // We found a potential import path for dir, // but check that using it wouldn't find something // else first. @@ -597,6 +604,7 @@ Found: p.PkgRoot = ctxt.joinPath(p.Root, "pkg") p.BinDir = ctxt.joinPath(p.Root, "bin") if pkga != "" { + p.PkgTargetRoot = ctxt.joinPath(p.Root, pkgtargetroot) p.PkgObj = ctxt.joinPath(p.Root, pkga) } } @@ -695,7 +703,11 @@ Found: p.Name = pkg firstFile = name } else if pkg != p.Name { - return p, &MultiplePackageError{p.Dir, []string{firstFile, name}, []string{p.Name, pkg}} + return p, &MultiplePackageError{ + Dir: p.Dir, + Packages: []string{p.Name, pkg}, + Files: []string{firstFile, name}, + } } if pf.Doc != nil && p.Doc == "" { p.Doc = doc.Synopsis(pf.Doc.Text()) @@ -962,7 +974,7 @@ func (ctxt *Context) matchFile(dir, name string, returnImports bool, allTags map } if strings.HasSuffix(filename, ".go") { - data, err = readImports(f, false) + data, err = readImports(f, false, nil) } else { data, err = readComments(f) } @@ -1075,9 +1087,6 @@ func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) bool { // saveCgo saves the information from the #cgo lines in the import "C" comment. // These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives // that affect the way cgo's C code is built. -// -// TODO(rsc): This duplicates code in cgo. -// Once the dust settles, remove this code from cgo. func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) error { text := cg.Text() for _, line := range strings.Split(text, "\n") { @@ -1123,10 +1132,12 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) if err != nil { return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) } - for _, arg := range args { + for i, arg := range args { + arg = expandSrcDir(arg, di.Dir) if !safeCgoName(arg) { return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) } + args[i] = arg } switch verb { @@ -1147,6 +1158,14 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) return nil } +func expandSrcDir(str string, srcdir string) string { + // "\" delimited paths cause safeCgoName to fail + // so convert native paths with a different delimeter + // to "/" before starting (eg: on windows) + srcdir = filepath.ToSlash(srcdir) + return strings.Replace(str, "${SRCDIR}", srcdir, -1) +} + // NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN. // We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay. // See golang.org/issue/6038. @@ -1225,7 +1244,7 @@ func splitQuoted(s string) (r []string, err error) { return args, err } -// match returns true if the name is one of: +// match reports whether the name is one of: // // $GOOS // $GOARCH @@ -1316,7 +1335,7 @@ func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool { // build tag "linux" in that file. For Go 1.4 and beyond, we require this // auto-tagging to apply only to files with a non-empty prefix, so // "foo_linux.go" is tagged but "linux.go" is not. This allows new operating - // sytems, such as android, to arrive without breaking existing code with + // systems, such as android, to arrive without breaking existing code with // innocuous source code in "android.go". The easiest fix: cut everything // in the name before the initial _. i := strings.Index(name, "_") @@ -1390,20 +1409,11 @@ func IsLocalImport(path string) bool { strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../") } -// ArchChar returns the architecture character for the given goarch. -// For example, ArchChar("amd64") returns "6". +// ArchChar returns "?" and an error. +// In earlier versions of Go, the returned string was used to derive +// the compiler and linker tool names, the default object file suffix, +// and the default linker output name. As of Go 1.5, those strings +// no longer vary by architecture; they are compile, link, .o, and a.out, respectively. func ArchChar(goarch string) (string, error) { - switch goarch { - case "386": - return "8", nil - case "amd64", "amd64p32": - return "6", nil - case "arm": - return "5", nil - case "arm64": - return "7", nil - case "ppc64", "ppc64le": - return "9", nil - } - return "", errors.New("unsupported GOARCH " + goarch) + return "?", errors.New("architecture letter no longer used") } diff --git a/libgo/go/go/build/build_test.go b/libgo/go/go/build/build_test.go index a40def0fa0e..2709ca34f54 100644 --- a/libgo/go/go/build/build_test.go +++ b/libgo/go/go/build/build_test.go @@ -94,12 +94,29 @@ func TestEmptyFolderImport(t *testing.T) { func TestMultiplePackageImport(t *testing.T) { _, err := Import(".", "testdata/multi", 0) - if _, ok := err.(*MultiplePackageError); !ok { + mpe, ok := err.(*MultiplePackageError) + if !ok { t.Fatal(`Import("testdata/multi") did not return MultiplePackageError.`) } + want := &MultiplePackageError{ + Dir: filepath.FromSlash("testdata/multi"), + Packages: []string{"main", "test_package"}, + Files: []string{"file.go", "file_appengine.go"}, + } + if !reflect.DeepEqual(mpe, want) { + t.Errorf("got %#v; want %#v", mpe, want) + } } func TestLocalDirectory(t *testing.T) { + t.Skip("does not work with gccgo") + if runtime.GOOS == "darwin" { + switch runtime.GOARCH { + case "arm", "arm64": + t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH) + } + } + cwd, err := os.Getwd() if err != nil { t.Fatal(err) @@ -214,6 +231,14 @@ func TestMatchFile(t *testing.T) { } func TestImportCmd(t *testing.T) { + t.Skip("does not work with gccgo") + if runtime.GOOS == "darwin" { + switch runtime.GOARCH { + case "arm", "arm64": + t.Skipf("skipping on %s/%s, no valid GOROOT", runtime.GOOS, runtime.GOARCH) + } + } + p, err := Import("cmd/internal/objfile", "", 0) if err != nil { t.Fatal(err) @@ -222,3 +247,33 @@ func TestImportCmd(t *testing.T) { t.Fatalf("Import cmd/internal/objfile returned Dir=%q, want %q", filepath.ToSlash(p.Dir), ".../src/cmd/internal/objfile") } } + +var ( + expandSrcDirPath = filepath.Join(string(filepath.Separator)+"projects", "src", "add") +) + +var expandSrcDirTests = []struct { + input, expected string +}{ + {"-L ${SRCDIR}/libs -ladd", "-L /projects/src/add/libs -ladd"}, + {"${SRCDIR}/add_linux_386.a -pthread -lstdc++", "/projects/src/add/add_linux_386.a -pthread -lstdc++"}, + {"Nothing to expand here!", "Nothing to expand here!"}, + {"$", "$"}, + {"$$", "$$"}, + {"${", "${"}, + {"$}", "$}"}, + {"$FOO ${BAR}", "$FOO ${BAR}"}, + {"Find me the $SRCDIRECTORY.", "Find me the $SRCDIRECTORY."}, + {"$SRCDIR is missing braces", "$SRCDIR is missing braces"}, +} + +func TestExpandSrcDir(t *testing.T) { + for _, test := range expandSrcDirTests { + output := expandSrcDir(test.input, expandSrcDirPath) + if output != test.expected { + t.Errorf("%q expands to %q with SRCDIR=%q when %q is expected", test.input, output, expandSrcDirPath, test.expected) + } else { + t.Logf("%q expands to %q with SRCDIR=%q", test.input, output, expandSrcDirPath) + } + } +} diff --git a/libgo/go/go/build/deps_test.go b/libgo/go/go/build/deps_test.go index a335effec3a..68969bbcf86 100644 --- a/libgo/go/go/build/deps_test.go +++ b/libgo/go/go/build/deps_test.go @@ -8,8 +8,15 @@ package build import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" "runtime" "sort" + "strconv" + "strings" "testing" ) @@ -121,9 +128,12 @@ var pkgDeps = map[string][]string{ // End of linear dependency definitions. // Operating system access. - "syscall": {"L0", "unicode/utf16"}, - "time": {"L0", "syscall"}, - "os": {"L1", "os", "syscall", "time"}, + "syscall": {"L0", "unicode/utf16"}, + "internal/syscall/unix": {"L0", "syscall"}, + "internal/syscall/windows": {"L0", "syscall"}, + "internal/syscall/windows/registry": {"L0", "syscall", "unicode/utf16"}, + "time": {"L0", "syscall", "internal/syscall/windows/registry"}, + "os": {"L1", "os", "syscall", "time", "internal/syscall/windows"}, "path/filepath": {"L2", "os", "syscall"}, "io/ioutil": {"L2", "os", "path/filepath", "time"}, "os/exec": {"L2", "os", "path/filepath", "syscall"}, @@ -148,11 +158,13 @@ var pkgDeps = map[string][]string{ "regexp/syntax": {"L2"}, "runtime/debug": {"L2", "fmt", "io/ioutil", "os", "time"}, "runtime/pprof": {"L2", "fmt", "text/tabwriter"}, + "runtime/trace": {"L0"}, "text/tabwriter": {"L2"}, - "testing": {"L2", "flag", "fmt", "os", "runtime/pprof", "time"}, - "testing/iotest": {"L2", "log"}, - "testing/quick": {"L2", "flag", "fmt", "reflect"}, + "testing": {"L2", "flag", "fmt", "os", "runtime/pprof", "runtime/trace", "time"}, + "testing/iotest": {"L2", "log"}, + "testing/quick": {"L2", "flag", "fmt", "reflect"}, + "internal/testenv": {"L2", "os", "testing"}, // L4 is defined as L3+fmt+log+time, because in general once // you're using L3 packages, use of fmt, log, or time is not a big deal. @@ -180,43 +192,60 @@ var pkgDeps = map[string][]string{ "go/token", }, + "go/format": {"L4", "GOPARSER", "internal/format"}, + "internal/format": {"L4", "GOPARSER"}, + + // Go type checking. + "go/constant": {"L4", "go/token", "math/big"}, + "go/importer": {"L4", "go/internal/gcimporter", "go/internal/gccgoimporter", "go/types"}, + "go/internal/gcimporter": {"L4", "OS", "go/build", "go/constant", "go/token", "go/types", "text/scanner"}, + "go/internal/gccgoimporter": {"L4", "OS", "debug/elf", "go/constant", "go/token", "go/types", "text/scanner"}, + "go/types": {"L4", "GOPARSER", "container/heap", "go/constant"}, + // One of a kind. - "archive/tar": {"L4", "OS", "syscall"}, - "archive/zip": {"L4", "OS", "compress/flate"}, - "compress/bzip2": {"L4"}, - "compress/flate": {"L4"}, - "compress/gzip": {"L4", "compress/flate"}, - "compress/lzw": {"L4"}, - "compress/zlib": {"L4", "compress/flate"}, - "database/sql": {"L4", "container/list", "database/sql/driver"}, - "database/sql/driver": {"L4", "time"}, - "debug/dwarf": {"L4"}, - "debug/elf": {"L4", "OS", "debug/dwarf"}, - "debug/gosym": {"L4"}, - "debug/macho": {"L4", "OS", "debug/dwarf"}, - "debug/pe": {"L4", "OS", "debug/dwarf"}, - "encoding": {"L4"}, - "encoding/ascii85": {"L4"}, - "encoding/asn1": {"L4", "math/big"}, - "encoding/csv": {"L4"}, - "encoding/gob": {"L4", "OS", "encoding"}, - "encoding/hex": {"L4"}, - "encoding/json": {"L4", "encoding"}, - "encoding/pem": {"L4"}, - "encoding/xml": {"L4", "encoding"}, - "flag": {"L4", "OS"}, - "go/build": {"L4", "OS", "GOPARSER"}, - "html": {"L4"}, - "image/draw": {"L4"}, - "image/gif": {"L4", "compress/lzw", "image/color/palette", "image/draw"}, - "image/jpeg": {"L4"}, - "image/png": {"L4", "compress/zlib"}, - "index/suffixarray": {"L4", "regexp"}, - "math/big": {"L4"}, - "mime": {"L4", "OS", "syscall"}, - "net/url": {"L4"}, - "text/scanner": {"L4", "OS"}, - "text/template/parse": {"L4"}, + "archive/tar": {"L4", "OS", "syscall"}, + "archive/zip": {"L4", "OS", "compress/flate"}, + "container/heap": {"sort"}, + "compress/bzip2": {"L4"}, + "compress/flate": {"L4"}, + "compress/gzip": {"L4", "compress/flate"}, + "compress/lzw": {"L4"}, + "compress/zlib": {"L4", "compress/flate"}, + "database/sql": {"L4", "container/list", "database/sql/driver"}, + "database/sql/driver": {"L4", "time"}, + "debug/dwarf": {"L4"}, + "debug/elf": {"L4", "OS", "debug/dwarf"}, + "debug/gosym": {"L4"}, + "debug/macho": {"L4", "OS", "debug/dwarf"}, + "debug/pe": {"L4", "OS", "debug/dwarf"}, + "debug/plan9obj": {"L4", "OS"}, + "encoding": {"L4"}, + "encoding/ascii85": {"L4"}, + "encoding/asn1": {"L4", "math/big"}, + "encoding/csv": {"L4"}, + "encoding/gob": {"L4", "OS", "encoding"}, + "encoding/hex": {"L4"}, + "encoding/json": {"L4", "encoding"}, + "encoding/pem": {"L4"}, + "encoding/xml": {"L4", "encoding"}, + "flag": {"L4", "OS"}, + "go/build": {"L4", "OS", "GOPARSER"}, + "html": {"L4"}, + "image/draw": {"L4", "image/internal/imageutil"}, + "image/gif": {"L4", "compress/lzw", "image/color/palette", "image/draw"}, + "image/internal/imageutil": {"L4"}, + "image/jpeg": {"L4", "image/internal/imageutil"}, + "image/png": {"L4", "compress/zlib"}, + "index/suffixarray": {"L4", "regexp"}, + "internal/singleflight": {"sync"}, + "internal/trace": {"L4", "OS"}, + "math/big": {"L4"}, + "mime": {"L4", "OS", "syscall", "internal/syscall/windows/registry"}, + "mime/quotedprintable": {"L4"}, + "net/internal/socktest": {"L4", "OS", "syscall"}, + "net/url": {"L4"}, + "text/scanner": {"L4", "OS"}, + "text/template/parse": {"L4"}, "html/template": { "L4", "OS", "encoding/json", "html", "text/template", @@ -234,13 +263,16 @@ var pkgDeps = map[string][]string{ // that shows up in programs that use cgo. "C": {}, + // Race detector uses cgo. + "runtime/race": {"C"}, + // Plan 9 alone needs io/ioutil and os. "os/user": {"L4", "CGO", "io/ioutil", "os", "syscall"}, // Basic networking. // Because net must be used by any package that wants to // do networking portably, it must have a small dependency set: just L1+basic os. - "net": {"L1", "CGO", "os", "syscall", "time"}, + "net": {"L1", "CGO", "os", "syscall", "time", "internal/syscall/windows", "internal/singleflight"}, // NET enables use of basic network-related packages. "NET": { @@ -252,7 +284,7 @@ var pkgDeps = map[string][]string{ // Uses of networking. "log/syslog": {"L4", "OS", "net"}, - "net/mail": {"L4", "NET", "OS"}, + "net/mail": {"L4", "NET", "OS", "mime"}, "net/textproto": {"L4", "OS", "net"}, // Core crypto. @@ -279,7 +311,7 @@ var pkgDeps = map[string][]string{ // Random byte, number generation. // This would be part of core crypto except that it imports // math/big, which imports fmt. - "crypto/rand": {"L4", "CRYPTO", "OS", "math/big", "syscall", "internal/syscall"}, + "crypto/rand": {"L4", "CRYPTO", "OS", "math/big", "syscall", "internal/syscall/unix"}, // Mathematical crypto: dependencies on fmt (L4) and math/big. // We could avoid some of the fmt, but math/big imports fmt anyway. @@ -311,7 +343,7 @@ var pkgDeps = map[string][]string{ "crypto/x509/pkix": {"L4", "CRYPTO-MATH"}, // Simple net+crypto-aware packages. - "mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto"}, + "mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto", "mime/quotedprintable"}, "net/smtp": {"L4", "CRYPTO", "NET", "crypto/tls"}, // HTTP, kingpin of dependencies. @@ -320,16 +352,18 @@ var pkgDeps = map[string][]string{ "compress/gzip", "crypto/tls", "mime/multipart", "runtime/debug", "net/http/internal", }, + "net/http/internal": {"L4"}, // HTTP-using packages. - "expvar": {"L4", "OS", "encoding/json", "net/http"}, - "net/http/cgi": {"L4", "NET", "OS", "crypto/tls", "net/http", "regexp"}, - "net/http/fcgi": {"L4", "NET", "OS", "net/http", "net/http/cgi"}, - "net/http/httptest": {"L4", "NET", "OS", "crypto/tls", "flag", "net/http"}, - "net/http/httputil": {"L4", "NET", "OS", "net/http", "net/http/internal"}, - "net/http/pprof": {"L4", "OS", "html/template", "net/http", "runtime/pprof"}, - "net/rpc": {"L4", "NET", "encoding/gob", "html/template", "net/http"}, - "net/rpc/jsonrpc": {"L4", "NET", "encoding/json", "net/rpc"}, + "expvar": {"L4", "OS", "encoding/json", "net/http"}, + "net/http/cgi": {"L4", "NET", "OS", "crypto/tls", "net/http", "regexp"}, + "net/http/cookiejar": {"L4", "NET", "net/http"}, + "net/http/fcgi": {"L4", "NET", "OS", "net/http", "net/http/cgi"}, + "net/http/httptest": {"L4", "NET", "OS", "crypto/tls", "flag", "net/http"}, + "net/http/httputil": {"L4", "NET", "OS", "net/http", "net/http/internal"}, + "net/http/pprof": {"L4", "OS", "html/template", "net/http", "runtime/pprof", "runtime/trace"}, + "net/rpc": {"L4", "NET", "encoding/gob", "html/template", "net/http"}, + "net/rpc/jsonrpc": {"L4", "NET", "encoding/json", "net/rpc"}, } // isMacro reports whether p is a package dependency macro @@ -375,69 +409,112 @@ var allowedErrors = map[osPkg]bool{ osPkg{"plan9", "log/syslog"}: true, } +// listStdPkgs returns the same list of packages as "go list std". +func listStdPkgs(goroot string) ([]string, error) { + // Based on cmd/go's matchPackages function. + var pkgs []string + + src := filepath.Join(goroot, "src") + string(filepath.Separator) + walkFn := func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() || path == src { + return nil + } + + base := filepath.Base(path) + if strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") || base == "testdata" { + return filepath.SkipDir + } + + name := filepath.ToSlash(path[len(src):]) + if name == "builtin" || name == "cmd" || strings.Contains(name, ".") { + return filepath.SkipDir + } + + pkgs = append(pkgs, name) + return nil + } + if err := filepath.Walk(src, walkFn); err != nil { + return nil, err + } + return pkgs, nil +} + func TestDependencies(t *testing.T) { - if runtime.GOOS == "nacl" { - // NaCl tests run in a limited file system and we do not + iOS := runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") + if runtime.GOOS == "nacl" || iOS { + // Tests run in a limited file system and we do not // provide access to every source file. - t.Skip("skipping on NaCl") + t.Skipf("skipping on %s/%s, missing full GOROOT", runtime.GOOS, runtime.GOARCH) } - var all []string - for k := range pkgDeps { - all = append(all, k) + ctxt := Default + all, err := listStdPkgs(ctxt.GOROOT) + if err != nil { + t.Fatal(err) } sort.Strings(all) - ctxt := Default test := func(mustImport bool) { for _, pkg := range all { - if isMacro(pkg) { - continue - } - if pkg == "runtime/cgo" && !ctxt.CgoEnabled { - continue - } - p, err := ctxt.Import(pkg, "", 0) + imports, err := findImports(pkg) if err != nil { - if allowedErrors[osPkg{ctxt.GOOS, pkg}] { - continue - } - if !ctxt.CgoEnabled && pkg == "runtime/cgo" { - continue - } - // Some of the combinations we try might not - // be reasonable (like arm,plan9,cgo), so ignore - // errors for the auto-generated combinations. - if !mustImport { - continue - } - t.Errorf("%s/%s/cgo=%v %v", ctxt.GOOS, ctxt.GOARCH, ctxt.CgoEnabled, err) + t.Error(err) continue } ok := allowed(pkg) var bad []string - for _, imp := range p.Imports { + for _, imp := range imports { if !ok[imp] { bad = append(bad, imp) } } if bad != nil { - t.Errorf("%s/%s/cgo=%v unexpected dependency: %s imports %v", ctxt.GOOS, ctxt.GOARCH, ctxt.CgoEnabled, pkg, bad) + t.Errorf("unexpected dependency: %s imports %v", pkg, bad) } } } test(true) +} - if testing.Short() { - t.Logf("skipping other systems") - return - } +var buildIgnore = []byte("\n// +build ignore") - for _, ctxt.GOOS = range geese { - for _, ctxt.GOARCH = range goarches { - for _, ctxt.CgoEnabled = range bools { - test(false) +func findImports(pkg string) ([]string, error) { + dir := filepath.Join(Default.GOROOT, "src", pkg) + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + var imports []string + var haveImport = map[string]bool{} + for _, file := range files { + name := file.Name() + if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { + continue + } + f, err := os.Open(filepath.Join(dir, name)) + if err != nil { + return nil, err + } + var imp []string + data, err := readImports(f, false, &imp) + f.Close() + if err != nil { + return nil, fmt.Errorf("reading %v: %v", name, err) + } + if bytes.Contains(data, buildIgnore) { + continue + } + for _, quoted := range imp { + path, err := strconv.Unquote(quoted) + if err != nil { + continue + } + if !haveImport[path] { + haveImport[path] = true + imports = append(imports, path) } } } + sort.Strings(imports) + return imports, nil } diff --git a/libgo/go/go/build/doc.go b/libgo/go/go/build/doc.go index 75a827bb91f..233f8b989d5 100644 --- a/libgo/go/go/build/doc.go +++ b/libgo/go/go/build/doc.go @@ -101,6 +101,7 @@ // - "go1.2", from Go version 1.2 onward // - "go1.3", from Go version 1.3 onward // - "go1.4", from Go version 1.4 onward +// - "go1.5", from Go version 1.5 onward // - any additional words listed in ctxt.BuildTags // // If a file's name, after stripping the extension and a possible _test suffix, @@ -111,7 +112,7 @@ // (example: source_windows_amd64.go) where GOOS and GOARCH represent // any known operating system and architecture values respectively, then // the file is considered to have an implicit build constraint requiring -// those terms. +// those terms (in addition to any explicit constraints in the file). // // To keep a file from being considered for the build: // diff --git a/libgo/go/go/build/read.go b/libgo/go/go/build/read.go index c8079dfd15d..1049ac50d94 100644 --- a/libgo/go/go/build/read.go +++ b/libgo/go/go/build/read.go @@ -146,11 +146,15 @@ func (r *importReader) readIdent() { // readString reads a quoted string literal from the input. // If an identifier is not present, readString records a syntax error. -func (r *importReader) readString() { +func (r *importReader) readString(save *[]string) { switch r.nextByte(true) { case '`': + start := len(r.buf) - 1 for r.err == nil { if r.nextByte(false) == '`' { + if save != nil { + *save = append(*save, string(r.buf[start:])) + } break } if r.eof { @@ -158,9 +162,13 @@ func (r *importReader) readString() { } } case '"': + start := len(r.buf) - 1 for r.err == nil { c := r.nextByte(false) if c == '"' { + if save != nil { + *save = append(*save, string(r.buf[start:])) + } break } if r.eof || c == '\n' { @@ -177,14 +185,14 @@ func (r *importReader) readString() { // readImport reads an import clause - optional identifier followed by quoted string - // from the input. -func (r *importReader) readImport() { +func (r *importReader) readImport(imports *[]string) { c := r.peekByte(true) if c == '.' { r.peek = 0 } else if isIdent(c) { r.readIdent() } - r.readString() + r.readString(imports) } // readComments is like ioutil.ReadAll, except that it only reads the leading @@ -201,7 +209,7 @@ func readComments(f io.Reader) ([]byte, error) { // readImports is like ioutil.ReadAll, except that it expects a Go file as input // and stops reading the input once the imports have completed. -func readImports(f io.Reader, reportSyntaxError bool) ([]byte, error) { +func readImports(f io.Reader, reportSyntaxError bool, imports *[]string) ([]byte, error) { r := &importReader{b: bufio.NewReader(f)} r.readKeyword("package") @@ -211,11 +219,11 @@ func readImports(f io.Reader, reportSyntaxError bool) ([]byte, error) { if r.peekByte(true) == '(' { r.nextByte(false) for r.peekByte(true) != ')' && r.err == nil { - r.readImport() + r.readImport(imports) } r.nextByte(false) } else { - r.readImport() + r.readImport(imports) } } diff --git a/libgo/go/go/build/read_test.go b/libgo/go/go/build/read_test.go index 2dcc1208f71..326960bdc91 100644 --- a/libgo/go/go/build/read_test.go +++ b/libgo/go/go/build/read_test.go @@ -131,7 +131,7 @@ func testRead(t *testing.T, tests []readTest, read func(io.Reader) ([]byte, erro } func TestReadImports(t *testing.T) { - testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true) }) + testRead(t, readImportsTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) }) } func TestReadComments(t *testing.T) { @@ -207,7 +207,7 @@ var readFailuresTests = []readTest{ func TestReadFailures(t *testing.T) { // Errors should be reported (true arg to readImports). - testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true) }) + testRead(t, readFailuresTests, func(r io.Reader) ([]byte, error) { return readImports(r, true, nil) }) } func TestReadFailuresIgnored(t *testing.T) { @@ -222,5 +222,5 @@ func TestReadFailuresIgnored(t *testing.T) { tt.err = "" } } - testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false) }) + testRead(t, tests, func(r io.Reader) ([]byte, error) { return readImports(r, false, nil) }) } diff --git a/libgo/go/go/build/syslist.go b/libgo/go/go/build/syslist.go index 0bf4b1573c9..d800a78f0a1 100644 --- a/libgo/go/go/build/syslist.go +++ b/libgo/go/go/build/syslist.go @@ -5,4 +5,4 @@ package build const goosList = "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows " -const goarchList = "386 amd64 amd64p32 arm arm64 alpha m68k mipso32 mipsn32 mipsn64 mipso64 ppc ppc64 ppc64le s390 s390x sparc sparc64 " +const goarchList = "386 amd64 amd64p32 arm armbe arm64 arm64be alpha m68k ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le mipso32 mipsn32 mipsn64 mipso64 ppc s390 s390x sparc sparc64 " diff --git a/libgo/go/go/build/testdata/empty/dummy b/libgo/go/go/build/testdata/empty/dummy new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/libgo/go/go/build/testdata/empty/dummy diff --git a/libgo/go/go/constant/go13.go b/libgo/go/go/constant/go13.go new file mode 100644 index 00000000000..a4a838a2908 --- /dev/null +++ b/libgo/go/go/constant/go13.go @@ -0,0 +1,24 @@ +// Copyright 2014 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. + +// +build !go1.4 + +package constant + +import ( + "math" + "math/big" +) + +func ratToFloat32(x *big.Rat) (float32, bool) { + // Before 1.4, there's no Rat.Float32. + // Emulate it, albeit at the cost of + // imprecision in corner cases. + x64, exact := x.Float64() + x32 := float32(x64) + if math.IsInf(float64(x32), 0) { + exact = false + } + return x32, exact +} diff --git a/libgo/go/go/constant/go14.go b/libgo/go/go/constant/go14.go new file mode 100644 index 00000000000..2ab6da02f67 --- /dev/null +++ b/libgo/go/go/constant/go14.go @@ -0,0 +1,13 @@ +// Copyright 2014 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. + +// +build go1.4 + +package constant + +import "math/big" + +func ratToFloat32(x *big.Rat) (float32, bool) { + return x.Float32() +} diff --git a/libgo/go/go/constant/value.go b/libgo/go/go/constant/value.go new file mode 100644 index 00000000000..79a80af1ab1 --- /dev/null +++ b/libgo/go/go/constant/value.go @@ -0,0 +1,925 @@ +// Copyright 2013 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 constant implements Values representing untyped +// Go constants and the corresponding operations. Values +// and operations may have arbitrary or unlimited precision. +// +// A special Unknown value may be used when a value +// is unknown due to an error. Operations on unknown +// values produce unknown values unless specified +// otherwise. +// +package constant // import "go/constant" + +import ( + "fmt" + "go/token" + "math/big" + "strconv" +) + +// Kind specifies the kind of value represented by a Value. +type Kind int + +// Implementation note: Kinds must be enumerated in +// order of increasing "complexity" (used by match). + +const ( + // unknown values + Unknown Kind = iota + + // non-numeric values + Bool + String + + // numeric values + Int + Float + Complex +) + +// A Value represents a mathematically exact value of a given Kind. +type Value interface { + // Kind returns the value kind; it is always the smallest + // kind in which the value can be represented exactly. + Kind() Kind + + // String returns a human-readable form of the value. + String() string + + // Prevent external implementations. + implementsValue() +} + +// ---------------------------------------------------------------------------- +// Implementations + +type ( + unknownVal struct{} + boolVal bool + stringVal string + int64Val int64 + intVal struct{ val *big.Int } + floatVal struct{ val *big.Rat } + complexVal struct{ re, im *big.Rat } +) + +func (unknownVal) Kind() Kind { return Unknown } +func (boolVal) Kind() Kind { return Bool } +func (stringVal) Kind() Kind { return String } +func (int64Val) Kind() Kind { return Int } +func (intVal) Kind() Kind { return Int } +func (floatVal) Kind() Kind { return Float } +func (complexVal) Kind() Kind { return Complex } + +func (unknownVal) String() string { return "unknown" } +func (x boolVal) String() string { return fmt.Sprintf("%v", bool(x)) } +func (x stringVal) String() string { return strconv.Quote(string(x)) } +func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } +func (x intVal) String() string { return x.val.String() } +func (x floatVal) String() string { return x.val.String() } +func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) } + +func (unknownVal) implementsValue() {} +func (boolVal) implementsValue() {} +func (stringVal) implementsValue() {} +func (int64Val) implementsValue() {} +func (intVal) implementsValue() {} +func (floatVal) implementsValue() {} +func (complexVal) implementsValue() {} + +// int64 bounds +var ( + minInt64 = big.NewInt(-1 << 63) + maxInt64 = big.NewInt(1<<63 - 1) +) + +func normInt(x *big.Int) Value { + if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 { + return int64Val(x.Int64()) + } + return intVal{x} +} + +func normFloat(x *big.Rat) Value { + if x.IsInt() { + return normInt(x.Num()) + } + return floatVal{x} +} + +func normComplex(re, im *big.Rat) Value { + if im.Sign() == 0 { + return normFloat(re) + } + return complexVal{re, im} +} + +// ---------------------------------------------------------------------------- +// Factories + +// MakeUnknown returns the Unknown value. +func MakeUnknown() Value { return unknownVal{} } + +// MakeBool returns the Bool value for x. +func MakeBool(b bool) Value { return boolVal(b) } + +// MakeString returns the String value for x. +func MakeString(s string) Value { return stringVal(s) } + +// MakeInt64 returns the Int value for x. +func MakeInt64(x int64) Value { return int64Val(x) } + +// MakeUint64 returns the Int value for x. +func MakeUint64(x uint64) Value { return normInt(new(big.Int).SetUint64(x)) } + +// MakeFloat64 returns the numeric value for x. +// If x is not finite, the result is unknown. +func MakeFloat64(x float64) Value { + if f := new(big.Rat).SetFloat64(x); f != nil { + return normFloat(f) + } + return unknownVal{} +} + +// MakeFromLiteral returns the corresponding integer, floating-point, +// imaginary, character, or string value for a Go literal string. +// If prec > 0, prec specifies an upper limit for the precision of +// a numeric value. If the literal string is invalid, the result is +// nil. +// BUG(gri) Only prec == 0 is supported at the moment. +func MakeFromLiteral(lit string, tok token.Token, prec uint) Value { + if prec != 0 { + panic("limited precision not supported") + } + switch tok { + case token.INT: + if x, err := strconv.ParseInt(lit, 0, 64); err == nil { + return int64Val(x) + } + if x, ok := new(big.Int).SetString(lit, 0); ok { + return intVal{x} + } + + case token.FLOAT: + if x, ok := new(big.Rat).SetString(lit); ok { + return normFloat(x) + } + + case token.IMAG: + if n := len(lit); n > 0 && lit[n-1] == 'i' { + if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok { + return normComplex(big.NewRat(0, 1), im) + } + } + + case token.CHAR: + if n := len(lit); n >= 2 { + if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { + return int64Val(code) + } + } + + case token.STRING: + if s, err := strconv.Unquote(lit); err == nil { + return stringVal(s) + } + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Accessors +// +// For unknown arguments the result is the zero value for the respective +// accessor type, except for Sign, where the result is 1. + +// BoolVal returns the Go boolean value of x, which must be a Bool or an Unknown. +// If x is Unknown, the result is false. +func BoolVal(x Value) bool { + switch x := x.(type) { + case boolVal: + return bool(x) + case unknownVal: + return false + } + panic(fmt.Sprintf("%v not a Bool", x)) +} + +// StringVal returns the Go string value of x, which must be a String or an Unknown. +// If x is Unknown, the result is "". +func StringVal(x Value) string { + switch x := x.(type) { + case stringVal: + return string(x) + case unknownVal: + return "" + } + panic(fmt.Sprintf("%v not a String", x)) +} + +// Int64Val returns the Go int64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Int64Val(x Value) (int64, bool) { + switch x := x.(type) { + case int64Val: + return int64(x), true + case intVal: + return x.val.Int64(), x.val.BitLen() <= 63 + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not an Int", x)) +} + +// Uint64Val returns the Go uint64 value of x and whether the result is exact; +// x must be an Int or an Unknown. If the result is not exact, its value is undefined. +// If x is Unknown, the result is (0, false). +func Uint64Val(x Value) (uint64, bool) { + switch x := x.(type) { + case int64Val: + return uint64(x), x >= 0 + case intVal: + return x.val.Uint64(), x.val.Sign() >= 0 && x.val.BitLen() <= 64 + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not an Int", x)) +} + +// Float32Val is like Float64Val but for float32 instead of float64. +func Float32Val(x Value) (float32, bool) { + switch x := x.(type) { + case int64Val: + f := float32(x) + return f, int64Val(f) == x + case intVal: + return ratToFloat32(new(big.Rat).SetFrac(x.val, int1)) + case floatVal: + return ratToFloat32(x.val) + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not a Float", x)) +} + +// Float64Val returns the nearest Go float64 value of x and whether the result is exact; +// x must be numeric but not Complex, or Unknown. For values too small (too close to 0) +// to represent as float64, Float64Val silently underflows to 0. The result sign always +// matches the sign of x, even for 0. +// If x is Unknown, the result is (0, false). +func Float64Val(x Value) (float64, bool) { + switch x := x.(type) { + case int64Val: + f := float64(int64(x)) + return f, int64Val(f) == x + case intVal: + return new(big.Rat).SetFrac(x.val, int1).Float64() + case floatVal: + return x.val.Float64() + case unknownVal: + return 0, false + } + panic(fmt.Sprintf("%v not a Float", x)) +} + +// BitLen returns the number of bits required to represent +// the absolute value x in binary representation; x must be an Int or an Unknown. +// If x is Unknown, the result is 0. +func BitLen(x Value) int { + switch x := x.(type) { + case int64Val: + return new(big.Int).SetInt64(int64(x)).BitLen() + case intVal: + return x.val.BitLen() + case unknownVal: + return 0 + } + panic(fmt.Sprintf("%v not an Int", x)) +} + +// Sign returns -1, 0, or 1 depending on whether x < 0, x == 0, or x > 0; +// x must be numeric or Unknown. For complex values x, the sign is 0 if x == 0, +// otherwise it is != 0. If x is Unknown, the result is 1. +func Sign(x Value) int { + switch x := x.(type) { + case int64Val: + switch { + case x < 0: + return -1 + case x > 0: + return 1 + } + return 0 + case intVal: + return x.val.Sign() + case floatVal: + return x.val.Sign() + case complexVal: + return x.re.Sign() | x.im.Sign() + case unknownVal: + return 1 // avoid spurious division by zero errors + } + panic(fmt.Sprintf("%v not numeric", x)) +} + +// ---------------------------------------------------------------------------- +// Support for serializing/deserializing integers + +const ( + // Compute the size of a Word in bytes. + _m = ^big.Word(0) + _log = _m>>8&1 + _m>>16&1 + _m>>32&1 + wordSize = 1 << _log +) + +// Bytes returns the bytes for the absolute value of x in little- +// endian binary representation; x must be an Int. +func Bytes(x Value) []byte { + var val *big.Int + switch x := x.(type) { + case int64Val: + val = new(big.Int).SetInt64(int64(x)) + case intVal: + val = x.val + default: + panic(fmt.Sprintf("%v not an Int", x)) + } + + words := val.Bits() + bytes := make([]byte, len(words)*wordSize) + + i := 0 + for _, w := range words { + for j := 0; j < wordSize; j++ { + bytes[i] = byte(w) + w >>= 8 + i++ + } + } + // remove leading 0's + for i > 0 && bytes[i-1] == 0 { + i-- + } + + return bytes[:i] +} + +// MakeFromBytes returns the Int value given the bytes of its little-endian +// binary representation. An empty byte slice argument represents 0. +func MakeFromBytes(bytes []byte) Value { + words := make([]big.Word, (len(bytes)+(wordSize-1))/wordSize) + + i := 0 + var w big.Word + var s uint + for _, b := range bytes { + w |= big.Word(b) << s + if s += 8; s == wordSize*8 { + words[i] = w + i++ + w = 0 + s = 0 + } + } + // store last word + if i < len(words) { + words[i] = w + i++ + } + // remove leading 0's + for i > 0 && words[i-1] == 0 { + i-- + } + + return normInt(new(big.Int).SetBits(words[:i])) +} + +// ---------------------------------------------------------------------------- +// Support for disassembling fractions + +// Num returns the numerator of x; x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown, otherwise it is an Int +// with the same sign as x. +func Num(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal: + return x + case floatVal: + return normInt(x.val.Num()) + } + panic(fmt.Sprintf("%v not Int or Float", x)) +} + +// Denom returns the denominator of x; x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown, otherwise it is an Int >= 1. +func Denom(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal: + return int64Val(1) + case floatVal: + return normInt(x.val.Denom()) + } + panic(fmt.Sprintf("%v not Int or Float", x)) +} + +// ---------------------------------------------------------------------------- +// Support for assembling/disassembling complex numbers + +// MakeImag returns the numeric value x*i (possibly 0); +// x must be Int, Float, or Unknown. +// If x is Unknown, the result is Unknown. +func MakeImag(x Value) Value { + var im *big.Rat + switch x := x.(type) { + case unknownVal: + return x + case int64Val: + im = big.NewRat(int64(x), 1) + case intVal: + im = new(big.Rat).SetFrac(x.val, int1) + case floatVal: + im = x.val + default: + panic(fmt.Sprintf("%v not Int or Float", x)) + } + return normComplex(rat0, im) +} + +// Real returns the real part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Real(x Value) Value { + switch x := x.(type) { + case unknownVal, int64Val, intVal, floatVal: + return x + case complexVal: + return normFloat(x.re) + } + panic(fmt.Sprintf("%v not numeric", x)) +} + +// Imag returns the imaginary part of x, which must be a numeric or unknown value. +// If x is Unknown, the result is Unknown. +func Imag(x Value) Value { + switch x := x.(type) { + case unknownVal: + return x + case int64Val, intVal, floatVal: + return int64Val(0) + case complexVal: + return normFloat(x.im) + } + panic(fmt.Sprintf("%v not numeric", x)) +} + +// ---------------------------------------------------------------------------- +// Operations + +// is32bit reports whether x can be represented using 32 bits. +func is32bit(x int64) bool { + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// is63bit reports whether x can be represented using 63 bits. +func is63bit(x int64) bool { + const s = 63 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// UnaryOp returns the result of the unary expression op y. +// The operation must be defined for the operand. +// If prec > 0 it specifies the ^ (xor) result size in bits. +// If y is Unknown, the result is Unknown. +// +func UnaryOp(op token.Token, y Value, prec uint) Value { + switch op { + case token.ADD: + switch y.(type) { + case unknownVal, int64Val, intVal, floatVal, complexVal: + return y + } + + case token.SUB: + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + if z := -y; z != y { + return z // no overflow + } + return normInt(new(big.Int).Neg(big.NewInt(int64(y)))) + case intVal: + return normInt(new(big.Int).Neg(y.val)) + case floatVal: + return normFloat(new(big.Rat).Neg(y.val)) + case complexVal: + return normComplex(new(big.Rat).Neg(y.re), new(big.Rat).Neg(y.im)) + } + + case token.XOR: + var z big.Int + switch y := y.(type) { + case unknownVal: + return y + case int64Val: + z.Not(big.NewInt(int64(y))) + case intVal: + z.Not(y.val) + default: + goto Error + } + // For unsigned types, the result will be negative and + // thus "too large": We must limit the result precision + // to the type's precision. + if prec > 0 { + z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), prec)) // z &^= (-1)<<prec + } + return normInt(&z) + + case token.NOT: + switch y := y.(type) { + case unknownVal: + return y + case boolVal: + return !y + } + } + +Error: + panic(fmt.Sprintf("invalid unary operation %s%v", op, y)) +} + +var ( + int1 = big.NewInt(1) + rat0 = big.NewRat(0, 1) +) + +func ord(x Value) int { + switch x.(type) { + default: + return 0 + case boolVal, stringVal: + return 1 + case int64Val: + return 2 + case intVal: + return 3 + case floatVal: + return 4 + case complexVal: + return 5 + } +} + +// match returns the matching representation (same type) with the +// smallest complexity for two values x and y. If one of them is +// numeric, both of them must be numeric. If one of them is Unknown, +// both results are Unknown. +// +func match(x, y Value) (_, _ Value) { + if ord(x) > ord(y) { + y, x = match(y, x) + return x, y + } + // ord(x) <= ord(y) + + switch x := x.(type) { + case unknownVal: + return x, x + + case boolVal, stringVal, complexVal: + return x, y + + case int64Val: + switch y := y.(type) { + case int64Val: + return x, y + case intVal: + return intVal{big.NewInt(int64(x))}, y + case floatVal: + return floatVal{big.NewRat(int64(x), 1)}, y + case complexVal: + return complexVal{big.NewRat(int64(x), 1), rat0}, y + } + + case intVal: + switch y := y.(type) { + case intVal: + return x, y + case floatVal: + return floatVal{new(big.Rat).SetFrac(x.val, int1)}, y + case complexVal: + return complexVal{new(big.Rat).SetFrac(x.val, int1), rat0}, y + } + + case floatVal: + switch y := y.(type) { + case floatVal: + return x, y + case complexVal: + return complexVal{x.val, rat0}, y + } + } + + panic("unreachable") +} + +// BinaryOp returns the result of the binary expression x op y. +// The operation must be defined for the operands. If one of the +// operands is Unknown, the result is Unknown. +// To force integer division of Int operands, use op == token.QUO_ASSIGN +// instead of token.QUO; the result is guaranteed to be Int in this case. +// Division by zero leads to a run-time panic. +// +func BinaryOp(x Value, op token.Token, y Value) Value { + x, y = match(x, y) + + switch x := x.(type) { + case unknownVal: + return x + + case boolVal: + y := y.(boolVal) + switch op { + case token.LAND: + return x && y + case token.LOR: + return x || y + } + + case int64Val: + a := int64(x) + b := int64(y.(int64Val)) + var c int64 + switch op { + case token.ADD: + if !is63bit(a) || !is63bit(b) { + return normInt(new(big.Int).Add(big.NewInt(a), big.NewInt(b))) + } + c = a + b + case token.SUB: + if !is63bit(a) || !is63bit(b) { + return normInt(new(big.Int).Sub(big.NewInt(a), big.NewInt(b))) + } + c = a - b + case token.MUL: + if !is32bit(a) || !is32bit(b) { + return normInt(new(big.Int).Mul(big.NewInt(a), big.NewInt(b))) + } + c = a * b + case token.QUO: + return normFloat(new(big.Rat).SetFrac(big.NewInt(a), big.NewInt(b))) + case token.QUO_ASSIGN: // force integer division + c = a / b + case token.REM: + c = a % b + case token.AND: + c = a & b + case token.OR: + c = a | b + case token.XOR: + c = a ^ b + case token.AND_NOT: + c = a &^ b + default: + goto Error + } + return int64Val(c) + + case intVal: + a := x.val + b := y.(intVal).val + var c big.Int + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + return normFloat(new(big.Rat).SetFrac(a, b)) + case token.QUO_ASSIGN: // force integer division + c.Quo(a, b) + case token.REM: + c.Rem(a, b) + case token.AND: + c.And(a, b) + case token.OR: + c.Or(a, b) + case token.XOR: + c.Xor(a, b) + case token.AND_NOT: + c.AndNot(a, b) + default: + goto Error + } + return normInt(&c) + + case floatVal: + a := x.val + b := y.(floatVal).val + var c big.Rat + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return normFloat(&c) + + case complexVal: + y := y.(complexVal) + a, b := x.re, x.im + c, d := y.re, y.im + var re, im big.Rat + switch op { + case token.ADD: + // (a+c) + i(b+d) + re.Add(a, c) + im.Add(b, d) + case token.SUB: + // (a-c) + i(b-d) + re.Sub(a, c) + im.Sub(b, d) + case token.MUL: + // (ac-bd) + i(bc+ad) + var ac, bd, bc, ad big.Rat + ac.Mul(a, c) + bd.Mul(b, d) + bc.Mul(b, c) + ad.Mul(a, d) + re.Sub(&ac, &bd) + im.Add(&bc, &ad) + case token.QUO: + // (ac+bd)/s + i(bc-ad)/s, with s = cc + dd + var ac, bd, bc, ad, s, cc, dd big.Rat + ac.Mul(a, c) + bd.Mul(b, d) + bc.Mul(b, c) + ad.Mul(a, d) + cc.Mul(c, c) + dd.Mul(d, d) + s.Add(&cc, &dd) + re.Add(&ac, &bd) + re.Quo(&re, &s) + im.Sub(&bc, &ad) + im.Quo(&im, &s) + default: + goto Error + } + return normComplex(&re, &im) + + case stringVal: + if op == token.ADD { + return x + y.(stringVal) + } + } + +Error: + panic(fmt.Sprintf("invalid binary operation %v %s %v", x, op, y)) +} + +// Shift returns the result of the shift expression x op s +// with op == token.SHL or token.SHR (<< or >>). x must be +// an Int or an Unknown. If x is Unknown, the result is x. +// +func Shift(x Value, op token.Token, s uint) Value { + switch x := x.(type) { + case unknownVal: + return x + + case int64Val: + if s == 0 { + return x + } + switch op { + case token.SHL: + z := big.NewInt(int64(x)) + return normInt(z.Lsh(z, s)) + case token.SHR: + return x >> s + } + + case intVal: + if s == 0 { + return x + } + var z big.Int + switch op { + case token.SHL: + return normInt(z.Lsh(x.val, s)) + case token.SHR: + return normInt(z.Rsh(x.val, s)) + } + } + + panic(fmt.Sprintf("invalid shift %v %s %d", x, op, s)) +} + +func cmpZero(x int, op token.Token) bool { + switch op { + case token.EQL: + return x == 0 + case token.NEQ: + return x != 0 + case token.LSS: + return x < 0 + case token.LEQ: + return x <= 0 + case token.GTR: + return x > 0 + case token.GEQ: + return x >= 0 + } + panic("unreachable") +} + +// Compare returns the result of the comparison x op y. +// The comparison must be defined for the operands. +// If one of the operands is Unknown, the result is +// false. +// +func Compare(x Value, op token.Token, y Value) bool { + x, y = match(x, y) + + switch x := x.(type) { + case unknownVal: + return false + + case boolVal: + y := y.(boolVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + } + + case int64Val: + y := y.(int64Val) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + + case intVal: + return cmpZero(x.val.Cmp(y.(intVal).val), op) + + case floatVal: + return cmpZero(x.val.Cmp(y.(floatVal).val), op) + + case complexVal: + y := y.(complexVal) + re := x.re.Cmp(y.re) + im := x.im.Cmp(y.im) + switch op { + case token.EQL: + return re == 0 && im == 0 + case token.NEQ: + return re != 0 || im != 0 + } + + case stringVal: + y := y.(stringVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + } + + panic(fmt.Sprintf("invalid comparison %v %s %v", x, op, y)) +} diff --git a/libgo/go/go/constant/value_test.go b/libgo/go/go/constant/value_test.go new file mode 100644 index 00000000000..08cdd5e625c --- /dev/null +++ b/libgo/go/go/constant/value_test.go @@ -0,0 +1,375 @@ +// Copyright 2013 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 constant + +import ( + "go/token" + "strings" + "testing" +) + +// TODO(gri) expand this test framework + +var opTests = []string{ + // unary operations + `+ 0 = 0`, + `+ ? = ?`, + `- 1 = -1`, + `- ? = ?`, + `^ 0 = -1`, + `^ ? = ?`, + + `! true = false`, + `! false = true`, + `! ? = ?`, + + // etc. + + // binary operations + `"" + "" = ""`, + `"foo" + "" = "foo"`, + `"" + "bar" = "bar"`, + `"foo" + "bar" = "foobar"`, + + `0 + 0 = 0`, + `0 + 0.1 = 0.1`, + `0 + 0.1i = 0.1i`, + `0.1 + 0.9 = 1`, + `1e100 + 1e100 = 2e100`, + `? + 0 = ?`, + `0 + ? = ?`, + + `0 - 0 = 0`, + `0 - 0.1 = -0.1`, + `0 - 0.1i = -0.1i`, + `1e100 - 1e100 = 0`, + `? - 0 = ?`, + `0 - ? = ?`, + + `0 * 0 = 0`, + `1 * 0.1 = 0.1`, + `1 * 0.1i = 0.1i`, + `1i * 1i = -1`, + `? * 0 = ?`, + `0 * ? = ?`, + + `0 / 0 = "division_by_zero"`, + `10 / 2 = 5`, + `5 / 3 = 5/3`, + `5i / 3i = 5/3`, + `? / 0 = ?`, + `0 / ? = ?`, + + `0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for / + `10 % 3 = 1`, + `? % 0 = ?`, + `0 % ? = ?`, + + `0 & 0 = 0`, + `12345 & 0 = 0`, + `0xff & 0xf = 0xf`, + `? & 0 = ?`, + `0 & ? = ?`, + + `0 | 0 = 0`, + `12345 | 0 = 12345`, + `0xb | 0xa0 = 0xab`, + `? | 0 = ?`, + `0 | ? = ?`, + + `0 ^ 0 = 0`, + `1 ^ -1 = -2`, + `? ^ 0 = ?`, + `0 ^ ? = ?`, + + `0 &^ 0 = 0`, + `0xf &^ 1 = 0xe`, + `1 &^ 0xf = 0`, + // etc. + + // shifts + `0 << 0 = 0`, + `1 << 10 = 1024`, + `0 >> 0 = 0`, + `1024 >> 10 == 1`, + `? << 0 == ?`, + `? >> 10 == ?`, + // etc. + + // comparisons + `false == false = true`, + `false == true = false`, + `true == false = false`, + `true == true = true`, + + `false != false = false`, + `false != true = true`, + `true != false = true`, + `true != true = false`, + + `"foo" == "bar" = false`, + `"foo" != "bar" = true`, + `"foo" < "bar" = false`, + `"foo" <= "bar" = false`, + `"foo" > "bar" = true`, + `"foo" >= "bar" = true`, + + `0 == 0 = true`, + `0 != 0 = false`, + `0 < 10 = true`, + `10 <= 10 = true`, + `0 > 10 = false`, + `10 >= 10 = true`, + + `1/123456789 == 1/123456789 == true`, + `1/123456789 != 1/123456789 == false`, + `1/123456789 < 1/123456788 == true`, + `1/123456788 <= 1/123456789 == false`, + `0.11 > 0.11 = false`, + `0.11 >= 0.11 = true`, + + `? == 0 = false`, + `? != 0 = false`, + `? < 10 = false`, + `? <= 10 = false`, + `? > 10 = false`, + `? >= 10 = false`, + + `0 == ? = false`, + `0 != ? = false`, + `0 < ? = false`, + `10 <= ? = false`, + `0 > ? = false`, + `10 >= ? = false`, + + // etc. +} + +func TestOps(t *testing.T) { + for _, test := range opTests { + a := strings.Split(test, " ") + i := 0 // operator index + + var x, x0 Value + switch len(a) { + case 4: + // unary operation + case 5: + // binary operation + x, x0 = val(a[0]), val(a[0]) + i = 1 + default: + t.Errorf("invalid test case: %s", test) + continue + } + + op, ok := optab[a[i]] + if !ok { + panic("missing optab entry for " + a[i]) + } + + y, y0 := val(a[i+1]), val(a[i+1]) + + got := doOp(x, op, y) + want := val(a[i+3]) + if !eql(got, want) { + t.Errorf("%s: got %s; want %s", test, got, want) + } + if x0 != nil && !eql(x, x0) { + t.Errorf("%s: x changed to %s", test, x) + } + if !eql(y, y0) { + t.Errorf("%s: y changed to %s", test, y) + } + } +} + +func eql(x, y Value) bool { + _, ux := x.(unknownVal) + _, uy := y.(unknownVal) + if ux || uy { + return ux == uy + } + return Compare(x, token.EQL, y) +} + +// ---------------------------------------------------------------------------- +// Support functions + +func val(lit string) Value { + if len(lit) == 0 { + return MakeUnknown() + } + + switch lit { + case "?": + return MakeUnknown() + case "true": + return MakeBool(true) + case "false": + return MakeBool(false) + } + + tok := token.INT + switch first, last := lit[0], lit[len(lit)-1]; { + case first == '"' || first == '`': + tok = token.STRING + lit = strings.Replace(lit, "_", " ", -1) + case first == '\'': + tok = token.CHAR + case last == 'i': + tok = token.IMAG + default: + if !strings.HasPrefix(lit, "0x") && strings.ContainsAny(lit, "./Ee") { + tok = token.FLOAT + } + } + + return MakeFromLiteral(lit, tok, 0) +} + +var optab = map[string]token.Token{ + "!": token.NOT, + + "+": token.ADD, + "-": token.SUB, + "*": token.MUL, + "/": token.QUO, + "%": token.REM, + + "<<": token.SHL, + ">>": token.SHR, + + "&": token.AND, + "|": token.OR, + "^": token.XOR, + "&^": token.AND_NOT, + + "==": token.EQL, + "!=": token.NEQ, + "<": token.LSS, + "<=": token.LEQ, + ">": token.GTR, + ">=": token.GEQ, +} + +func panicHandler(v *Value) { + switch p := recover().(type) { + case nil: + // nothing to do + case string: + *v = MakeString(p) + case error: + *v = MakeString(p.Error()) + default: + panic(p) + } +} + +func doOp(x Value, op token.Token, y Value) (z Value) { + defer panicHandler(&z) + + if x == nil { + return UnaryOp(op, y, 0) + } + + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return MakeBool(Compare(x, op, y)) + case token.SHL, token.SHR: + s, _ := Int64Val(y) + return Shift(x, op, uint(s)) + default: + return BinaryOp(x, op, y) + } +} + +// ---------------------------------------------------------------------------- +// Other tests + +var fracTests = []string{ + "0 0 1", + "1 1 1", + "-1 -1 1", + "1.2 6 5", + "-0.991 -991 1000", + "1e100 1e100 1", +} + +func TestFractions(t *testing.T) { + for _, test := range fracTests { + a := strings.Split(test, " ") + if len(a) != 3 { + t.Errorf("invalid test case: %s", test) + continue + } + + x := val(a[0]) + n := val(a[1]) + d := val(a[2]) + + if got := Num(x); !eql(got, n) { + t.Errorf("%s: got num = %s; want %s", test, got, n) + } + + if got := Denom(x); !eql(got, d) { + t.Errorf("%s: got denom = %s; want %s", test, got, d) + } + } +} + +var bytesTests = []string{ + "0", + "1", + "123456789", + "123456789012345678901234567890123456789012345678901234567890", +} + +func TestBytes(t *testing.T) { + for _, test := range bytesTests { + x := val(test) + bytes := Bytes(x) + + // special case 0 + if Sign(x) == 0 && len(bytes) != 0 { + t.Errorf("%s: got %v; want empty byte slice", test, bytes) + } + + if n := len(bytes); n > 0 && bytes[n-1] == 0 { + t.Errorf("%s: got %v; want no leading 0 byte", test, bytes) + } + + if got := MakeFromBytes(bytes); !eql(got, x) { + t.Errorf("%s: got %s; want %s (bytes = %v)", test, got, x, bytes) + } + } +} + +func TestUnknown(t *testing.T) { + u := MakeUnknown() + var values = []Value{ + u, + MakeBool(false), // token.ADD ok below, operation is never considered + MakeString(""), + MakeInt64(1), + MakeFromLiteral("-1234567890123456789012345678901234567890", token.INT, 0), + MakeFloat64(1.2), + MakeImag(MakeFloat64(1.2)), + } + for _, val := range values { + x, y := val, u + for i := range [2]int{} { + if i == 1 { + x, y = y, x + } + if got := BinaryOp(x, token.ADD, y); got.Kind() != Unknown { + t.Errorf("%s + %s: got %s; want %s", x, y, got, u) + } + if got := Compare(x, token.EQL, y); got { + t.Errorf("%s == %s: got true; want false", x, y) + } + } + } +} diff --git a/libgo/go/go/doc/doc.go b/libgo/go/go/doc/doc.go index 4264940a0cd..3c3e28d48fb 100644 --- a/libgo/go/go/doc/doc.go +++ b/libgo/go/go/doc/doc.go @@ -18,7 +18,8 @@ type Package struct { Imports []string Filenames []string Notes map[string][]*Note - // DEPRECATED. For backward compatibility Bugs is still populated, + + // Deprecated: For backward compatibility Bugs is still populated, // but all new code should use Notes instead. Bugs []string diff --git a/libgo/go/go/doc/exports.go b/libgo/go/go/doc/exports.go index 1d3b466d8c7..4a12b1e55f8 100644 --- a/libgo/go/go/doc/exports.go +++ b/libgo/go/go/doc/exports.go @@ -12,13 +12,12 @@ import ( ) // filterIdentList removes unexported names from list in place -// and returns the resulting list. If blankOk is set, blank -// identifiers are considered exported names. +// and returns the resulting list. // -func filterIdentList(list []*ast.Ident, blankOk bool) []*ast.Ident { +func filterIdentList(list []*ast.Ident) []*ast.Ident { j := 0 for _, x := range list { - if ast.IsExported(x.Name) || (blankOk && x.Name == "_") { + if ast.IsExported(x.Name) { list[j] = x j++ } @@ -26,6 +25,17 @@ func filterIdentList(list []*ast.Ident, blankOk bool) []*ast.Ident { return list[0:j] } +// hasExportedName reports whether list contains any exported names. +// +func hasExportedName(list []*ast.Ident) bool { + for _, x := range list { + if x.IsExported() { + return true + } + } + return false +} + // removeErrorField removes anonymous fields named "error" from an interface. // This is called when "error" has been determined to be a local name, // not the predeclared type. @@ -53,7 +63,7 @@ func removeErrorField(ityp *ast.InterfaceType) { } // filterFieldList removes unexported fields (field names) from the field list -// in place and returns true if fields were removed. Anonymous fields are +// in place and reports whether fields were removed. Anonymous fields are // recorded with the parent type. filterType is called with the types of // all remaining fields. // @@ -78,7 +88,7 @@ func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp r.remember(ityp) } } else { - field.Names = filterIdentList(field.Names, false) + field.Names = filterIdentList(field.Names) if len(field.Names) < n { removedFields = true } @@ -146,9 +156,7 @@ func (r *reader) filterSpec(spec ast.Spec, tok token.Token) bool { // always keep imports so we can collect them return true case *ast.ValueSpec: - // special case: consider blank constants as exported - // (work-around for issue 5397) - s.Names = filterIdentList(s.Names, tok == token.CONST) + s.Names = filterIdentList(s.Names) if len(s.Names) > 0 { r.filterType(nil, s.Type) return true @@ -165,7 +173,46 @@ func (r *reader) filterSpec(spec ast.Spec, tok token.Token) bool { return false } +// copyConstType returns a copy of typ with position pos. +// typ must be a valid constant type. +// In practice, only (possibly qualified) identifiers are possible. +// +func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr { + switch typ := typ.(type) { + case *ast.Ident: + return &ast.Ident{Name: typ.Name, NamePos: pos} + case *ast.SelectorExpr: + if id, ok := typ.X.(*ast.Ident); ok { + // presumably a qualified identifier + return &ast.SelectorExpr{ + Sel: ast.NewIdent(typ.Sel.Name), + X: &ast.Ident{Name: id.Name, NamePos: pos}, + } + } + } + return nil // shouldn't happen, but be conservative and don't panic +} + func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec { + if tok == token.CONST { + // Propagate any type information that would get lost otherwise + // when unexported constants are filtered. + var prevType ast.Expr + for _, spec := range list { + spec := spec.(*ast.ValueSpec) + if spec.Type == nil && prevType != nil { + // provide current spec with an explicit type + spec.Type = copyConstType(prevType, spec.Pos()) + } + if hasExportedName(spec.Names) { + // exported names are preserved so there's no need to propagate the type + prevType = nil + } else { + prevType = spec.Type + } + } + } + j := 0 for _, s := range list { if r.filterSpec(s, tok) { diff --git a/libgo/go/go/doc/testdata/blank.0.golden b/libgo/go/go/doc/testdata/blank.0.golden index dae3ab2affa..c2987cf140e 100644 --- a/libgo/go/go/doc/testdata/blank.0.golden +++ b/libgo/go/go/doc/testdata/blank.0.golden @@ -4,14 +4,33 @@ PACKAGE blank IMPORTPATH testdata/blank +IMPORTS + os + FILENAMES testdata/blank.go CONSTANTS + // T constants counting from unexported constants. + const ( + C1 T + C2 + + C3 + + C4 int + ) + + // Constants with an imported type that needs to be propagated. + const ( + Default os.FileMode = 0644 + Useless = 0312 + WideOpen = 0777 + ) + // Package constants. const ( - _ int = iota - I1 + I1 int I2 ) @@ -28,10 +47,9 @@ TYPES // type T int - // T constants. + // T constants counting from a blank constant. const ( - _ T = iota - T1 + T1 T T2 ) diff --git a/libgo/go/go/doc/testdata/blank.1.golden b/libgo/go/go/doc/testdata/blank.1.golden index 333d7e5b040..ee5054a4ed6 100644 --- a/libgo/go/go/doc/testdata/blank.1.golden +++ b/libgo/go/go/doc/testdata/blank.1.golden @@ -4,10 +4,25 @@ PACKAGE blank IMPORTPATH testdata/blank +IMPORTS + os + FILENAMES testdata/blank.go CONSTANTS + // T constants counting from unexported constants. + const ( + tweedledee T = iota + tweedledum + C1 + C2 + alice + C3 + redQueen int = iota + C4 + ) + // Package constants. const ( _ int = iota @@ -15,6 +30,20 @@ CONSTANTS I2 ) + // Constants with an imported type that needs to be propagated. + const ( + zero os.FileMode = 0 + Default = 0644 + Useless = 0312 + WideOpen = 0777 + ) + + // Unexported constants counting from blank iota. See issue 9615. + const ( + _ = iota + one = iota + 1 + ) + VARIABLES // @@ -37,7 +66,7 @@ TYPES // type T int - // T constants. + // T constants counting from a blank constant. const ( _ T = iota T1 diff --git a/libgo/go/go/doc/testdata/blank.2.golden b/libgo/go/go/doc/testdata/blank.2.golden index dae3ab2affa..c2987cf140e 100644 --- a/libgo/go/go/doc/testdata/blank.2.golden +++ b/libgo/go/go/doc/testdata/blank.2.golden @@ -4,14 +4,33 @@ PACKAGE blank IMPORTPATH testdata/blank +IMPORTS + os + FILENAMES testdata/blank.go CONSTANTS + // T constants counting from unexported constants. + const ( + C1 T + C2 + + C3 + + C4 int + ) + + // Constants with an imported type that needs to be propagated. + const ( + Default os.FileMode = 0644 + Useless = 0312 + WideOpen = 0777 + ) + // Package constants. const ( - _ int = iota - I1 + I1 int I2 ) @@ -28,10 +47,9 @@ TYPES // type T int - // T constants. + // T constants counting from a blank constant. const ( - _ T = iota - T1 + T1 T T2 ) diff --git a/libgo/go/go/doc/testdata/blank.go b/libgo/go/go/doc/testdata/blank.go index f812c77b777..419a78f7d51 100644 --- a/libgo/go/go/doc/testdata/blank.go +++ b/libgo/go/go/doc/testdata/blank.go @@ -6,15 +6,37 @@ // See issue 5397. package blank +import "os" + type T int -// T constants. +// T constants counting from a blank constant. const ( _ T = iota T1 T2 ) +// T constants counting from unexported constants. +const ( + tweedledee T = iota + tweedledum + C1 + C2 + alice + C3 + redQueen int = iota + C4 +) + +// Constants with an imported type that needs to be propagated. +const ( + zero os.FileMode = 0 + Default = 0644 + Useless = 0312 + WideOpen = 0777 +) + // Package constants. const ( _ int = iota @@ -22,6 +44,13 @@ const ( I2 ) +// Unexported constants counting from blank iota. +// See issue 9615. +const ( + _ = iota + one = iota + 1 +) + // Blanks not in doc output: // S has a padding field. diff --git a/libgo/go/go/format/format.go b/libgo/go/go/format/format.go index 668a42df2df..1adfd7d45e8 100644 --- a/libgo/go/go/format/format.go +++ b/libgo/go/go/format/format.go @@ -12,8 +12,8 @@ import ( "go/parser" "go/printer" "go/token" + "internal/format" "io" - "strings" ) var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8} @@ -82,7 +82,7 @@ func Node(dst io.Writer, fset *token.FileSet, node interface{}) error { // func Source(src []byte) ([]byte, error) { fset := token.NewFileSet() - file, sourceAdj, indentAdj, err := parse(fset, "", src, true) + file, sourceAdj, indentAdj, err := format.Parse(fset, "", src, true) if err != nil { return nil, err } @@ -93,7 +93,7 @@ func Source(src []byte) ([]byte, error) { ast.SortImports(fset, file) } - return format(fset, file, sourceAdj, indentAdj, src, config) + return format.Format(fset, file, sourceAdj, indentAdj, src, config) } func hasUnsortedImports(file *ast.File) bool { @@ -113,154 +113,3 @@ func hasUnsortedImports(file *ast.File) bool { } return false } - -// ---------------------------------------------------------------------------- -// Support functions -// -// The functions parse, format, and isSpace below are identical to the -// respective functions in cmd/gofmt/gofmt.go - keep them in sync! -// -// TODO(gri) Factor out this functionality, eventually. - -// parse parses src, which was read from the named file, -// as a Go source file, declaration, or statement list. -func parse(fset *token.FileSet, filename string, src []byte, fragmentOk bool) ( - file *ast.File, - sourceAdj func(src []byte, indent int) []byte, - indentAdj int, - err error, -) { - // Try as whole source file. - file, err = parser.ParseFile(fset, filename, src, parserMode) - // If there's no error, return. If the error is that the source file didn't begin with a - // package line and source fragments are ok, fall through to - // try as a source fragment. Stop and return on any other error. - if err == nil || !fragmentOk || !strings.Contains(err.Error(), "expected 'package'") { - return - } - - // If this is a declaration list, make it a source file - // by inserting a package clause. - // Insert using a ;, not a newline, so that the line numbers - // in psrc match the ones in src. - psrc := append([]byte("package p;"), src...) - file, err = parser.ParseFile(fset, filename, psrc, parserMode) - if err == nil { - sourceAdj = func(src []byte, indent int) []byte { - // Remove the package clause. - // Gofmt has turned the ; into a \n. - src = src[indent+len("package p\n"):] - return bytes.TrimSpace(src) - } - return - } - // If the error is that the source file didn't begin with a - // declaration, fall through to try as a statement list. - // Stop and return on any other error. - if !strings.Contains(err.Error(), "expected declaration") { - return - } - - // If this is a statement list, make it a source file - // by inserting a package clause and turning the list - // into a function body. This handles expressions too. - // Insert using a ;, not a newline, so that the line numbers - // in fsrc match the ones in src. - fsrc := append(append([]byte("package p; func _() {"), src...), '\n', '}') - file, err = parser.ParseFile(fset, filename, fsrc, parserMode) - if err == nil { - sourceAdj = func(src []byte, indent int) []byte { - // Cap adjusted indent to zero. - if indent < 0 { - indent = 0 - } - // Remove the wrapping. - // Gofmt has turned the ; into a \n\n. - // There will be two non-blank lines with indent, hence 2*indent. - src = src[2*indent+len("package p\n\nfunc _() {"):] - src = src[:len(src)-(indent+len("\n}\n"))] - return bytes.TrimSpace(src) - } - // Gofmt has also indented the function body one level. - // Adjust that with indentAdj. - indentAdj = -1 - } - - // Succeeded, or out of options. - return -} - -// format formats the given package file originally obtained from src -// and adjusts the result based on the original source via sourceAdj -// and indentAdj. -func format( - fset *token.FileSet, - file *ast.File, - sourceAdj func(src []byte, indent int) []byte, - indentAdj int, - src []byte, - cfg printer.Config, -) ([]byte, error) { - if sourceAdj == nil { - // Complete source file. - var buf bytes.Buffer - err := cfg.Fprint(&buf, fset, file) - if err != nil { - return nil, err - } - return buf.Bytes(), nil - } - - // Partial source file. - // Determine and prepend leading space. - i, j := 0, 0 - for j < len(src) && isSpace(src[j]) { - if src[j] == '\n' { - i = j + 1 // byte offset of last line in leading space - } - j++ - } - var res []byte - res = append(res, src[:i]...) - - // Determine and prepend indentation of first code line. - // Spaces are ignored unless there are no tabs, - // in which case spaces count as one tab. - indent := 0 - hasSpace := false - for _, b := range src[i:j] { - switch b { - case ' ': - hasSpace = true - case '\t': - indent++ - } - } - if indent == 0 && hasSpace { - indent = 1 - } - for i := 0; i < indent; i++ { - res = append(res, '\t') - } - - // Format the source. - // Write it without any leading and trailing space. - cfg.Indent = indent + indentAdj - var buf bytes.Buffer - err := cfg.Fprint(&buf, fset, file) - if err != nil { - return nil, err - } - res = append(res, sourceAdj(buf.Bytes(), cfg.Indent)...) - - // Determine and append trailing space. - i = len(src) - for i > 0 && isSpace(src[i-1]) { - i-- - } - return append(res, src[i:]...), nil -} - -func isSpace(b byte) bool { - return b == ' ' || b == '\t' || b == '\n' || b == '\r' -} diff --git a/libgo/go/go/format/format_test.go b/libgo/go/go/format/format_test.go index d7846bec652..000c611aa25 100644 --- a/libgo/go/go/format/format_test.go +++ b/libgo/go/go/format/format_test.go @@ -91,7 +91,11 @@ var tests = []string{ "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\n\t\tfoo\n`\n\n\n", // no indentation removed inside raw strings // comments - "i := 5 /* Comment */", // Issue 5551. + "i := 5 /* Comment */", // Issue 5551. + "\ta()\n//line :1", // Issue 11276. + "\t//xxx\n\ta()\n//line :2", // Issue 11276. + "\ta() //line :1\n\tb()\n", // Issue 11276. + "x := 0\n//line :1\n//line :2", // Issue 11276. // erroneous programs "ERROR1 + 2 +", diff --git a/libgo/go/go/importer/importer.go b/libgo/go/go/importer/importer.go new file mode 100644 index 00000000000..4590ca30e60 --- /dev/null +++ b/libgo/go/go/importer/importer.go @@ -0,0 +1,69 @@ +// Copyright 2015 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 importer provides access to export data importers. +package importer + +import ( + "go/internal/gccgoimporter" + "go/internal/gcimporter" + "go/types" + "io" + "runtime" +) + +// A Lookup function returns a reader to access package data for +// a given import path, or an error if no matching package is found. +type Lookup func(path string) (io.ReadCloser, error) + +// For returns an Importer for the given compiler and lookup interface, +// or nil. Supported compilers are "gc", and "gccgo". If lookup is nil, +// the default package lookup mechanism for the given compiler is used. +func For(compiler string, lookup Lookup) types.Importer { + switch compiler { + case "gc": + if lookup == nil { + return make(gcimports) + } + panic("gc importer for custom import path lookup not yet implemented") + case "gccgo": + if lookup == nil { + var inst gccgoimporter.GccgoInstallation + if err := inst.InitFromDriver("gccgo"); err != nil { + return nil + } + return &gccgoimports{ + packages: make(map[string]*types.Package), + importer: inst.GetImporter(nil, nil), + } + } + panic("gccgo importer for custom import path lookup not yet implemented") + } + // compiler not supported + return nil +} + +// Default returns an Importer for the compiler that built the running binary. +func Default() types.Importer { + return For(runtime.Compiler, nil) +} + +// gc support + +type gcimports map[string]*types.Package + +func (m gcimports) Import(path string) (*types.Package, error) { + return gcimporter.Import(m, path) +} + +// gccgo support + +type gccgoimports struct { + packages map[string]*types.Package + importer gccgoimporter.Importer +} + +func (m *gccgoimports) Import(path string) (*types.Package, error) { + return m.importer(m.packages, path) +} diff --git a/libgo/go/go/internal/gccgoimporter/gccgoinstallation.go b/libgo/go/go/internal/gccgoimporter/gccgoinstallation.go new file mode 100644 index 00000000000..622dfc8b696 --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/gccgoinstallation.go @@ -0,0 +1,94 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bufio" + "go/types" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// Information about a specific installation of gccgo. +type GccgoInstallation struct { + // Version of gcc (e.g. 4.8.0). + GccVersion string + + // Target triple (e.g. x86_64-unknown-linux-gnu). + TargetTriple string + + // Built-in library paths used by this installation. + LibPaths []string +} + +// Ask the driver at the given path for information for this GccgoInstallation. +func (inst *GccgoInstallation) InitFromDriver(gccgoPath string) (err error) { + cmd := exec.Command(gccgoPath, "-###", "-S", "-x", "go", "-") + stderr, err := cmd.StderrPipe() + if err != nil { + return + } + + err = cmd.Start() + if err != nil { + return + } + + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "Target: "): + inst.TargetTriple = line[8:] + + case line[0] == ' ': + args := strings.Fields(line) + for _, arg := range args[1:] { + if strings.HasPrefix(arg, "-L") { + inst.LibPaths = append(inst.LibPaths, arg[2:]) + } + } + } + } + + stdout, err := exec.Command(gccgoPath, "-dumpversion").Output() + if err != nil { + return + } + inst.GccVersion = strings.TrimSpace(string(stdout)) + + return +} + +// Return the list of export search paths for this GccgoInstallation. +func (inst *GccgoInstallation) SearchPaths() (paths []string) { + for _, lpath := range inst.LibPaths { + spath := filepath.Join(lpath, "go", inst.GccVersion) + fi, err := os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + + spath = filepath.Join(spath, inst.TargetTriple) + fi, err = os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + } + + paths = append(paths, inst.LibPaths...) + + return +} + +// Return an importer that searches incpaths followed by the gcc installation's +// built-in search paths and the current directory. +func (inst *GccgoInstallation) GetImporter(incpaths []string, initmap map[*types.Package]InitData) Importer { + return GetImporter(append(append(incpaths, inst.SearchPaths()...), "."), initmap) +} diff --git a/libgo/go/go/internal/gccgoimporter/gccgoinstallation_test.go b/libgo/go/go/internal/gccgoimporter/gccgoinstallation_test.go new file mode 100644 index 00000000000..a2acaf7331a --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/gccgoinstallation_test.go @@ -0,0 +1,197 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "go/types" + "runtime" + "testing" +) + +var importablePackages = [...]string{ + "archive/tar", + "archive/zip", + "bufio", + "bytes", + "compress/bzip2", + "compress/flate", + "compress/gzip", + "compress/lzw", + "compress/zlib", + "container/heap", + "container/list", + "container/ring", + "crypto/aes", + "crypto/cipher", + "crypto/des", + "crypto/dsa", + "crypto/ecdsa", + "crypto/elliptic", + "crypto", + "crypto/hmac", + "crypto/md5", + "crypto/rand", + "crypto/rc4", + "crypto/rsa", + "crypto/sha1", + "crypto/sha256", + "crypto/sha512", + "crypto/subtle", + "crypto/tls", + "crypto/x509", + "crypto/x509/pkix", + "database/sql/driver", + "database/sql", + "debug/dwarf", + "debug/elf", + "debug/gosym", + "debug/macho", + "debug/pe", + "encoding/ascii85", + "encoding/asn1", + "encoding/base32", + "encoding/base64", + "encoding/binary", + "encoding/csv", + "encoding/gob", + "encoding", + "encoding/hex", + "encoding/json", + "encoding/pem", + "encoding/xml", + "errors", + "exp/proxy", + "exp/terminal", + "expvar", + "flag", + "fmt", + "go/ast", + "go/build", + "go/doc", + "go/format", + "go/parser", + "go/printer", + "go/scanner", + "go/token", + "hash/adler32", + "hash/crc32", + "hash/crc64", + "hash/fnv", + "hash", + "html", + "html/template", + "image/color", + "image/color/palette", + "image/draw", + "image/gif", + "image", + "image/jpeg", + "image/png", + "index/suffixarray", + "io", + "io/ioutil", + "log", + "log/syslog", + "math/big", + "math/cmplx", + "math", + "math/rand", + "mime", + "mime/multipart", + "net", + "net/http/cgi", + "net/http/cookiejar", + "net/http/fcgi", + "net/http", + "net/http/httptest", + "net/http/httputil", + "net/http/pprof", + "net/mail", + "net/rpc", + "net/rpc/jsonrpc", + "net/smtp", + "net/textproto", + "net/url", + "old/regexp", + "old/template", + "os/exec", + "os", + "os/signal", + "os/user", + "path/filepath", + "path", + "reflect", + "regexp", + "regexp/syntax", + "runtime/debug", + "runtime", + "runtime/pprof", + "sort", + "strconv", + "strings", + "sync/atomic", + "sync", + "syscall", + "testing", + "testing/iotest", + "testing/quick", + "text/scanner", + "text/tabwriter", + "text/template", + "text/template/parse", + "time", + "unicode", + "unicode/utf16", + "unicode/utf8", +} + +func TestInstallationImporter(t *testing.T) { + // This test relies on gccgo being around, which it most likely will be if we + // were compiled with gccgo. + if runtime.Compiler != "gccgo" { + t.Skip("This test needs gccgo") + return + } + + // Even when we have gccgo, this doesn't work while building + // gccgo itself. + t.Skip("gccgo is not necessarily available") + + var inst GccgoInstallation + err := inst.InitFromDriver("gccgo") + if err != nil { + t.Fatal(err) + } + imp := inst.GetImporter(nil, nil) + + // Ensure we don't regress the number of packages we can parse. First import + // all packages into the same map and then each individually. + pkgMap := make(map[string]*types.Package) + for _, pkg := range importablePackages { + _, err = imp(pkgMap, pkg) + if err != nil { + t.Error(err) + } + } + + for _, pkg := range importablePackages { + _, err = imp(make(map[string]*types.Package), pkg) + if err != nil { + t.Error(err) + } + } + + // Test for certain specific entities in the imported data. + for _, test := range [...]importerTest{ + {pkgpath: "io", name: "Reader", want: "type Reader interface{Read(p []uint8) (n int, err error)}"}, + {pkgpath: "io", name: "ReadWriter", want: "type ReadWriter interface{Reader; Writer}"}, + {pkgpath: "math", name: "Pi", want: "const Pi untyped float"}, + {pkgpath: "math", name: "Sin", want: "func Sin(x float64) float64"}, + {pkgpath: "sort", name: "Ints", want: "func Ints(a []int)"}, + {pkgpath: "unsafe", name: "Pointer", want: "type Pointer unsafe.Pointer"}, + } { + runImporterTest(t, imp, nil, &test) + } +} diff --git a/libgo/go/go/internal/gccgoimporter/importer.go b/libgo/go/go/internal/gccgoimporter/importer.go new file mode 100644 index 00000000000..aa0d01afdf3 --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/importer.go @@ -0,0 +1,212 @@ +// Copyright 2013 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 gccgoimporter implements Import for gccgo-generated object files. +package gccgoimporter // import "go/internal/gccgoimporter" + +import ( + "bytes" + "debug/elf" + "fmt" + "go/types" + "io" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// A PackageInit describes an imported package that needs initialization. +type PackageInit struct { + Name string // short package name + InitFunc string // name of init function + Priority int // priority of init function, see InitData.Priority +} + +// The gccgo-specific init data for a package. +type InitData struct { + // Initialization priority of this package relative to other packages. + // This is based on the maximum depth of the package's dependency graph; + // it is guaranteed to be greater than that of its dependencies. + Priority int + + // The list of packages which this package depends on to be initialized, + // including itself if needed. This is the subset of the transitive closure of + // the package's dependencies that need initialization. + Inits []PackageInit +} + +// Locate the file from which to read export data. +// This is intended to replicate the logic in gofrontend. +func findExportFile(searchpaths []string, pkgpath string) (string, error) { + for _, spath := range searchpaths { + pkgfullpath := filepath.Join(spath, pkgpath) + pkgdir, name := filepath.Split(pkgfullpath) + + for _, filepath := range [...]string{ + pkgfullpath, + pkgfullpath + ".gox", + pkgdir + "lib" + name + ".so", + pkgdir + "lib" + name + ".a", + pkgfullpath + ".o", + } { + fi, err := os.Stat(filepath) + if err == nil && !fi.IsDir() { + return filepath, nil + } + } + } + + return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":")) +} + +const ( + gccgov1Magic = "v1;\n" + goimporterMagic = "\n$$ " + archiveMagic = "!<ar" +) + +// Opens the export data file at the given path. If this is an ELF file, +// searches for and opens the .go_export section. If this is an archive, +// reads the export data from the first member, which is assumed to be an ELF file. +// This is intended to replicate the logic in gofrontend. +func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) { + f, err := os.Open(fpath) + if err != nil { + return + } + closer = f + defer func() { + if err != nil && closer != nil { + f.Close() + } + }() + + var magic [4]byte + _, err = f.ReadAt(magic[:], 0) + if err != nil { + return + } + // reset to offset 0 - needed on Plan 9 (see issue #11265) + // TODO: remove once issue #11265 has been resolved. + _, err = f.Seek(0, 0) + if err != nil { + return + } + + var elfreader io.ReaderAt + switch string(magic[:]) { + case gccgov1Magic, goimporterMagic: + // Raw export data. + reader = f + return + + case archiveMagic: + // TODO(pcc): Read the archive directly instead of using "ar". + f.Close() + closer = nil + + cmd := exec.Command("ar", "p", fpath) + var out []byte + out, err = cmd.Output() + if err != nil { + return + } + + elfreader = bytes.NewReader(out) + + default: + elfreader = f + } + + ef, err := elf.NewFile(elfreader) + if err != nil { + return + } + + sec := ef.Section(".go_export") + if sec == nil { + err = fmt.Errorf("%s: .go_export section not found", fpath) + return + } + + reader = sec.Open() + return +} + +// An Importer resolves import paths to Packages. The imports map records +// packages already known, indexed by package path. +// An importer must determine the canonical package path and check imports +// to see if it is already present in the map. If so, the Importer can return +// the map entry. Otherwise, the importer must load the package data for the +// given path into a new *Package, record it in imports map, and return the +// package. +type Importer func(imports map[string]*types.Package, path string) (*types.Package, error) + +func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer { + return func(imports map[string]*types.Package, pkgpath string) (pkg *types.Package, err error) { + if pkgpath == "unsafe" { + return types.Unsafe, nil + } + + fpath, err := findExportFile(searchpaths, pkgpath) + if err != nil { + return + } + + reader, closer, err := openExportFile(fpath) + if err != nil { + return + } + if closer != nil { + defer closer.Close() + } + + var magic [4]byte + _, err = reader.Read(magic[:]) + if err != nil { + return + } + _, err = reader.Seek(0, 0) + if err != nil { + return + } + + switch string(magic[:]) { + case gccgov1Magic: + var p parser + p.init(fpath, reader, imports) + pkg = p.parsePackage() + if initmap != nil { + initmap[pkg] = p.initdata + } + + // Excluded for now: Standard gccgo doesn't support this import format currently. + // case goimporterMagic: + // var data []byte + // data, err = ioutil.ReadAll(reader) + // if err != nil { + // return + // } + // var n int + // n, pkg, err = importer.ImportData(imports, data) + // if err != nil { + // return + // } + + // if initmap != nil { + // suffixreader := bytes.NewReader(data[n:]) + // var p parser + // p.init(fpath, suffixreader, nil) + // p.parseInitData() + // initmap[pkg] = p.initdata + // } + + default: + err = fmt.Errorf("unrecognized magic string: %q", string(magic[:])) + } + + return + } +} diff --git a/libgo/go/go/internal/gccgoimporter/importer_test.go b/libgo/go/go/internal/gccgoimporter/importer_test.go new file mode 100644 index 00000000000..f3bcadbaf77 --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/importer_test.go @@ -0,0 +1,171 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "go/types" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" +) + +type importerTest struct { + pkgpath, name, want, wantval string + wantinits []string +} + +func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) { + pkg, err := imp(make(map[string]*types.Package), test.pkgpath) + if err != nil { + t.Error(err) + return + } + + if test.name != "" { + obj := pkg.Scope().Lookup(test.name) + if obj == nil { + t.Errorf("%s: object not found", test.name) + return + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if test.wantval != "" { + gotval := obj.(*types.Const).Val().String() + if gotval != test.wantval { + t.Errorf("%s: got val %q; want val %q", test.name, gotval, test.wantval) + } + } + } + + if len(test.wantinits) > 0 { + initdata := initmap[pkg] + found := false + // Check that the package's own init function has the package's priority + for _, pkginit := range initdata.Inits { + if pkginit.InitFunc == test.wantinits[0] { + if initdata.Priority != pkginit.Priority { + t.Errorf("%s: got self priority %d; want %d", test.pkgpath, pkginit.Priority, initdata.Priority) + } + found = true + break + } + } + + if !found { + t.Errorf("%s: could not find expected function %q", test.pkgpath, test.wantinits[0]) + } + + // Each init function in the list other than the first one is a + // dependency of the function immediately before it. Check that + // the init functions appear in descending priority order. + priority := initdata.Priority + for _, wantdepinit := range test.wantinits[1:] { + found = false + for _, pkginit := range initdata.Inits { + if pkginit.InitFunc == wantdepinit { + if priority <= pkginit.Priority { + t.Errorf("%s: got dep priority %d; want less than %d", test.pkgpath, pkginit.Priority, priority) + } + found = true + priority = pkginit.Priority + break + } + } + + if !found { + t.Errorf("%s: could not find expected function %q", test.pkgpath, wantdepinit) + } + } + } +} + +var importerTests = [...]importerTest{ + {pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"}, + {pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1/1 + -1/1i)"}, + {pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1/1 + 1/1i)"}, + {pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1/1 + -1/1i)"}, + {pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1/1 + 1/1i)"}, + // TODO: enable this entry once bug has been tracked down + //{pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import", "math..import"}}, +} + +func TestGoxImporter(t *testing.T) { + testenv.MustHaveGoBuild(t) + + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{"testdata"}, initmap) + + for _, test := range importerTests { + runImporterTest(t, imp, initmap, &test) + } +} + +func TestObjImporter(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // This test relies on gccgo being around, which it most likely will be if we + // were compiled with gccgo. + if runtime.Compiler != "gccgo" { + t.Skip("This test needs gccgo") + return + } + + tmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{tmpdir}, initmap) + + artmpdir, err := ioutil.TempDir("", "") + if err != nil { + t.Fatal(err) + } + arinitmap := make(map[*types.Package]InitData) + arimp := GetImporter([]string{artmpdir}, arinitmap) + + for _, test := range importerTests { + gofile := filepath.Join("testdata", test.pkgpath+".go") + ofile := filepath.Join(tmpdir, test.pkgpath+".o") + afile := filepath.Join(artmpdir, "lib"+test.pkgpath+".a") + + cmd := exec.Command("gccgo", "-fgo-pkgpath="+test.pkgpath, "-c", "-o", ofile, gofile) + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("gccgo %s failed: %s", gofile, err) + } + + runImporterTest(t, imp, initmap, &test) + + cmd = exec.Command("ar", "cr", afile, ofile) + out, err = cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("ar cr %s %s failed: %s", afile, ofile, err) + } + + runImporterTest(t, arimp, arinitmap, &test) + + if err = os.Remove(ofile); err != nil { + t.Fatal(err) + } + if err = os.Remove(afile); err != nil { + t.Fatal(err) + } + } + + if err = os.Remove(tmpdir); err != nil { + t.Fatal(err) + } +} diff --git a/libgo/go/go/internal/gccgoimporter/parser.go b/libgo/go/go/internal/gccgoimporter/parser.go new file mode 100644 index 00000000000..c06cce435b1 --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/parser.go @@ -0,0 +1,855 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bytes" + "errors" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "strconv" + "strings" + "text/scanner" +) + +type parser struct { + scanner scanner.Scanner + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + pkgpath string // package path of imported package + pkgname string // name of imported package + pkg *types.Package // reference to imported package + imports map[string]*types.Package // package path -> package object + typeMap map[int]types.Type // type number -> type + initdata InitData // package init priority data +} + +func (p *parser) init(filename string, src io.Reader, imports map[string]*types.Package) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments + p.scanner.Whitespace = 1<<'\t' | 1<<'\n' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() + p.imports = imports + p.typeMap = make(map[int]types.Type) +} + +type importError struct { + pos scanner.Position + err error +} + +func (e importError) Error() string { + return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err) +} + +func (p *parser) error(err interface{}) { + if s, ok := err.(string); ok { + err = errors.New(s) + } + // panic with a runtime.Error if err is not an error + panic(importError{p.scanner.Pos(), err.(error)}) +} + +func (p *parser) errorf(format string, args ...interface{}) { + p.error(fmt.Errorf(format, args...)) +} + +func (p *parser) expect(tok rune) string { + lit := p.lit + if p.tok != tok { + p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit) + } + p.next() + return lit +} + +func (p *parser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +func (p *parser) parseString() string { + str, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + return str +} + +// unquotedString = { unquotedStringChar } . +// unquotedStringChar = <neither a whitespace nor a ';' char> . +func (p *parser) parseUnquotedString() string { + if p.tok == scanner.EOF { + p.error("unexpected EOF") + } + var buf bytes.Buffer + buf.WriteString(p.scanner.TokenText()) + // This loop needs to examine each character before deciding whether to consume it. If we see a semicolon, + // we need to let it be consumed by p.next(). + for ch := p.scanner.Peek(); ch != ';' && ch != scanner.EOF && p.scanner.Whitespace&(1<<uint(ch)) == 0; ch = p.scanner.Peek() { + buf.WriteRune(ch) + p.scanner.Next() + } + p.next() + return buf.String() +} + +func (p *parser) next() { + p.tok = p.scanner.Scan() + switch p.tok { + case scanner.Ident, scanner.Int, scanner.Float, scanner.String, '·': + p.lit = p.scanner.TokenText() + default: + p.lit = "" + } +} + +func (p *parser) parseQualifiedName() (path, name string) { + return p.parseQualifiedNameStr(p.parseString()) +} + +func (p *parser) parseUnquotedQualifiedName() (path, name string) { + return p.parseQualifiedNameStr(p.parseUnquotedString()) +} + +// qualifiedName = [ ["."] unquotedString "." ] unquotedString . +// +// The above production uses greedy matching. +func (p *parser) parseQualifiedNameStr(unquotedName string) (pkgpath, name string) { + parts := strings.Split(unquotedName, ".") + if parts[0] == "" { + parts = parts[1:] + } + + switch len(parts) { + case 0: + p.errorf("malformed qualified name: %q", unquotedName) + case 1: + // unqualified name + pkgpath = p.pkgpath + name = parts[0] + default: + // qualified name, which may contain periods + pkgpath = strings.Join(parts[0:len(parts)-1], ".") + name = parts[len(parts)-1] + } + + return +} + +// getPkg returns the package for a given path. If the package is +// not found but we have a package name, create the package and +// add it to the p.imports map. +// +func (p *parser) getPkg(pkgpath, name string) *types.Package { + // package unsafe is not in the imports map - handle explicitly + if pkgpath == "unsafe" { + return types.Unsafe + } + pkg := p.imports[pkgpath] + if pkg == nil && name != "" { + pkg = types.NewPackage(pkgpath, name) + p.imports[pkgpath] = pkg + } + return pkg +} + +// parseExportedName is like parseQualifiedName, but +// the package path is resolved to an imported *types.Package. +// +// ExportedName = string [string] . +func (p *parser) parseExportedName() (pkg *types.Package, name string) { + path, name := p.parseQualifiedName() + var pkgname string + if p.tok == scanner.String { + pkgname = p.parseString() + } + pkg = p.getPkg(path, pkgname) + if pkg == nil { + p.errorf("package %s (path = %q) not found", name, path) + } + return +} + +// Name = QualifiedName | "?" . +func (p *parser) parseName() string { + if p.tok == '?' { + // Anonymous. + p.next() + return "" + } + // The package path is redundant for us. Don't try to parse it. + _, name := p.parseUnquotedQualifiedName() + return name +} + +func deref(typ types.Type) types.Type { + if p, _ := typ.(*types.Pointer); p != nil { + typ = p.Elem() + } + return typ +} + +// Field = Name Type [string] . +func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) { + name := p.parseName() + typ := p.parseType(pkg) + anon := false + if name == "" { + anon = true + switch typ := deref(typ).(type) { + case *types.Basic: + name = typ.Name() + case *types.Named: + name = typ.Obj().Name() + default: + p.error("anonymous field expected") + } + } + field = types.NewField(token.NoPos, pkg, name, typ, anon) + if p.tok == scanner.String { + tag = p.parseString() + } + return +} + +// Param = Name ["..."] Type . +func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bool) { + name := p.parseName() + if p.tok == '.' { + p.next() + p.expect('.') + p.expect('.') + isVariadic = true + } + typ := p.parseType(pkg) + if isVariadic { + typ = types.NewSlice(typ) + } + param = types.NewParam(token.NoPos, pkg, name, typ) + return +} + +// Var = Name Type . +func (p *parser) parseVar(pkg *types.Package) *types.Var { + name := p.parseName() + return types.NewVar(token.NoPos, pkg, name, p.parseType(pkg)) +} + +// ConstValue = string | "false" | "true" | ["-"] (int ["'"] | FloatOrComplex) . +// FloatOrComplex = float ["i" | ("+"|"-") float "i"] . +func (p *parser) parseConstValue() (val constant.Value, typ types.Type) { + switch p.tok { + case scanner.String: + str := p.parseString() + val = constant.MakeString(str) + typ = types.Typ[types.UntypedString] + return + + case scanner.Ident: + b := false + switch p.lit { + case "false": + case "true": + b = true + + default: + p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + + p.next() + val = constant.MakeBool(b) + typ = types.Typ[types.UntypedBool] + return + } + + sign := "" + if p.tok == '-' { + p.next() + sign = "-" + } + + switch p.tok { + case scanner.Int: + val = constant.MakeFromLiteral(sign+p.lit, token.INT, 0) + if val == nil { + p.error("could not parse integer literal") + } + + p.next() + if p.tok == '\'' { + p.next() + typ = types.Typ[types.UntypedRune] + } else { + typ = types.Typ[types.UntypedInt] + } + + case scanner.Float: + re := sign + p.lit + p.next() + + var im string + switch p.tok { + case '+': + p.next() + im = p.expect(scanner.Float) + + case '-': + p.next() + im = "-" + p.expect(scanner.Float) + + case scanner.Ident: + // re is in fact the imaginary component. Expect "i" below. + im = re + re = "0" + + default: + val = constant.MakeFromLiteral(re, token.FLOAT, 0) + if val == nil { + p.error("could not parse float literal") + } + typ = types.Typ[types.UntypedFloat] + return + } + + p.expectKeyword("i") + reval := constant.MakeFromLiteral(re, token.FLOAT, 0) + if reval == nil { + p.error("could not parse real component of complex literal") + } + imval := constant.MakeFromLiteral(im+"i", token.IMAG, 0) + if imval == nil { + p.error("could not parse imag component of complex literal") + } + val = constant.BinaryOp(reval, token.ADD, imval) + typ = types.Typ[types.UntypedComplex] + + default: + p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + + return +} + +// Const = Name [Type] "=" ConstValue . +func (p *parser) parseConst(pkg *types.Package) *types.Const { + name := p.parseName() + var typ types.Type + if p.tok == '<' { + typ = p.parseType(pkg) + } + p.expect('=') + val, vtyp := p.parseConstValue() + if typ == nil { + typ = vtyp + } + return types.NewConst(token.NoPos, pkg, name, typ, val) +} + +// TypeName = ExportedName . +func (p *parser) parseTypeName() *types.TypeName { + pkg, name := p.parseExportedName() + scope := pkg.Scope() + if obj := scope.Lookup(name); obj != nil { + return obj.(*types.TypeName) + } + obj := types.NewTypeName(token.NoPos, pkg, name, nil) + // a named type may be referred to before the underlying type + // is known - set it up + types.NewNamed(obj, nil, nil) + scope.Insert(obj) + return obj +} + +// NamedType = TypeName Type { Method } . +// Method = "func" "(" Param ")" Name ParamList ResultList ";" . +func (p *parser) parseNamedType(n int) types.Type { + obj := p.parseTypeName() + + pkg := obj.Pkg() + typ := obj.Type() + p.typeMap[n] = typ + + nt, ok := typ.(*types.Named) + if !ok { + // This can happen for unsafe.Pointer, which is a TypeName holding a Basic type. + pt := p.parseType(pkg) + if pt != typ { + p.error("unexpected underlying type for non-named TypeName") + } + return typ + } + + underlying := p.parseType(pkg) + if nt.Underlying() == nil { + nt.SetUnderlying(underlying.Underlying()) + } + + for p.tok == scanner.Ident { + // collect associated methods + p.expectKeyword("func") + p.expect('(') + receiver, _ := p.parseParam(pkg) + p.expect(')') + name := p.parseName() + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + p.expect(';') + + sig := types.NewSignature(receiver, params, results, isVariadic) + nt.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig)) + } + + return nt +} + +func (p *parser) parseInt() int64 { + lit := p.expect(scanner.Int) + n, err := strconv.ParseInt(lit, 10, 0) + if err != nil { + p.error(err) + } + return n +} + +// ArrayOrSliceType = "[" [ int ] "]" Type . +func (p *parser) parseArrayOrSliceType(pkg *types.Package) types.Type { + p.expect('[') + if p.tok == ']' { + p.next() + return types.NewSlice(p.parseType(pkg)) + } + + n := p.parseInt() + p.expect(']') + return types.NewArray(p.parseType(pkg), n) +} + +// MapType = "map" "[" Type "]" Type . +func (p *parser) parseMapType(pkg *types.Package) types.Type { + p.expectKeyword("map") + p.expect('[') + key := p.parseType(pkg) + p.expect(']') + elem := p.parseType(pkg) + return types.NewMap(key, elem) +} + +// ChanType = "chan" ["<-" | "-<"] Type . +func (p *parser) parseChanType(pkg *types.Package) types.Type { + p.expectKeyword("chan") + dir := types.SendRecv + switch p.tok { + case '-': + p.next() + p.expect('<') + dir = types.SendOnly + + case '<': + // don't consume '<' if it belongs to Type + if p.scanner.Peek() == '-' { + p.next() + p.expect('-') + dir = types.RecvOnly + } + } + + return types.NewChan(dir, p.parseType(pkg)) +} + +// StructType = "struct" "{" { Field } "}" . +func (p *parser) parseStructType(pkg *types.Package) types.Type { + p.expectKeyword("struct") + + var fields []*types.Var + var tags []string + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + field, tag := p.parseField(pkg) + p.expect(';') + fields = append(fields, field) + tags = append(tags, tag) + } + p.expect('}') + + return types.NewStruct(fields, tags) +} + +// ParamList = "(" [ { Parameter "," } Parameter ] ")" . +func (p *parser) parseParamList(pkg *types.Package) (*types.Tuple, bool) { + var list []*types.Var + isVariadic := false + + p.expect('(') + for p.tok != ')' && p.tok != scanner.EOF { + if len(list) > 0 { + p.expect(',') + } + par, variadic := p.parseParam(pkg) + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return types.NewTuple(list...), isVariadic +} + +// ResultList = Type | ParamList . +func (p *parser) parseResultList(pkg *types.Package) *types.Tuple { + switch p.tok { + case '<': + return types.NewTuple(types.NewParam(token.NoPos, pkg, "", p.parseType(pkg))) + + case '(': + params, _ := p.parseParamList(pkg) + return params + + default: + return nil + } +} + +// FunctionType = ParamList ResultList . +func (p *parser) parseFunctionType(pkg *types.Package) *types.Signature { + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + return types.NewSignature(nil, params, results, isVariadic) +} + +// Func = Name FunctionType . +func (p *parser) parseFunc(pkg *types.Package) *types.Func { + name := p.parseName() + if strings.ContainsRune(name, '$') { + // This is a Type$equal or Type$hash function, which we don't want to parse, + // except for the types. + p.discardDirectiveWhileParsingTypes(pkg) + return nil + } + return types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg)) +} + +// InterfaceType = "interface" "{" { ("?" Type | Func) ";" } "}" . +func (p *parser) parseInterfaceType(pkg *types.Package) types.Type { + p.expectKeyword("interface") + + var methods []*types.Func + var typs []*types.Named + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + if p.tok == '?' { + p.next() + typs = append(typs, p.parseType(pkg).(*types.Named)) + } else { + method := p.parseFunc(pkg) + methods = append(methods, method) + } + p.expect(';') + } + p.expect('}') + + return types.NewInterface(methods, typs) +} + +// PointerType = "*" ("any" | Type) . +func (p *parser) parsePointerType(pkg *types.Package) types.Type { + p.expect('*') + if p.tok == scanner.Ident { + p.expectKeyword("any") + return types.Typ[types.UnsafePointer] + } + return types.NewPointer(p.parseType(pkg)) +} + +// TypeDefinition = NamedType | MapType | ChanType | StructType | InterfaceType | PointerType | ArrayOrSliceType | FunctionType . +func (p *parser) parseTypeDefinition(pkg *types.Package, n int) types.Type { + var t types.Type + switch p.tok { + case scanner.String: + t = p.parseNamedType(n) + + case scanner.Ident: + switch p.lit { + case "map": + t = p.parseMapType(pkg) + + case "chan": + t = p.parseChanType(pkg) + + case "struct": + t = p.parseStructType(pkg) + + case "interface": + t = p.parseInterfaceType(pkg) + } + + case '*': + t = p.parsePointerType(pkg) + + case '[': + t = p.parseArrayOrSliceType(pkg) + + case '(': + t = p.parseFunctionType(pkg) + } + + p.typeMap[n] = t + return t +} + +const ( + // From gofrontend/go/export.h + // Note that these values are negative in the gofrontend and have been made positive + // in the gccgoimporter. + gccgoBuiltinINT8 = 1 + gccgoBuiltinINT16 = 2 + gccgoBuiltinINT32 = 3 + gccgoBuiltinINT64 = 4 + gccgoBuiltinUINT8 = 5 + gccgoBuiltinUINT16 = 6 + gccgoBuiltinUINT32 = 7 + gccgoBuiltinUINT64 = 8 + gccgoBuiltinFLOAT32 = 9 + gccgoBuiltinFLOAT64 = 10 + gccgoBuiltinINT = 11 + gccgoBuiltinUINT = 12 + gccgoBuiltinUINTPTR = 13 + gccgoBuiltinBOOL = 15 + gccgoBuiltinSTRING = 16 + gccgoBuiltinCOMPLEX64 = 17 + gccgoBuiltinCOMPLEX128 = 18 + gccgoBuiltinERROR = 19 + gccgoBuiltinBYTE = 20 + gccgoBuiltinRUNE = 21 +) + +func lookupBuiltinType(typ int) types.Type { + return [...]types.Type{ + gccgoBuiltinINT8: types.Typ[types.Int8], + gccgoBuiltinINT16: types.Typ[types.Int16], + gccgoBuiltinINT32: types.Typ[types.Int32], + gccgoBuiltinINT64: types.Typ[types.Int64], + gccgoBuiltinUINT8: types.Typ[types.Uint8], + gccgoBuiltinUINT16: types.Typ[types.Uint16], + gccgoBuiltinUINT32: types.Typ[types.Uint32], + gccgoBuiltinUINT64: types.Typ[types.Uint64], + gccgoBuiltinFLOAT32: types.Typ[types.Float32], + gccgoBuiltinFLOAT64: types.Typ[types.Float64], + gccgoBuiltinINT: types.Typ[types.Int], + gccgoBuiltinUINT: types.Typ[types.Uint], + gccgoBuiltinUINTPTR: types.Typ[types.Uintptr], + gccgoBuiltinBOOL: types.Typ[types.Bool], + gccgoBuiltinSTRING: types.Typ[types.String], + gccgoBuiltinCOMPLEX64: types.Typ[types.Complex64], + gccgoBuiltinCOMPLEX128: types.Typ[types.Complex128], + gccgoBuiltinERROR: types.Universe.Lookup("error").Type(), + gccgoBuiltinBYTE: types.Universe.Lookup("byte").Type(), + gccgoBuiltinRUNE: types.Universe.Lookup("rune").Type(), + }[typ] +} + +// Type = "<" "type" ( "-" int | int [ TypeDefinition ] ) ">" . +func (p *parser) parseType(pkg *types.Package) (t types.Type) { + p.expect('<') + p.expectKeyword("type") + + switch p.tok { + case scanner.Int: + n := p.parseInt() + + if p.tok == '>' { + t = p.typeMap[int(n)] + } else { + t = p.parseTypeDefinition(pkg, int(n)) + } + + case '-': + p.next() + n := p.parseInt() + t = lookupBuiltinType(int(n)) + + default: + p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil + } + + p.expect('>') + return +} + +// PackageInit = unquotedString unquotedString int . +func (p *parser) parsePackageInit() PackageInit { + name := p.parseUnquotedString() + initfunc := p.parseUnquotedString() + priority := int(p.parseInt()) + return PackageInit{Name: name, InitFunc: initfunc, Priority: priority} +} + +// Throw away tokens until we see a ';'. If we see a '<', attempt to parse as a type. +func (p *parser) discardDirectiveWhileParsingTypes(pkg *types.Package) { + for { + switch p.tok { + case ';': + return + case '<': + p.parseType(p.pkg) + case scanner.EOF: + p.error("unexpected EOF") + default: + p.next() + } + } +} + +// Create the package if we have parsed both the package path and package name. +func (p *parser) maybeCreatePackage() { + if p.pkgname != "" && p.pkgpath != "" { + p.pkg = p.getPkg(p.pkgpath, p.pkgname) + } +} + +// InitDataDirective = "v1" ";" | +// "priority" int ";" | +// "init" { PackageInit } ";" | +// "checksum" unquotedString ";" . +func (p *parser) parseInitDataDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1": + p.next() + p.expect(';') + + case "priority": + p.next() + p.initdata.Priority = int(p.parseInt()) + p.expect(';') + + case "init": + p.next() + for p.tok != ';' && p.tok != scanner.EOF { + p.initdata.Inits = append(p.initdata.Inits, p.parsePackageInit()) + } + p.expect(';') + + case "checksum": + // Don't let the scanner try to parse the checksum as a number. + defer func(mode uint) { + p.scanner.Mode = mode + }(p.scanner.Mode) + p.scanner.Mode &^= scanner.ScanInts | scanner.ScanFloats + p.next() + p.parseUnquotedString() + p.expect(';') + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Directive = InitDataDirective | +// "package" unquotedString ";" | +// "pkgpath" unquotedString ";" | +// "import" unquotedString unquotedString string ";" | +// "func" Func ";" | +// "type" Type ";" | +// "var" Var ";" | +// "const" Const ";" . +func (p *parser) parseDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1", "priority", "init", "checksum": + p.parseInitDataDirective() + + case "package": + p.next() + p.pkgname = p.parseUnquotedString() + p.maybeCreatePackage() + p.expect(';') + + case "pkgpath": + p.next() + p.pkgpath = p.parseUnquotedString() + p.maybeCreatePackage() + p.expect(';') + + case "import": + p.next() + pkgname := p.parseUnquotedString() + pkgpath := p.parseUnquotedString() + p.getPkg(pkgpath, pkgname) + p.parseString() + p.expect(';') + + case "func": + p.next() + fun := p.parseFunc(p.pkg) + if fun != nil { + p.pkg.Scope().Insert(fun) + } + p.expect(';') + + case "type": + p.next() + p.parseType(p.pkg) + p.expect(';') + + case "var": + p.next() + v := p.parseVar(p.pkg) + p.pkg.Scope().Insert(v) + p.expect(';') + + case "const": + p.next() + c := p.parseConst(p.pkg) + p.pkg.Scope().Insert(c) + p.expect(';') + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Package = { Directive } . +func (p *parser) parsePackage() *types.Package { + for p.tok != scanner.EOF { + p.parseDirective() + } + for _, typ := range p.typeMap { + if it, ok := typ.(*types.Interface); ok { + it.Complete() + } + } + p.pkg.MarkComplete() + return p.pkg +} + +// InitData = { InitDataDirective } . +func (p *parser) parseInitData() { + for p.tok != scanner.EOF { + p.parseInitDataDirective() + } +} diff --git a/libgo/go/go/internal/gccgoimporter/parser_test.go b/libgo/go/go/internal/gccgoimporter/parser_test.go new file mode 100644 index 00000000000..b96486f20ad --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/parser_test.go @@ -0,0 +1,72 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bytes" + "go/types" + "strings" + "testing" + "text/scanner" +) + +var typeParserTests = []struct { + id, typ, want, underlying, methods string +}{ + {id: "foo", typ: "<type -1>", want: "int8"}, + {id: "foo", typ: "<type 1 *<type -19>>", want: "*error"}, + {id: "foo", typ: "<type 1 *any>", want: "unsafe.Pointer"}, + {id: "foo", typ: "<type 1 \"Bar\" <type 2 *<type 1>>>", want: "foo.Bar", underlying: "*foo.Bar"}, + {id: "foo", typ: "<type 1 \"bar.Foo\" \"bar\" <type -1> func (? <type 1>) M (); >", want: "bar.Foo", underlying: "int8", methods: "func (bar.Foo).M()"}, + {id: "foo", typ: "<type 1 \".bar.foo\" \"bar\" <type -1>>", want: "bar.foo", underlying: "int8"}, + {id: "foo", typ: "<type 1 []<type -1>>", want: "[]int8"}, + {id: "foo", typ: "<type 1 [42]<type -1>>", want: "[42]int8"}, + {id: "foo", typ: "<type 1 map [<type -1>] <type -2>>", want: "map[int8]int16"}, + {id: "foo", typ: "<type 1 chan <type -1>>", want: "chan int8"}, + {id: "foo", typ: "<type 1 chan <- <type -1>>", want: "<-chan int8"}, + {id: "foo", typ: "<type 1 chan -< <type -1>>", want: "chan<- int8"}, + {id: "foo", typ: "<type 1 struct { I8 <type -1>; I16 <type -2> \"i16\"; }>", want: "struct{I8 int8; I16 int16 \"i16\"}"}, + {id: "foo", typ: "<type 1 interface { Foo (a <type -1>, b <type -2>) <type -1>; Bar (? <type -2>, ? ...<type -1>) (? <type -2>, ? <type -1>); Baz (); }>", want: "interface{Bar(int16, ...int8) (int16, int8); Baz(); Foo(a int8, b int16) int8}"}, + {id: "foo", typ: "<type 1 (? <type -1>) <type -2>>", want: "func(int8) int16"}, +} + +func TestTypeParser(t *testing.T) { + for _, test := range typeParserTests { + var p parser + p.init("test.gox", strings.NewReader(test.typ), make(map[string]*types.Package)) + p.pkgname = test.id + p.pkgpath = test.id + p.maybeCreatePackage() + typ := p.parseType(p.pkg) + + if p.tok != scanner.EOF { + t.Errorf("expected full parse, stopped at %q", p.lit) + } + + got := typ.String() + if got != test.want { + t.Errorf("got type %q, expected %q", got, test.want) + } + + if test.underlying != "" { + underlying := typ.Underlying().String() + if underlying != test.underlying { + t.Errorf("got underlying type %q, expected %q", underlying, test.underlying) + } + } + + if test.methods != "" { + nt := typ.(*types.Named) + var buf bytes.Buffer + for i := 0; i != nt.NumMethods(); i++ { + buf.WriteString(nt.Method(i).String()) + } + methods := buf.String() + if methods != test.methods { + t.Errorf("got methods %q, expected %q", methods, test.methods) + } + } + } +} diff --git a/libgo/go/go/internal/gccgoimporter/testdata/complexnums.go b/libgo/go/go/internal/gccgoimporter/testdata/complexnums.go new file mode 100644 index 00000000000..a51b6b01c0b --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/testdata/complexnums.go @@ -0,0 +1,6 @@ +package complexnums + +const NN = -1 - 1i +const NP = -1 + 1i +const PN = 1 - 1i +const PP = 1 + 1i diff --git a/libgo/go/go/internal/gccgoimporter/testdata/imports.go b/libgo/go/go/internal/gccgoimporter/testdata/imports.go new file mode 100644 index 00000000000..7907316a607 --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/testdata/imports.go @@ -0,0 +1,5 @@ +package imports + +import "fmt" + +var Hello = fmt.Sprintf("Hello, world") diff --git a/libgo/go/go/internal/gccgoimporter/testdata/pointer.go b/libgo/go/go/internal/gccgoimporter/testdata/pointer.go new file mode 100644 index 00000000000..4ebc67137d6 --- /dev/null +++ b/libgo/go/go/internal/gccgoimporter/testdata/pointer.go @@ -0,0 +1,3 @@ +package pointer + +type Int8Ptr *int8 diff --git a/libgo/go/go/internal/gcimporter/exportdata.go b/libgo/go/go/internal/gcimporter/exportdata.go new file mode 100644 index 00000000000..657742bb6d7 --- /dev/null +++ b/libgo/go/go/internal/gcimporter/exportdata.go @@ -0,0 +1,108 @@ +// 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 FindExportData. + +package gcimporter + +import ( + "bufio" + "errors" + "fmt" + "io" + "strconv" + "strings" +) + +func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { + // See $GOROOT/include/ar.h. + hdr := make([]byte, 16+12+6+6+8+10+2) + _, err = io.ReadFull(r, hdr) + if err != nil { + return + } + // leave for debugging + if false { + fmt.Printf("header: %s", hdr) + } + s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) + size, err = strconv.Atoi(s) + if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { + err = errors.New("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindExportData positions the reader r at the beginning of the +// export data section of an underlying GC-created object/archive +// file by reading from it. The reader must be positioned at the +// start of the file before calling this function. +// +func FindExportData(r *bufio.Reader) (err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + return + } + if string(line) == "!<arch>\n" { + // Archive file. Scan to __.PKGDEF. + var name string + var size int + if name, size, err = readGopackHeader(r); err != nil { + return + } + + // Optional leading __.GOSYMDEF or __.SYMDEF. + // Read and discard. + if name == "__.SYMDEF" || name == "__.GOSYMDEF" { + const block = 4096 + tmp := make([]byte, block) + for size > 0 { + n := size + if n > block { + n = block + } + if _, err = io.ReadFull(r, tmp[:n]); err != nil { + return + } + size -= n + } + + if name, size, err = readGopackHeader(r); err != nil { + return + } + } + + // First real entry should be __.PKGDEF. + if name != "__.PKGDEF" { + err = errors.New("go archive is missing __.PKGDEF") + return + } + + // Read first line of __.PKGDEF data, so that line + // is once again the first line of the input. + if line, err = r.ReadSlice('\n'); err != nil { + return + } + } + + // Now at __.PKGDEF in archive or still at beginning of file. + // Either way, line should begin with "go object ". + if !strings.HasPrefix(string(line), "go object ") { + err = errors.New("not a go object file") + return + } + + // Skip over object header to export data. + // Begins after first line with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + return + } + } + + return +} diff --git a/libgo/go/go/internal/gcimporter/gcimporter.go b/libgo/go/go/internal/gcimporter/gcimporter.go new file mode 100644 index 00000000000..1d485cf9cbf --- /dev/null +++ b/libgo/go/go/internal/gcimporter/gcimporter.go @@ -0,0 +1,991 @@ +// 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 gcimporter implements Import for gc-generated object files. +package gcimporter // import "go/internal/gcimporter" + +import ( + "bufio" + "errors" + "fmt" + "go/build" + "go/token" + "io" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "text/scanner" + + exact "go/constant" + "go/types" +) + +// debugging/development support +const debug = false + +var pkgExts = [...]string{".a", ".o"} + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). +// If no file was found, an empty filename is returned. +// +func FindPkg(path, srcDir string) (filename, id string) { + if len(path) == 0 { + return + } + + id = path + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + return + } + noext = strings.TrimSuffix(bp.PkgObj, ".a") + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// ImportData imports a package by reading the gc-generated export data, +// adds the corresponding package object to the packages map indexed by id, +// and returns the object. +// +// The packages map must contains all packages already imported. The data +// reader position must be the beginning of the export data section. The +// filename is only used in error messages. +// +// If packages[id] contains the completely imported package, that package +// can be used directly, and there is no need to call this function (but +// there is also no harm but for extra time used). +// +func ImportData(packages map[string]*types.Package, filename, id string, data io.Reader) (pkg *types.Package, err error) { + // support for parser error handling + defer func() { + switch r := recover().(type) { + case nil: + // nothing to do + case importError: + err = r + default: + panic(r) // internal error + } + }() + + var p parser + p.init(filename, id, data, packages) + pkg = p.parseExport() + + return +} + +// Import imports a gc-generated package given its import path, adds the +// corresponding package object to the packages map, and returns the object. +// Local import paths are interpreted relative to the current working directory. +// The packages map must contain all packages already imported. +// +func Import(packages map[string]*types.Package, path string) (pkg *types.Package, err error) { + // package "unsafe" is handled by the type checker + if path == "unsafe" { + panic(`gcimporter.Import called for package "unsafe"`) + } + + srcDir := "." + if build.IsLocalImport(path) { + srcDir, err = os.Getwd() + if err != nil { + return + } + } + + filename, id := FindPkg(path, srcDir) + if filename == "" { + err = fmt.Errorf("can't find import: %s", id) + return + } + + // no need to re-import if the package was imported completely before + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return + } + defer func() { + f.Close() + if err != nil { + // add file name to error + err = fmt.Errorf("reading export data: %s: %v", filename, err) + } + }() + + buf := bufio.NewReader(f) + if err = FindExportData(buf); err != nil { + return + } + + pkg, err = ImportData(packages, filename, id, buf) + + return +} + +// ---------------------------------------------------------------------------- +// Parser + +// TODO(gri) Imported objects don't have position information. +// Ideally use the debug table line info; alternatively +// create some fake position (or the position of the +// import). That way error messages referring to imported +// objects can print meaningful information. + +// parser parses the exports inside a gc compiler-produced +// object/archive file and populates its scope with the results. +type parser struct { + scanner scanner.Scanner + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + id string // package id of imported package + sharedPkgs map[string]*types.Package // package id -> package object (across importer) + localPkgs map[string]*types.Package // package id -> package object (just this package) +} + +func (p *parser) init(filename, id string, src io.Reader, packages map[string]*types.Package) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments + p.scanner.Whitespace = 1<<'\t' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() + p.id = id + p.sharedPkgs = packages + if debug { + // check consistency of packages map + for _, pkg := range packages { + if pkg.Name() == "" { + fmt.Printf("no package name for %s\n", pkg.Path()) + } + } + } +} + +func (p *parser) next() { + p.tok = p.scanner.Scan() + switch p.tok { + case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·': + p.lit = p.scanner.TokenText() + default: + p.lit = "" + } + if debug { + fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit) + } +} + +func declTypeName(pkg *types.Package, name string) *types.TypeName { + scope := pkg.Scope() + if obj := scope.Lookup(name); obj != nil { + return obj.(*types.TypeName) + } + obj := types.NewTypeName(token.NoPos, pkg, name, nil) + // a named type may be referred to before the underlying type + // is known - set it up + types.NewNamed(obj, nil, nil) + scope.Insert(obj) + return obj +} + +// ---------------------------------------------------------------------------- +// Error handling + +// Internal errors are boxed as importErrors. +type importError struct { + pos scanner.Position + err error +} + +func (e importError) Error() string { + return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err) +} + +func (p *parser) error(err interface{}) { + if s, ok := err.(string); ok { + err = errors.New(s) + } + // panic with a runtime.Error if err is not an error + panic(importError{p.scanner.Pos(), err.(error)}) +} + +func (p *parser) errorf(format string, args ...interface{}) { + p.error(fmt.Sprintf(format, args...)) +} + +func (p *parser) expect(tok rune) string { + lit := p.lit + if p.tok != tok { + p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit) + } + p.next() + return lit +} + +func (p *parser) expectSpecial(tok string) { + sep := 'x' // not white space + i := 0 + for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' { + sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token + p.next() + i++ + } + if i < len(tok) { + p.errorf("expected %q, got %q", tok, tok[0:i]) + } +} + +func (p *parser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +// ---------------------------------------------------------------------------- +// Qualified and unqualified names + +// PackageId = string_lit . +// +func (p *parser) parsePackageId() string { + id, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + // id == "" stands for the imported package id + // (only known at time of package installation) + if id == "" { + id = p.id + } + return id +} + +// PackageName = ident . +// +func (p *parser) parsePackageName() string { + return p.expect(scanner.Ident) +} + +// dotIdentifier = ( ident | '·' ) { ident | int | '·' } . +func (p *parser) parseDotIdent() string { + ident := "" + if p.tok != scanner.Int { + sep := 'x' // not white space + for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' { + ident += p.lit + sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token + p.next() + } + } + if ident == "" { + p.expect(scanner.Ident) // use expect() for error handling + } + return ident +} + +// QualifiedName = "@" PackageId "." ( "?" | dotIdentifier ) . +// +func (p *parser) parseQualifiedName() (id, name string) { + p.expect('@') + id = p.parsePackageId() + p.expect('.') + // Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields. + if p.tok == '?' { + p.next() + } else { + name = p.parseDotIdent() + } + return +} + +// getPkg returns the package for a given id. If the package is +// not found but we have a package name, create the package and +// add it to the p.localPkgs and p.sharedPkgs maps. +// +// id identifies a package, usually by a canonical package path like +// "encoding/json" but possibly by a non-canonical import path like +// "./json". +// +func (p *parser) getPkg(id, name string) *types.Package { + // package unsafe is not in the packages maps - handle explicitly + if id == "unsafe" { + return types.Unsafe + } + + pkg := p.localPkgs[id] + if pkg == nil && name != "" { + // first import of id from this package + pkg = p.sharedPkgs[id] + if pkg == nil { + // first import of id by this importer + pkg = types.NewPackage(id, name) + p.sharedPkgs[id] = pkg + } + + if p.localPkgs == nil { + p.localPkgs = make(map[string]*types.Package) + } + p.localPkgs[id] = pkg + } + return pkg +} + +// parseExportedName is like parseQualifiedName, but +// the package id is resolved to an imported *types.Package. +// +func (p *parser) parseExportedName() (pkg *types.Package, name string) { + id, name := p.parseQualifiedName() + pkg = p.getPkg(id, "") + if pkg == nil { + p.errorf("%s package not found", id) + } + return +} + +// ---------------------------------------------------------------------------- +// Types + +// BasicType = identifier . +// +func (p *parser) parseBasicType() types.Type { + id := p.expect(scanner.Ident) + obj := types.Universe.Lookup(id) + if obj, ok := obj.(*types.TypeName); ok { + return obj.Type() + } + p.errorf("not a basic type: %s", id) + return nil +} + +// ArrayType = "[" int_lit "]" Type . +// +func (p *parser) parseArrayType() types.Type { + // "[" already consumed and lookahead known not to be "]" + lit := p.expect(scanner.Int) + p.expect(']') + elem := p.parseType() + n, err := strconv.ParseInt(lit, 10, 64) + if err != nil { + p.error(err) + } + return types.NewArray(elem, n) +} + +// MapType = "map" "[" Type "]" Type . +// +func (p *parser) parseMapType() types.Type { + p.expectKeyword("map") + p.expect('[') + key := p.parseType() + p.expect(']') + elem := p.parseType() + return types.NewMap(key, elem) +} + +// Name = identifier | "?" | QualifiedName . +// +// If materializePkg is set, the returned package is guaranteed to be set. +// For fully qualified names, the returned package may be a fake package +// (without name, scope, and not in the p.sharedPkgs map), created for the +// sole purpose of providing a package path. Fake packages are created +// when the package id is not found in the p.sharedPkgs map; in that case +// we cannot create a real package because we don't have a package name. +// For non-qualified names, the returned package is the imported package. +// +func (p *parser) parseName(materializePkg bool) (pkg *types.Package, name string) { + switch p.tok { + case scanner.Ident: + pkg = p.sharedPkgs[p.id] + name = p.lit + p.next() + case '?': + // anonymous + pkg = p.sharedPkgs[p.id] + p.next() + case '@': + // exported name prefixed with package path + var id string + id, name = p.parseQualifiedName() + if materializePkg { + // we don't have a package name - if the package + // doesn't exist yet, create a fake package instead + pkg = p.getPkg(id, "") + if pkg == nil { + pkg = types.NewPackage(id, "") + } + } + default: + p.error("name expected") + } + return +} + +func deref(typ types.Type) types.Type { + if p, _ := typ.(*types.Pointer); p != nil { + return p.Elem() + } + return typ +} + +// Field = Name Type [ string_lit ] . +// +func (p *parser) parseField() (*types.Var, string) { + pkg, name := p.parseName(true) + typ := p.parseType() + anonymous := false + if name == "" { + // anonymous field - typ must be T or *T and T must be a type name + switch typ := deref(typ).(type) { + case *types.Basic: // basic types are named types + pkg = nil + name = typ.Name() + case *types.Named: + name = typ.Obj().Name() + default: + p.errorf("anonymous field expected") + } + anonymous = true + } + tag := "" + if p.tok == scanner.String { + s := p.expect(scanner.String) + var err error + tag, err = strconv.Unquote(s) + if err != nil { + p.errorf("invalid struct tag %s: %s", s, err) + } + } + return types.NewField(token.NoPos, pkg, name, typ, anonymous), tag +} + +// StructType = "struct" "{" [ FieldList ] "}" . +// FieldList = Field { ";" Field } . +// +func (p *parser) parseStructType() types.Type { + var fields []*types.Var + var tags []string + + p.expectKeyword("struct") + p.expect('{') + for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ { + if i > 0 { + p.expect(';') + } + fld, tag := p.parseField() + if tag != "" && tags == nil { + tags = make([]string, i) + } + if tags != nil { + tags = append(tags, tag) + } + fields = append(fields, fld) + } + p.expect('}') + + return types.NewStruct(fields, tags) +} + +// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] . +// +func (p *parser) parseParameter() (par *types.Var, isVariadic bool) { + _, name := p.parseName(false) + // remove gc-specific parameter numbering + if i := strings.Index(name, "·"); i >= 0 { + name = name[:i] + } + if p.tok == '.' { + p.expectSpecial("...") + isVariadic = true + } + typ := p.parseType() + if isVariadic { + typ = types.NewSlice(typ) + } + // ignore argument tag (e.g. "noescape") + if p.tok == scanner.String { + p.next() + } + // TODO(gri) should we provide a package? + par = types.NewVar(token.NoPos, nil, name, typ) + return +} + +// Parameters = "(" [ ParameterList ] ")" . +// ParameterList = { Parameter "," } Parameter . +// +func (p *parser) parseParameters() (list []*types.Var, isVariadic bool) { + p.expect('(') + for p.tok != ')' && p.tok != scanner.EOF { + if len(list) > 0 { + p.expect(',') + } + par, variadic := p.parseParameter() + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return +} + +// Signature = Parameters [ Result ] . +// Result = Type | Parameters . +// +func (p *parser) parseSignature(recv *types.Var) *types.Signature { + params, isVariadic := p.parseParameters() + + // optional result type + var results []*types.Var + if p.tok == '(' { + var variadic bool + results, variadic = p.parseParameters() + if variadic { + p.error("... not permitted on result type") + } + } + + return types.NewSignature(recv, types.NewTuple(params...), types.NewTuple(results...), isVariadic) +} + +// InterfaceType = "interface" "{" [ MethodList ] "}" . +// MethodList = Method { ";" Method } . +// Method = Name Signature . +// +// The methods of embedded interfaces are always "inlined" +// by the compiler and thus embedded interfaces are never +// visible in the export data. +// +func (p *parser) parseInterfaceType() types.Type { + var methods []*types.Func + + p.expectKeyword("interface") + p.expect('{') + for i := 0; p.tok != '}' && p.tok != scanner.EOF; i++ { + if i > 0 { + p.expect(';') + } + pkg, name := p.parseName(true) + sig := p.parseSignature(nil) + methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig)) + } + p.expect('}') + + // Complete requires the type's embedded interfaces to be fully defined, + // but we do not define any + return types.NewInterface(methods, nil).Complete() +} + +// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . +// +func (p *parser) parseChanType() types.Type { + dir := types.SendRecv + if p.tok == scanner.Ident { + p.expectKeyword("chan") + if p.tok == '<' { + p.expectSpecial("<-") + dir = types.SendOnly + } + } else { + p.expectSpecial("<-") + p.expectKeyword("chan") + dir = types.RecvOnly + } + elem := p.parseType() + return types.NewChan(dir, elem) +} + +// Type = +// BasicType | TypeName | ArrayType | SliceType | StructType | +// PointerType | FuncType | InterfaceType | MapType | ChanType | +// "(" Type ")" . +// +// BasicType = ident . +// TypeName = ExportedName . +// SliceType = "[" "]" Type . +// PointerType = "*" Type . +// FuncType = "func" Signature . +// +func (p *parser) parseType() types.Type { + switch p.tok { + case scanner.Ident: + switch p.lit { + default: + return p.parseBasicType() + case "struct": + return p.parseStructType() + case "func": + // FuncType + p.next() + return p.parseSignature(nil) + case "interface": + return p.parseInterfaceType() + case "map": + return p.parseMapType() + case "chan": + return p.parseChanType() + } + case '@': + // TypeName + pkg, name := p.parseExportedName() + return declTypeName(pkg, name).Type() + case '[': + p.next() // look ahead + if p.tok == ']' { + // SliceType + p.next() + return types.NewSlice(p.parseType()) + } + return p.parseArrayType() + case '*': + // PointerType + p.next() + return types.NewPointer(p.parseType()) + case '<': + return p.parseChanType() + case '(': + // "(" Type ")" + p.next() + typ := p.parseType() + p.expect(')') + return typ + } + p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil +} + +// ---------------------------------------------------------------------------- +// Declarations + +// ImportDecl = "import" PackageName PackageId . +// +func (p *parser) parseImportDecl() { + p.expectKeyword("import") + name := p.parsePackageName() + p.getPkg(p.parsePackageId(), name) +} + +// int_lit = [ "+" | "-" ] { "0" ... "9" } . +// +func (p *parser) parseInt() string { + s := "" + switch p.tok { + case '-': + s = "-" + p.next() + case '+': + p.next() + } + return s + p.expect(scanner.Int) +} + +// number = int_lit [ "p" int_lit ] . +// +func (p *parser) parseNumber() (typ *types.Basic, val exact.Value) { + // mantissa + mant := exact.MakeFromLiteral(p.parseInt(), token.INT, 0) + if mant == nil { + panic("invalid mantissa") + } + + if p.lit == "p" { + // exponent (base 2) + p.next() + exp, err := strconv.ParseInt(p.parseInt(), 10, 0) + if err != nil { + p.error(err) + } + if exp < 0 { + denom := exact.MakeInt64(1) + denom = exact.Shift(denom, token.SHL, uint(-exp)) + typ = types.Typ[types.UntypedFloat] + val = exact.BinaryOp(mant, token.QUO, denom) + return + } + if exp > 0 { + mant = exact.Shift(mant, token.SHL, uint(exp)) + } + typ = types.Typ[types.UntypedFloat] + val = mant + return + } + + typ = types.Typ[types.UntypedInt] + val = mant + return +} + +// ConstDecl = "const" ExportedName [ Type ] "=" Literal . +// Literal = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit . +// bool_lit = "true" | "false" . +// complex_lit = "(" float_lit "+" float_lit "i" ")" . +// rune_lit = "(" int_lit "+" int_lit ")" . +// string_lit = `"` { unicode_char } `"` . +// +func (p *parser) parseConstDecl() { + p.expectKeyword("const") + pkg, name := p.parseExportedName() + + var typ0 types.Type + if p.tok != '=' { + typ0 = p.parseType() + } + + p.expect('=') + var typ types.Type + var val exact.Value + switch p.tok { + case scanner.Ident: + // bool_lit + if p.lit != "true" && p.lit != "false" { + p.error("expected true or false") + } + typ = types.Typ[types.UntypedBool] + val = exact.MakeBool(p.lit == "true") + p.next() + + case '-', scanner.Int: + // int_lit + typ, val = p.parseNumber() + + case '(': + // complex_lit or rune_lit + p.next() + if p.tok == scanner.Char { + p.next() + p.expect('+') + typ = types.Typ[types.UntypedRune] + _, val = p.parseNumber() + p.expect(')') + break + } + _, re := p.parseNumber() + p.expect('+') + _, im := p.parseNumber() + p.expectKeyword("i") + p.expect(')') + typ = types.Typ[types.UntypedComplex] + val = exact.BinaryOp(re, token.ADD, exact.MakeImag(im)) + + case scanner.Char: + // rune_lit + typ = types.Typ[types.UntypedRune] + val = exact.MakeFromLiteral(p.lit, token.CHAR, 0) + p.next() + + case scanner.String: + // string_lit + typ = types.Typ[types.UntypedString] + val = exact.MakeFromLiteral(p.lit, token.STRING, 0) + p.next() + + default: + p.errorf("expected literal got %s", scanner.TokenString(p.tok)) + } + + if typ0 == nil { + typ0 = typ + } + + pkg.Scope().Insert(types.NewConst(token.NoPos, pkg, name, typ0, val)) +} + +// TypeDecl = "type" ExportedName Type . +// +func (p *parser) parseTypeDecl() { + p.expectKeyword("type") + pkg, name := p.parseExportedName() + obj := declTypeName(pkg, name) + + // The type object may have been imported before and thus already + // have a type associated with it. We still need to parse the type + // structure, but throw it away if the object already has a type. + // This ensures that all imports refer to the same type object for + // a given type declaration. + typ := p.parseType() + + if name := obj.Type().(*types.Named); name.Underlying() == nil { + name.SetUnderlying(typ) + } +} + +// VarDecl = "var" ExportedName Type . +// +func (p *parser) parseVarDecl() { + p.expectKeyword("var") + pkg, name := p.parseExportedName() + typ := p.parseType() + pkg.Scope().Insert(types.NewVar(token.NoPos, pkg, name, typ)) +} + +// Func = Signature [ Body ] . +// Body = "{" ... "}" . +// +func (p *parser) parseFunc(recv *types.Var) *types.Signature { + sig := p.parseSignature(recv) + if p.tok == '{' { + p.next() + for i := 1; i > 0; p.next() { + switch p.tok { + case '{': + i++ + case '}': + i-- + } + } + } + return sig +} + +// MethodDecl = "func" Receiver Name Func . +// Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" . +// +func (p *parser) parseMethodDecl() { + // "func" already consumed + p.expect('(') + recv, _ := p.parseParameter() // receiver + p.expect(')') + + // determine receiver base type object + base := deref(recv.Type()).(*types.Named) + + // parse method name, signature, and possibly inlined body + _, name := p.parseName(true) + sig := p.parseFunc(recv) + + // methods always belong to the same package as the base type object + pkg := base.Obj().Pkg() + + // add method to type unless type was imported before + // and method exists already + // TODO(gri) This leads to a quadratic algorithm - ok for now because method counts are small. + base.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig)) +} + +// FuncDecl = "func" ExportedName Func . +// +func (p *parser) parseFuncDecl() { + // "func" already consumed + pkg, name := p.parseExportedName() + typ := p.parseFunc(nil) + pkg.Scope().Insert(types.NewFunc(token.NoPos, pkg, name, typ)) +} + +// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" . +// +func (p *parser) parseDecl() { + if p.tok == scanner.Ident { + switch p.lit { + case "import": + p.parseImportDecl() + case "const": + p.parseConstDecl() + case "type": + p.parseTypeDecl() + case "var": + p.parseVarDecl() + case "func": + p.next() // look ahead + if p.tok == '(' { + p.parseMethodDecl() + } else { + p.parseFuncDecl() + } + } + } + p.expect('\n') +} + +// ---------------------------------------------------------------------------- +// Export + +// Export = "PackageClause { Decl } "$$" . +// PackageClause = "package" PackageName [ "safe" ] "\n" . +// +func (p *parser) parseExport() *types.Package { + p.expectKeyword("package") + name := p.parsePackageName() + if p.tok == scanner.Ident && p.lit == "safe" { + // package was compiled with -u option - ignore + p.next() + } + p.expect('\n') + + pkg := p.getPkg(p.id, name) + + for p.tok != '$' && p.tok != scanner.EOF { + p.parseDecl() + } + + if ch := p.scanner.Peek(); p.tok != '$' || ch != '$' { + // don't call next()/expect() since reading past the + // export data may cause scanner errors (e.g. NUL chars) + p.errorf("expected '$$', got %s %c", scanner.TokenString(p.tok), ch) + } + + if n := p.scanner.ErrorCount; n != 0 { + p.errorf("expected no scanner errors, got %d", n) + } + + // Record all referenced packages as imports. + var imports []*types.Package + for id, pkg2 := range p.localPkgs { + if id == p.id { + continue // avoid self-edge + } + imports = append(imports, pkg2) + } + sort.Sort(byPath(imports)) + pkg.SetImports(imports) + + // package was imported completely and without errors + pkg.MarkComplete() + + return pkg +} + +type byPath []*types.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/libgo/go/go/internal/gcimporter/gcimporter_test.go b/libgo/go/go/internal/gcimporter/gcimporter_test.go new file mode 100644 index 00000000000..07993a801f7 --- /dev/null +++ b/libgo/go/go/internal/gcimporter/gcimporter_test.go @@ -0,0 +1,225 @@ +// 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 gcimporter + +import ( + "fmt" + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "go/types" +) + +// skipSpecialPlatforms causes the test to be skipped for platforms where +// builders (build.golang.org) don't have access to compiled packages for +// import. +func skipSpecialPlatforms(t *testing.T) { + switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform { + case "nacl-amd64p32", + "nacl-386", + "nacl-arm", + "darwin-arm", + "darwin-arm64": + t.Skipf("no compiled packages available for import on %s", platform) + } +} + +func compile(t *testing.T, dirname, filename string) string { + testenv.MustHaveGoBuild(t) + cmd := exec.Command("go", "tool", "compile", filename) + cmd.Dir = dirname + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("go tool compile %s failed: %s", filename, err) + } + // filename should end with ".go" + return filepath.Join(dirname, filename[:len(filename)-2]+"o") +} + +// Use the same global imports map for all tests. The effect is +// as if all tested packages were imported into a single package. +var imports = make(map[string]*types.Package) + +func testPath(t *testing.T, path string) *types.Package { + t0 := time.Now() + pkg, err := Import(imports, path) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return nil + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return pkg +} + +const maxTime = 30 * time.Second + +func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { + dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) + list, err := ioutil.ReadDir(dirname) + if err != nil { + t.Fatalf("testDir(%s): %s", dirname, err) + } + for _, f := range list { + if time.Now().After(endTime) { + t.Log("testing time used up") + return + } + switch { + case !f.IsDir(): + // try extensions + for _, ext := range pkgExts { + if strings.HasSuffix(f.Name(), ext) { + name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension + if testPath(t, filepath.Join(dir, name)) != nil { + nimports++ + } + } + } + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) + } + } + return +} + +func TestImport(t *testing.T) { + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + return + } + + if outFn := compile(t, "testdata", "exports.go"); outFn != "" { + defer os.Remove(outFn) + } + + nimports := 0 + if pkg := testPath(t, "./testdata/exports"); pkg != nil { + nimports++ + // The package's Imports should include all the types + // referenced by the exportdata, which may be more than + // the import statements in the package's source, but + // fewer than the transitive closure of dependencies. + want := `[package ast ("go/ast") package token ("go/token") package runtime ("runtime")]` + got := fmt.Sprint(pkg.Imports()) + if got != want { + t.Errorf(`Package("exports").Imports() = %s, want %s`, got, want) + } + } + nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + {"math.Pi", "const Pi untyped float"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"}, + {"math.Sin", "func Sin(x float64) float64"}, + // TODO(gri) add more tests +} + +func TestImportedTypes(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + return + } + + for _, test := range importedObjectTests { + s := strings.Split(test.name, ".") + if len(s) != 2 { + t.Fatal("inconsistent test data") + } + importPath := s[0] + objName := s[1] + + pkg, err := Import(imports, importPath) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + } +} + +func TestIssue5815(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + return + } + + pkg, err := Import(make(map[string]*types.Package), "strings") + if err != nil { + t.Fatal(err) + } + + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Pkg() == nil { + t.Errorf("no pkg for %s", obj) + } + if tname, _ := obj.(*types.TypeName); tname != nil { + named := tname.Type().(*types.Named) + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m.Pkg() == nil { + t.Errorf("no pkg for %s", m) + } + } + } + } +} + +// Smoke test to ensure that imported methods get the correct package. +func TestCorrectMethodPackage(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + return + } + + imports := make(map[string]*types.Package) + _, err := Import(imports, "net/http") + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() + mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex + sel := mset.Lookup(nil, "Lock") + lock := sel.Obj().(*types.Func) + if got, want := lock.Pkg().Path(), "sync"; got != want { + t.Errorf("got package path %q; want %q", got, want) + } +} diff --git a/libgo/go/go/internal/gcimporter/testdata/exports.go b/libgo/go/go/internal/gcimporter/testdata/exports.go new file mode 100644 index 00000000000..8ee28b0942b --- /dev/null +++ b/libgo/go/go/internal/gcimporter/testdata/exports.go @@ -0,0 +1,89 @@ +// 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 is used to generate an object file which +// serves as test file for gcimporter_test.go. + +package exports + +import ( + "go/ast" +) + +// Issue 3682: Correctly read dotted identifiers from export data. +const init1 = 0 + +func init() {} + +const ( + C0 int = 0 + C1 = 3.14159265 + C2 = 2.718281828i + C3 = -123.456e-789 + C4 = +123.456E+789 + C5 = 1234i + C6 = "foo\n" + C7 = `bar\n` +) + +type ( + T1 int + T2 [10]int + T3 []int + T4 *int + T5 chan int + T6a chan<- int + T6b chan (<-chan int) + T6c chan<- (chan int) + T7 <-chan *ast.File + T8 struct{} + T9 struct { + a int + b, c float32 + d []string `go:"tag"` + } + T10 struct { + T8 + T9 + _ *T10 + } + T11 map[int]string + T12 interface{} + T13 interface { + m1() + m2(int) float32 + } + T14 interface { + T12 + T13 + m3(x ...struct{}) []T9 + } + T15 func() + T16 func(int) + T17 func(x int) + T18 func() float32 + T19 func() (x float32) + T20 func(...interface{}) + T21 struct{ next *T21 } + T22 struct{ link *T23 } + T23 struct{ link *T22 } + T24 *T24 + T25 *T26 + T26 *T27 + T27 *T25 + T28 func(T28) T28 +) + +var ( + V0 int + V1 = -991.0 +) + +func F1() {} +func F2(x int) {} +func F3() int { return 0 } +func F4() float32 { return 0 } +func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10) + +func (p *T1) M1() diff --git a/libgo/go/go/parser/error_test.go b/libgo/go/go/parser/error_test.go index 48fb53e5b0d..1a08d5a6b11 100644 --- a/libgo/go/go/parser/error_test.go +++ b/libgo/go/go/parser/error_test.go @@ -34,11 +34,9 @@ import ( const testdata = "testdata" -var fsetErrs = token.NewFileSet() - // getFile assumes that each filename occurs at most once -func getFile(filename string) (file *token.File) { - fsetErrs.Iterate(func(f *token.File) bool { +func getFile(fset *token.FileSet, filename string) (file *token.File) { + fset.Iterate(func(f *token.File) bool { if f.Name() == filename { if file != nil { panic(filename + " used multiple times") @@ -50,8 +48,8 @@ func getFile(filename string) (file *token.File) { return file } -func getPos(filename string, offset int) token.Pos { - if f := getFile(filename); f != nil { +func getPos(fset *token.FileSet, filename string, offset int) token.Pos { + if f := getFile(fset, filename); f != nil { return f.Pos(offset) } return token.NoPos @@ -68,14 +66,14 @@ var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`) // 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, filename string, src []byte) map[token.Pos]string { +func expectedErrors(t *testing.T, fset *token.FileSet, filename string, src []byte) map[token.Pos]string { errors := make(map[token.Pos]string) 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) + s.Init(getFile(fset, filename), src, nil, scanner.ScanComments) var prev token.Pos // position of last non-comment, non-semicolon token var here token.Pos // position immediately after the token at position prev @@ -109,11 +107,11 @@ func expectedErrors(t *testing.T, filename string, src []byte) map[token.Pos]str // compareErrors compares the map of expected error messages with the list // of found errors and reports discrepancies. // -func compareErrors(t *testing.T, expected map[token.Pos]string, found scanner.ErrorList) { +func compareErrors(t *testing.T, fset *token.FileSet, expected map[token.Pos]string, found scanner.ErrorList) { for _, error := range found { // 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) + pos := getPos(fset, 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) @@ -140,7 +138,7 @@ func compareErrors(t *testing.T, expected map[token.Pos]string, found scanner.Er if len(expected) > 0 { t.Errorf("%d errors not reported:", len(expected)) for pos, msg := range expected { - t.Errorf("%s: %s\n", fsetErrs.Position(pos), msg) + t.Errorf("%s: %s\n", fset.Position(pos), msg) } } } @@ -152,7 +150,8 @@ func checkErrors(t *testing.T, filename string, input interface{}) { return } - _, err = ParseFile(fsetErrs, filename, src, DeclarationErrors|AllErrors) + fset := token.NewFileSet() + _, err = ParseFile(fset, filename, src, DeclarationErrors|AllErrors) found, ok := err.(scanner.ErrorList) if err != nil && !ok { t.Error(err) @@ -162,10 +161,10 @@ func checkErrors(t *testing.T, filename string, input interface{}) { // we are expecting the following errors // (collect these after parsing a file so that it is found in the file set) - expected := expectedErrors(t, filename, src) + expected := expectedErrors(t, fset, filename, src) // verify errors returned by the parser - compareErrors(t, expected, found) + compareErrors(t, fset, expected, found) } func TestErrors(t *testing.T) { diff --git a/libgo/go/go/parser/interface.go b/libgo/go/go/parser/interface.go index 49103058b59..c6fd93240af 100644 --- a/libgo/go/go/parser/interface.go +++ b/libgo/go/go/parser/interface.go @@ -91,7 +91,10 @@ func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) var p parser defer func() { if e := recover(); e != nil { - _ = e.(bailout) // re-panics if it's not a bailout + // resume same panic if it's not a bailout + if _, ok := e.(bailout); !ok { + panic(e) + } } // set result values @@ -164,14 +167,31 @@ func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, m return } -// ParseExpr is a convenience function for obtaining the AST of an expression x. -// The position information recorded in the AST is undefined. The filename used -// in error messages is the empty string. +// ParseExprFrom is a convenience function for parsing an expression. +// The arguments have the same meaning as for Parse, but the source must +// be a valid Go (type or value) expression. // -func ParseExpr(x string) (ast.Expr, error) { +func ParseExprFrom(fset *token.FileSet, filename string, src interface{}, mode Mode) (ast.Expr, error) { + // get source + text, err := readSource(filename, src) + if err != nil { + return nil, err + } + var p parser - p.init(token.NewFileSet(), "", []byte(x), 0) + defer func() { + if e := recover(); e != nil { + // resume same panic if it's not a bailout + if _, ok := e.(bailout); !ok { + panic(e) + } + } + p.errors.Sort() + err = p.errors.Err() + }() + // parse expr + p.init(fset, filename, text, mode) // Set up pkg-level scopes to avoid nil-pointer errors. // This is not needed for a correct expression x as the // parser will be ok with a nil topScope, but be cautious @@ -196,3 +216,11 @@ func ParseExpr(x string) (ast.Expr, error) { return e, nil } + +// ParseExpr is a convenience function for obtaining the AST of an expression x. +// The position information recorded in the AST is undefined. The filename used +// in error messages is the empty string. +// +func ParseExpr(x string) (ast.Expr, error) { + return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0) +} diff --git a/libgo/go/go/parser/parser.go b/libgo/go/go/parser/parser.go index 4a005d8ffa3..e82c0bd1223 100644 --- a/libgo/go/go/parser/parser.go +++ b/libgo/go/go/parser/parser.go @@ -7,6 +7,13 @@ // output is an abstract syntax tree (AST) representing the Go source. The // parser is invoked through one of the Parse* functions. // +// The parser accepts a larger language than is syntactically permitted by +// the Go spec, for simplicity, and for improved robustness in the presence +// of syntax errors. For instance, in method declarations, the receiver is +// treated like an ordinary parameter list and thus may contain multiple +// entries where the spec permits exactly one. Consequently, the corresponding +// field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry. +// package parser import ( @@ -412,14 +419,17 @@ func (p *parser) expectSemi() { } } -func (p *parser) atComma(context string) bool { +func (p *parser) atComma(context string, follow token.Token) bool { if p.tok == token.COMMA { return true } - if p.tok == token.SEMICOLON && p.lit == "\n" { - p.error(p.pos, "missing ',' before newline in "+context) - return true // "insert" the comma and continue - + if p.tok != follow { + msg := "missing ','" + if p.tok == token.SEMICOLON && p.lit == "\n" { + msg += " before newline" + } + p.error(p.pos, msg+" in "+context) + return true // "insert" comma and continue } return false } @@ -825,7 +835,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ // parameter or result variable is the function body. p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) - if !p.atComma("parameter list") { + if !p.atComma("parameter list", token.RPAREN) { return } p.next() @@ -838,7 +848,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ // parameter or result variable is the function body. p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) - if !p.atComma("parameter list") { + if !p.atComma("parameter list", token.RPAREN) { break } p.next() @@ -1248,7 +1258,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { ellipsis = p.pos p.next() } - if !p.atComma("argument list") { + if !p.atComma("argument list", token.RPAREN) { break } p.next() @@ -1259,7 +1269,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { return &ast.CallExpr{Fun: fun, Lparen: lparen, Args: list, Ellipsis: ellipsis, Rparen: rparen} } -func (p *parser) parseElement(keyOk bool) ast.Expr { +func (p *parser) parseValue(keyOk bool) ast.Expr { if p.trace { defer un(trace(p, "Element")) } @@ -1287,16 +1297,30 @@ func (p *parser) parseElement(keyOk bool) ast.Expr { x := p.checkExpr(p.parseExpr(keyOk)) if keyOk { if p.tok == token.COLON { - colon := p.pos - p.next() // Try to resolve the key but don't collect it // as unresolved identifier if it fails so that // we don't get (possibly false) errors about // undeclared names. p.tryResolve(x, false) - return &ast.KeyValueExpr{Key: x, Colon: colon, Value: p.parseElement(false)} + } else { + // not a key + p.resolve(x) } - p.resolve(x) // not a key + } + + return x +} + +func (p *parser) parseElement() ast.Expr { + if p.trace { + defer un(trace(p, "Element")) + } + + x := p.parseValue(true) + if p.tok == token.COLON { + colon := p.pos + p.next() + x = &ast.KeyValueExpr{Key: x, Colon: colon, Value: p.parseValue(false)} } return x @@ -1308,8 +1332,8 @@ func (p *parser) parseElementList() (list []ast.Expr) { } for p.tok != token.RBRACE && p.tok != token.EOF { - list = append(list, p.parseElement(true)) - if !p.atComma("composite literal") { + list = append(list, p.parseElement()) + if !p.atComma("composite literal", token.RBRACE) { break } p.next() @@ -1365,7 +1389,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr { return x } -// isTypeName returns true iff x is a (qualified) TypeName. +// isTypeName reports whether x is a (qualified) TypeName. func isTypeName(x ast.Expr) bool { switch t := x.(type) { case *ast.BadExpr: @@ -1379,7 +1403,7 @@ func isTypeName(x ast.Expr) bool { return true } -// isLiteralType returns true iff x is a legal composite literal type. +// isLiteralType reports whether x is a legal composite literal type. func isLiteralType(x ast.Expr) bool { switch t := x.(type) { case *ast.BadExpr: @@ -1455,7 +1479,8 @@ L: pos := p.pos p.errorExpected(pos, "selector or type assertion") p.next() // make progress - x = &ast.BadExpr{From: pos, To: p.pos} + sel := &ast.Ident{NamePos: pos, Name: "_"} + x = &ast.SelectorExpr{X: x, Sel: sel} } case token.LBRACK: if lhs { @@ -2123,7 +2148,7 @@ func (p *parser) parseStmt() (s ast.Stmt) { case // tokens that may start an expression token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operands - token.LBRACK, token.STRUCT, // composite types + token.LBRACK, token.STRUCT, token.MAP, token.CHAN, token.INTERFACE, // composite types token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT: // unary operators s, _ = p.parseSimpleStmt(labelOk) // because of the required look-ahead, labeled statements are @@ -2152,11 +2177,14 @@ func (p *parser) parseStmt() (s ast.Stmt) { case token.FOR: s = p.parseForStmt() case token.SEMICOLON: - s = &ast.EmptyStmt{Semicolon: p.pos} + // Is it ever possible to have an implicit semicolon + // producing an empty statement in a valid program? + // (handle correctly anyway) + s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.lit == "\n"} p.next() case token.RBRACE: // a semicolon may be omitted before a closing "}" - s = &ast.EmptyStmt{Semicolon: p.pos} + s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} default: // no statement found pos := p.pos @@ -2228,6 +2256,7 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota defer un(trace(p, keyword.String()+"Spec")) } + pos := p.pos idents := p.parseIdentList() typ := p.tryType() var values []ast.Expr @@ -2238,6 +2267,17 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota } p.expectSemi() // call before accessing p.linecomment + switch keyword { + case token.VAR: + if typ == nil && values == nil { + p.error(pos, "missing variable type or initialization") + } + case token.CONST: + if values == nil && (iota == 0 || typ != nil) { + p.error(pos, "missing constant value") + } + } + // Go spec: The scope of a constant or variable identifier declared inside // a function begins at the end of the ConstSpec or VarSpec and ends at // the end of the innermost containing block. diff --git a/libgo/go/go/parser/parser_test.go b/libgo/go/go/parser/parser_test.go index 85065fd1829..c7bb36d789d 100644 --- a/libgo/go/go/parser/parser_test.go +++ b/libgo/go/go/parser/parser_test.go @@ -14,8 +14,6 @@ import ( "testing" ) -var fset = token.NewFileSet() - var validFiles = []string{ "parser.go", "parser_test.go", @@ -25,7 +23,7 @@ var validFiles = []string{ func TestParse(t *testing.T) { for _, filename := range validFiles { - _, err := ParseFile(fset, filename, nil, DeclarationErrors) + _, err := ParseFile(token.NewFileSet(), filename, nil, DeclarationErrors) if err != nil { t.Fatalf("ParseFile(%s): %v", filename, err) } @@ -46,7 +44,7 @@ func dirFilter(f os.FileInfo) bool { return nameFilter(f.Name()) } func TestParseDir(t *testing.T) { path := "." - pkgs, err := ParseDir(fset, path, dirFilter, 0) + pkgs, err := ParseDir(token.NewFileSet(), path, dirFilter, 0) if err != nil { t.Fatalf("ParseDir(%s): %v", path, err) } @@ -131,7 +129,7 @@ func TestParseExpr(t *testing.T) { } func TestColonEqualsScope(t *testing.T) { - f, err := ParseFile(fset, "", `package p; func f() { x, y, z := x, y, z }`, 0) + f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { x, y, z := x, y, z }`, 0) if err != nil { t.Fatal(err) } @@ -153,7 +151,7 @@ func TestColonEqualsScope(t *testing.T) { } func TestVarScope(t *testing.T) { - f, err := ParseFile(fset, "", `package p; func f() { var x, y, z = x, y, z }`, 0) + f, err := ParseFile(token.NewFileSet(), "", `package p; func f() { var x, y, z = x, y, z }`, 0) if err != nil { t.Fatal(err) } @@ -183,7 +181,7 @@ var x int func f() { L: } ` - f, err := ParseFile(fset, "", src, 0) + f, err := ParseFile(token.NewFileSet(), "", src, 0) if err != nil { t.Fatal(err) } @@ -221,7 +219,7 @@ func f() { L: } } func TestUnresolved(t *testing.T) { - f, err := ParseFile(fset, "", ` + f, err := ParseFile(token.NewFileSet(), "", ` package p // func f1a(int) @@ -316,7 +314,7 @@ var imports = map[string]bool{ func TestImports(t *testing.T) { for path, isValid := range imports { src := fmt.Sprintf("package p; import %s", path) - _, err := ParseFile(fset, "", src, 0) + _, err := ParseFile(token.NewFileSet(), "", src, 0) switch { case err != nil && isValid: t.Errorf("ParseFile(%s): got %v; expected no error", src, err) @@ -327,7 +325,7 @@ func TestImports(t *testing.T) { } func TestCommentGroups(t *testing.T) { - f, err := ParseFile(fset, "", ` + f, err := ParseFile(token.NewFileSet(), "", ` package p /* 1a */ /* 1b */ /* 1c */ // 1d /* 2a */ @@ -421,7 +419,7 @@ func checkFieldComments(t *testing.T, file *ast.File, fieldname, lead, line stri } func TestLeadAndLineComments(t *testing.T) { - f, err := ParseFile(fset, "", ` + f, err := ParseFile(token.NewFileSet(), "", ` package p type T struct { /* F1 lead comment */ @@ -447,3 +445,89 @@ type T struct { t.Error("not expected to find T.f3") } } + +// TestIssue9979 verifies that empty statements are contained within their enclosing blocks. +func TestIssue9979(t *testing.T) { + for _, src := range []string{ + "package p; func f() {;}", + "package p; func f() {L:}", + "package p; func f() {L:;}", + "package p; func f() {L:\n}", + "package p; func f() {L:\n;}", + "package p; func f() { ; }", + "package p; func f() { L: }", + "package p; func f() { L: ; }", + "package p; func f() { L: \n}", + "package p; func f() { L: \n; }", + } { + fset := token.NewFileSet() + f, err := ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var pos, end token.Pos + ast.Inspect(f, func(x ast.Node) bool { + switch s := x.(type) { + case *ast.BlockStmt: + pos, end = s.Pos()+1, s.End()-1 // exclude "{", "}" + case *ast.LabeledStmt: + pos, end = s.Pos()+2, s.End() // exclude "L:" + case *ast.EmptyStmt: + // check containment + if s.Pos() < pos || s.End() > end { + t.Errorf("%s: %T[%d, %d] not inside [%d, %d]", src, s, s.Pos(), s.End(), pos, end) + } + // check semicolon + offs := fset.Position(s.Pos()).Offset + if ch := src[offs]; ch != ';' != s.Implicit { + want := "want ';'" + if s.Implicit { + want = "but ';' is implicit" + } + t.Errorf("%s: found %q at offset %d; %s", src, ch, offs, want) + } + } + return true + }) + } +} + +// TestIncompleteSelection ensures that an incomplete selector +// expression is parsed as a (blank) *ast.SelectorExpr, not a +// *ast.BadExpr. +func TestIncompleteSelection(t *testing.T) { + for _, src := range []string{ + "package p; var _ = fmt.", // at EOF + "package p; var _ = fmt.\ntype X int", // not at EOF + } { + fset := token.NewFileSet() + f, err := ParseFile(fset, "", src, 0) + if err == nil { + t.Errorf("ParseFile(%s) succeeded unexpectedly", src) + continue + } + + const wantErr = "expected selector or type assertion" + if !strings.Contains(err.Error(), wantErr) { + t.Errorf("ParseFile returned wrong error %q, want %q", err, wantErr) + } + + var sel *ast.SelectorExpr + ast.Inspect(f, func(n ast.Node) bool { + if n, ok := n.(*ast.SelectorExpr); ok { + sel = n + } + return true + }) + if sel == nil { + t.Error("found no *ast.SelectorExpr") + continue + } + const wantSel = "&{fmt _}" + if fmt.Sprint(sel) != wantSel { + t.Errorf("found selector %s, want %s", sel, wantSel) + continue + } + } +} diff --git a/libgo/go/go/parser/short_test.go b/libgo/go/go/parser/short_test.go index 05e44de28a7..ef2ffadbd98 100644 --- a/libgo/go/go/parser/short_test.go +++ b/libgo/go/go/parser/short_test.go @@ -40,6 +40,12 @@ var valids = []string{ `package p; func (*(T),) m() {}`, `package p; func _(x []int) { for range x {} }`, `package p; func _() { if [T{}.n]int{} {} }`, + `package p; func _() { map[int]int{}[0]++; map[int]int{}[0] += 1 }`, + `package p; func _(x interface{f()}) { interface{f()}(x).f() }`, + `package p; func _(x chan int) { chan int(x) <- 0 }`, + `package p; const (x = 0; y; z)`, // issue 9639 + `package p; var _ = map[P]int{P{}:0, {}:1}`, + `package p; var _ = map[*P]int{&P{}:0, {}:1}`, } func TestValid(t *testing.T) { @@ -93,8 +99,13 @@ var invalids = []string{ `package p; func f() { for i /* ERROR "boolean or range expression" */ , x := []string {} }`, `package p; func f() { go f /* ERROR HERE "function must be invoked" */ }`, `package p; func f() { defer func() {} /* ERROR HERE "function must be invoked" */ }`, - `package p; func f() { go func() { func() { f(x func /* ERROR "expected '\)'" */ (){}) } } }`, - `package p; func f() (a b string /* ERROR "expected '\)'" */ , ok bool) // issue 8656`, + `package p; func f() { go func() { func() { f(x func /* ERROR "missing ','" */ (){}) } } }`, + `package p; func f(x func(), u v func /* ERROR "missing ','" */ ()){}`, + `package p; func f() (a b string /* ERROR "missing ','" */ , ok bool)`, // issue 8656 + `package p; var x /* ERROR "missing variable type or initialization" */ , y, z;`, // issue 9639 + `package p; const x /* ERROR "missing constant value" */ ;`, // issue 9639 + `package p; const x /* ERROR "missing constant value" */ int;`, // issue 9639 + `package p; const (x = 0; y; z /* ERROR "missing constant value" */ int);`, // issue 9639 } func TestInvalid(t *testing.T) { diff --git a/libgo/go/go/parser/testdata/issue3106.src b/libgo/go/go/parser/testdata/issue3106.src index 82796c8ceb6..2db10be2354 100644 --- a/libgo/go/go/parser/testdata/issue3106.src +++ b/libgo/go/go/parser/testdata/issue3106.src @@ -19,7 +19,7 @@ func f() { time.Sleep(1e8) m.Lock() defer - if /* ERROR "expected operand, found 'if'" */ percent == 100 { + if /* ERROR "expected ';', found 'if'" */ percent == 100 { m.Unlock() break } diff --git a/libgo/go/go/printer/nodes.go b/libgo/go/go/printer/nodes.go index d5a69349beb..fe047053afb 100644 --- a/libgo/go/go/printer/nodes.go +++ b/libgo/go/go/printer/nodes.go @@ -12,6 +12,9 @@ import ( "bytes" "go/ast" "go/token" + "strconv" + "strings" + "unicode" "unicode/utf8" ) @@ -1334,6 +1337,49 @@ func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) { } } +func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit { + // Note: An unmodified AST generated by go/parser will already + // contain a backward- or double-quoted path string that does + // not contain any invalid characters, and most of the work + // here is not needed. However, a modified or generated AST + // may possibly contain non-canonical paths. Do the work in + // all cases since it's not too hard and not speed-critical. + + // if we don't have a proper string, be conservative and return whatever we have + if lit.Kind != token.STRING { + return lit + } + s, err := strconv.Unquote(lit.Value) + if err != nil { + return lit + } + + // if the string is an invalid path, return whatever we have + // + // spec: "Implementation restriction: A compiler may restrict + // ImportPaths to non-empty strings using only characters belonging + // to Unicode's L, M, N, P, and S general categories (the Graphic + // characters without spaces) and may also exclude the characters + // !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character + // U+FFFD." + if s == "" { + return lit + } + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return lit + } + } + + // otherwise, return the double-quoted path + s = strconv.Quote(s) + if s == lit.Value { + return lit // nothing wrong with lit + } + return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: token.STRING, Value: s} +} + // The parameter n is the number of specs in the group. If doIndent is set, // multi-line identifier lists in the spec are indented when the first // linebreak is encountered. @@ -1346,7 +1392,7 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { p.expr(s.Name) p.print(blank) } - p.expr(s.Path) + p.expr(sanitizeImportPath(s.Path)) p.setComment(s.Comment) p.print(s.EndPos) diff --git a/libgo/go/go/printer/printer.go b/libgo/go/go/printer/printer.go index 280c697a0dd..f9343d3af0d 100644 --- a/libgo/go/go/printer/printer.go +++ b/libgo/go/go/printer/printer.go @@ -144,7 +144,7 @@ func (p *printer) nextComment() { p.commentOffset = infinity } -// commentBefore returns true iff the current comment group occurs +// commentBefore reports whether the current comment group occurs // before the next position in the source code and printing it does // not introduce implicit semicolons. // @@ -496,29 +496,33 @@ func stripCommonPrefix(lines []string) { // Compute maximum common white prefix of all but the first, // last, and blank lines, and replace blank lines with empty // lines (the first line starts with /* and has no prefix). - // In case of two-line comments, consider the last line for - // the prefix computation since otherwise the prefix would - // be empty. + // In cases where only the first and last lines are not blank, + // such as two-line comments, or comments where all inner lines + // are blank, consider the last line for the prefix computation + // since otherwise the prefix would be empty. // // Note that the first and last line are never empty (they // contain the opening /* and closing */ respectively) and // thus they can be ignored by the blank line check. - var prefix string + prefix := "" + prefixSet := false if len(lines) > 2 { - first := true for i, line := range lines[1 : len(lines)-1] { - switch { - case isBlank(line): + if isBlank(line) { lines[1+i] = "" // range starts with lines[1] - case first: - prefix = commonPrefix(line, line) - first = false - default: + } else { + if !prefixSet { + prefix = line + prefixSet = true + } prefix = commonPrefix(prefix, line) } + } - } else { // len(lines) == 2, lines cannot be blank (contain /* and */) - line := lines[1] + } + // If we don't have a prefix yet, consider the last line. + if !prefixSet { + line := lines[len(lines)-1] prefix = commonPrefix(line, line) } diff --git a/libgo/go/go/printer/testdata/comments.golden b/libgo/go/go/printer/testdata/comments.golden index b1af7958a96..849fa624489 100644 --- a/libgo/go/go/printer/testdata/comments.golden +++ b/libgo/go/go/printer/testdata/comments.golden @@ -413,6 +413,68 @@ func _() { aligned line */ } +// Issue 9751. +func _() { + /*a string + + b string*/ + + /*A string + + + + Z string*/ + + /*a string + + b string + + c string*/ + + { + /*a string + b string*/ + + /*a string + + b string*/ + + /*a string + + b string + + c string*/ + } + + { + /*a string + b string*/ + + /*a string + + b string*/ + + /*a string + + b string + + c string*/ + } + + /* + */ + + /* + + */ + + /* + + * line + + */ +} + /* * line * of diff --git a/libgo/go/go/printer/testdata/comments.input b/libgo/go/go/printer/testdata/comments.input index 983e2b2c97e..30cd23c6dd4 100644 --- a/libgo/go/go/printer/testdata/comments.input +++ b/libgo/go/go/printer/testdata/comments.input @@ -418,6 +418,68 @@ func _() { aligned line */ } +// Issue 9751. +func _() { + /*a string + + b string*/ + + /*A string + + + + Z string*/ + + /*a string + + b string + + c string*/ + + { + /*a string +b string*/ + + /*a string + +b string*/ + + /*a string + +b string + +c string*/ + } + + { + /*a string + b string*/ + + /*a string + + b string*/ + + /*a string + + b string + + c string*/ + } + + /* + */ + + /* + + */ + + /* + + * line + + */ +} + /* * line * of diff --git a/libgo/go/go/printer/testdata/declarations.golden b/libgo/go/go/printer/testdata/declarations.golden index 9acd41b7d28..82f5e0f9147 100644 --- a/libgo/go/go/printer/testdata/declarations.golden +++ b/libgo/go/go/printer/testdata/declarations.golden @@ -110,6 +110,15 @@ import ( "package_dddd" // comment ) +// print import paths as double-quoted strings +// (we would like more test cases but the go/parser +// already excludes most incorrect paths, and we don't +// bother setting up test-ASTs manually) +import ( + "fmt" + "math" +) + // at least one empty line between declarations of different kind import _ "io" diff --git a/libgo/go/go/printer/testdata/declarations.input b/libgo/go/go/printer/testdata/declarations.input index 45beec25fc7..a0a3783b846 100644 --- a/libgo/go/go/printer/testdata/declarations.input +++ b/libgo/go/go/printer/testdata/declarations.input @@ -111,6 +111,15 @@ import ( "package_dddd" // comment ) +// print import paths as double-quoted strings +// (we would like more test cases but the go/parser +// already excludes most incorrect paths, and we don't +// bother setting up test-ASTs manually) +import ( + `fmt` + "math" +) + // at least one empty line between declarations of different kind import _ "io" var _ int diff --git a/libgo/go/go/printer/testdata/parser.go b/libgo/go/go/printer/testdata/parser.go index dba8bbd4351..44dfa19ff35 100644 --- a/libgo/go/go/printer/testdata/parser.go +++ b/libgo/go/go/printer/testdata/parser.go @@ -1165,7 +1165,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr { return x } -// isTypeName returns true iff x is a (qualified) TypeName. +// isTypeName reports whether x is a (qualified) TypeName. func isTypeName(x ast.Expr) bool { switch t := x.(type) { case *ast.BadExpr: @@ -1179,7 +1179,7 @@ func isTypeName(x ast.Expr) bool { return true } -// isLiteralType returns true iff x is a legal composite literal type. +// isLiteralType reports whether x is a legal composite literal type. func isLiteralType(x ast.Expr) bool { switch t := x.(type) { case *ast.BadExpr: diff --git a/libgo/go/go/scanner/errors.go b/libgo/go/go/scanner/errors.go index 22de69c3c1c..bf7bfa30e4b 100644 --- a/libgo/go/go/scanner/errors.go +++ b/libgo/go/go/scanner/errors.go @@ -54,18 +54,16 @@ func (p ErrorList) Less(i, j int) bool { // Note that it is not sufficient to simply compare file offsets because // the offsets do not reflect modified line information (through //line // comments). - if e.Filename < f.Filename { - return true + if e.Filename != f.Filename { + return e.Filename < f.Filename } - if e.Filename == f.Filename { - if e.Line < f.Line { - return true - } - if e.Line == f.Line { - return e.Column < f.Column - } + if e.Line != f.Line { + return e.Line < f.Line + } + if e.Column != f.Column { + return e.Column < f.Column } - return false + return p[i].Msg < p[j].Msg } // Sort sorts an ErrorList. *Error entries are sorted by position, diff --git a/libgo/go/go/scanner/scanner.go b/libgo/go/go/scanner/scanner.go index cec82ea10ef..e9476c4dee4 100644 --- a/libgo/go/go/scanner/scanner.go +++ b/libgo/go/go/scanner/scanner.go @@ -706,13 +706,14 @@ scanAgain: s.insertSemi = false // newline consumed return pos, token.SEMICOLON, "\n" } - lit = s.scanComment() + comment := s.scanComment() if s.mode&ScanComments == 0 { // skip comment s.insertSemi = false // newline consumed goto scanAgain } tok = token.COMMENT + lit = comment } else { tok = s.switch2(token.QUO, token.QUO_ASSIGN) } diff --git a/libgo/go/go/scanner/scanner_test.go b/libgo/go/go/scanner/scanner_test.go index fc450d8a6eb..0d21905166c 100644 --- a/libgo/go/go/scanner/scanner_test.go +++ b/libgo/go/go/scanner/scanner_test.go @@ -734,6 +734,41 @@ func TestScanErrors(t *testing.T) { } } +// Verify that no comments show up as literal values when skipping comments. +func TestIssue10213(t *testing.T) { + var src = ` + var ( + A = 1 // foo + ) + + var ( + B = 2 + // foo + ) + + var C = 3 // foo + + var D = 4 + // foo + + func anycode() { + // foo + } + ` + var s Scanner + s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), nil, 0) + for { + pos, tok, lit := s.Scan() + class := tokenclass(tok) + if lit != "" && class != keyword && class != literal && tok != token.SEMICOLON { + t.Errorf("%s: tok = %s, lit = %q", fset.Position(pos), tok, lit) + } + if tok <= token.EOF { + break + } + } +} + func BenchmarkScan(b *testing.B) { b.StopTimer() fset := token.NewFileSet() diff --git a/libgo/go/go/token/position.go b/libgo/go/go/token/position.go index 82d90eeb728..33751779a3d 100644 --- a/libgo/go/go/token/position.go +++ b/libgo/go/go/token/position.go @@ -21,10 +21,10 @@ type Position struct { Filename string // filename, if any Offset int // offset, starting at 0 Line int // line number, starting at 1 - Column int // column number, starting at 1 (character count) + Column int // column number, starting at 1 (byte count) } -// IsValid returns true if the position is valid. +// IsValid reports whether the position is valid. func (pos *Position) IsValid() bool { return pos.Line > 0 } // String returns a string in one of several forms: @@ -56,8 +56,8 @@ func (pos Position) String() string { // where base and size are specified when adding the file to the file set via // AddFile. // -// To create the Pos value for a specific source offset, first add -// the respective file to the current file set (via FileSet.AddFile) +// To create the Pos value for a specific source offset (measured in bytes), +// first add the respective file to the current file set using FileSet.AddFile // and then call File.Pos(offset) for that file. Given a Pos value p // for a specific file set fset, the corresponding Position value is // obtained by calling fset.Position(p). @@ -77,7 +77,7 @@ type Pos int // const NoPos Pos = 0 -// IsValid returns true if the position is valid. +// IsValid reports whether the position is valid. func (p Pos) IsValid() bool { return p != NoPos } @@ -157,7 +157,7 @@ func (f *File) MergeLine(line int) { f.lines = f.lines[:len(f.lines)-1] } -// SetLines sets the line offsets for a file and returns true if successful. +// SetLines sets the line offsets for a file and reports whether it succeeded. // The line offsets are the offsets of the first character of each line; // for instance for the content "ab\nc\n" the line offsets are {0, 3}. // An empty file has an empty line offset table. diff --git a/libgo/go/go/types/api.go b/libgo/go/go/types/api.go new file mode 100644 index 00000000000..b3bf6f01476 --- /dev/null +++ b/libgo/go/go/types/api.go @@ -0,0 +1,336 @@ +// 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. + +// Package types declares the data types and implements +// the algorithms for type-checking of Go packages. Use +// Config.Check to invoke the type checker for a package. +// Alternatively, create a new type checked with NewChecker +// and invoke it incrementally by calling Checker.Files. +// +// Type-checking consists of several interdependent phases: +// +// Name resolution maps each identifier (ast.Ident) in the program to the +// language object (Object) it denotes. +// Use Info.{Defs,Uses,Implicits} for the results of name resolution. +// +// Constant folding computes the exact constant value (constant.Value) +// for every expression (ast.Expr) that is a compile-time constant. +// Use Info.Types[expr].Value for the results of constant folding. +// +// Type inference computes the type (Type) of every expression (ast.Expr) +// and checks for compliance with the language specification. +// Use Info.Types[expr].Type for the results of type inference. +// +package types // import "go/types" + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/token" +) + +// An Error describes a type-checking error; it implements the error interface. +// A "soft" error is an error that still permits a valid interpretation of a +// package (such as "unused variable"); "hard" errors may lead to unpredictable +// behavior if ignored. +type Error struct { + Fset *token.FileSet // file set for interpretation of Pos + Pos token.Pos // error position + Msg string // error message + Soft bool // if set, error is "soft" +} + +// Error returns an error string formatted as follows: +// filename:line:column: message +func (err Error) Error() string { + return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg) +} + +// An importer resolves import paths to Packages. +// See go/importer for existing implementations. +type Importer interface { + // Import returns the imported package for the given import + // path, or an error if the package couldn't be imported. + // Import is responsible for returning the same package for + // matching import paths. + Import(path string) (*Package, error) +} + +// A Config specifies the configuration for type checking. +// The zero value for Config is a ready-to-use default configuration. +type Config struct { + // If IgnoreFuncBodies is set, function bodies are not + // type-checked. + IgnoreFuncBodies bool + + // If FakeImportC is set, `import "C"` (for packages requiring Cgo) + // declares an empty "C" package and errors are omitted for qualified + // identifiers referring to package C (which won't find an object). + // This feature is intended for the standard library cmd/api tool. + // + // Caution: Effects may be unpredictable due to follow-up errors. + // Do not use casually! + FakeImportC bool + + // If Error != nil, it is called with each error found + // during type checking; err has dynamic type Error. + // Secondary errors (for instance, to enumerate all types + // involved in an invalid recursive type declaration) have + // error strings that start with a '\t' character. + // If Error == nil, type-checking stops with the first + // error found. + Error func(err error) + + // Importer is called for each import declaration except when + // importing package "unsafe". An error is reported if an + // importer is needed but none was installed. + Importer Importer + + // If Sizes != nil, it provides the sizing functions for package unsafe. + // Otherwise &StdSizes{WordSize: 8, MaxAlign: 8} is used instead. + Sizes Sizes + + // If DisableUnusedImportCheck is set, packages are not checked + // for unused imports. + DisableUnusedImportCheck bool +} + +// Info holds result type information for a type-checked package. +// Only the information for which a map is provided is collected. +// If the package has type errors, the collected information may +// be incomplete. +type Info struct { + // Types maps expressions to their types, and for constant + // expressions, their values. Invalid expressions are omitted. + // + // For (possibly parenthesized) identifiers denoting built-in + // functions, the recorded signatures are call-site specific: + // if the call result is not a constant, the recorded type is + // an argument-specific signature. Otherwise, the recorded type + // is invalid. + // + // Identifiers on the lhs of declarations (i.e., the identifiers + // which are being declared) are collected in the Defs map. + // Identifiers denoting packages are collected in the Uses maps. + Types map[ast.Expr]TypeAndValue + + // 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, or symbolic variables t in t := x.(type) of + // type switch headers), the corresponding objects are nil. + // + // 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: + // + // node declared object + // + // *ast.ImportSpec *PkgName for dot-imports and imports without renames + // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) + // *ast.Field anonymous struct field or parameter *Var + // + Implicits map[ast.Node]Object + + // Selections maps selector expressions (excluding qualified identifiers) + // to their corresponding selections. + Selections map[*ast.SelectorExpr]*Selection + + // Scopes maps ast.Nodes to the scopes they define. Package scopes are not + // associated with a specific node but with all files belonging to a package. + // Thus, the package scope can be found in the type-checked Package object. + // Scopes nest, with the Universe scope being the outermost scope, enclosing + // the package scope, which contains (one or more) files scopes, which enclose + // function scopes which in turn enclose statement and function literal scopes. + // Note that even though package-level functions are declared in the package + // scope, the function scopes are embedded in the file scope of the file + // containing the function declaration. + // + // The following node types may appear in Scopes: + // + // *ast.File + // *ast.FuncType + // *ast.BlockStmt + // *ast.IfStmt + // *ast.SwitchStmt + // *ast.TypeSwitchStmt + // *ast.CaseClause + // *ast.CommClause + // *ast.ForStmt + // *ast.RangeStmt + // + Scopes map[ast.Node]*Scope + + // InitOrder is the list of package-level initializers in the order in which + // they must be executed. Initializers referring to variables related by an + // initialization dependency appear in topological order, the others appear + // in source order. Variables without an initialization expression do not + // appear in this list. + InitOrder []*Initializer +} + +// TypeOf returns the type of expression e, or nil if not found. +// Precondition: the Types, Uses and Defs maps are populated. +// +func (info *Info) TypeOf(e ast.Expr) Type { + if t, ok := info.Types[e]; ok { + return t.Type + } + if id, _ := e.(*ast.Ident); id != nil { + if obj := info.ObjectOf(id); obj != nil { + return obj.Type() + } + } + return nil +} + +// ObjectOf returns the object denoted by the specified id, +// or nil if not found. +// +// If id is an anonymous struct field, ObjectOf returns the field (*Var) +// it uses, not the type (*TypeName) it defines. +// +// Precondition: the Uses and Defs maps are populated. +// +func (info *Info) ObjectOf(id *ast.Ident) Object { + if obj, _ := info.Defs[id]; obj != nil { + return obj + } + return info.Uses[id] +} + +// TypeAndValue reports the type and value (for constants) +// of the corresponding expression. +type TypeAndValue struct { + mode operandMode + Type Type + Value constant.Value +} + +// TODO(gri) Consider eliminating the IsVoid predicate. Instead, report +// "void" values as regular values but with the empty tuple type. + +// IsVoid reports whether the corresponding expression +// is a function call without results. +func (tv TypeAndValue) IsVoid() bool { + return tv.mode == novalue +} + +// IsType reports whether the corresponding expression specifies a type. +func (tv TypeAndValue) IsType() bool { + return tv.mode == typexpr +} + +// IsBuiltin reports whether the corresponding expression denotes +// a (possibly parenthesized) built-in function. +func (tv TypeAndValue) IsBuiltin() bool { + return tv.mode == builtin +} + +// IsValue reports whether the corresponding expression is a value. +// Builtins are not considered values. Constant values have a non- +// nil Value. +func (tv TypeAndValue) IsValue() bool { + switch tv.mode { + case constant_, variable, mapindex, value, commaok: + return true + } + return false +} + +// IsNil reports whether the corresponding expression denotes the +// predeclared value nil. +func (tv TypeAndValue) IsNil() bool { + return tv.mode == value && tv.Type == Typ[UntypedNil] +} + +// Addressable reports whether the corresponding expression +// is addressable (https://golang.org/ref/spec#Address_operators). +func (tv TypeAndValue) Addressable() bool { + return tv.mode == variable +} + +// Assignable reports whether the corresponding expression +// is assignable to (provided a value of the right type). +func (tv TypeAndValue) Assignable() bool { + return tv.mode == variable || tv.mode == mapindex +} + +// HasOk reports whether the corresponding expression may be +// used on the lhs of a comma-ok assignment. +func (tv TypeAndValue) HasOk() bool { + return tv.mode == commaok || tv.mode == mapindex +} + +// An Initializer describes a package-level variable, or a list of variables in case +// of a multi-valued initialization expression, and the corresponding initialization +// expression. +type Initializer struct { + Lhs []*Var // var Lhs = Rhs + Rhs ast.Expr +} + +func (init *Initializer) String() string { + var buf bytes.Buffer + for i, lhs := range init.Lhs { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(lhs.Name()) + } + buf.WriteString(" = ") + WriteExpr(&buf, init.Rhs) + return buf.String() +} + +// Check type-checks a package and returns the resulting package object, +// the first error if any, and if info != nil, additional type information. +// The package is marked as complete if no errors occurred, otherwise it is +// incomplete. See Config.Error for controlling behavior in the presence of +// errors. +// +// The package is specified by a list of *ast.Files and corresponding +// file set, and the package path the package is identified with. +// The clean path must not be empty or dot ("."). +func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) { + pkg := NewPackage(path, "") + return pkg, NewChecker(conf, fset, pkg, info).Files(files) +} + +// AssertableTo reports whether a value of type V can be asserted to have type T. +func AssertableTo(V *Interface, T Type) bool { + m, _ := assertableTo(V, T) + return m == nil +} + +// AssignableTo reports whether a value of type V is assignable to a variable of type T. +func AssignableTo(V, T Type) bool { + x := operand{mode: value, typ: V} + return x.assignableTo(nil, T) // config not needed for non-constant x +} + +// ConvertibleTo reports whether a value of type V is convertible to a value of type T. +func ConvertibleTo(V, T Type) bool { + x := operand{mode: value, typ: V} + return x.convertibleTo(nil, T) // config not needed for non-constant x +} + +// Implements reports whether type V implements interface T. +func Implements(V Type, T *Interface) bool { + f, _ := MissingMethod(V, T, true) + return f == nil +} diff --git a/libgo/go/go/types/api_test.go b/libgo/go/go/types/api_test.go new file mode 100644 index 00000000000..eeda0d847c3 --- /dev/null +++ b/libgo/go/go/types/api_test.go @@ -0,0 +1,1044 @@ +// Copyright 2013 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 ( + "bytes" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "reflect" + "regexp" + "strings" + "testing" + + . "go/types" +) + +func pkgFor(path, source string, info *Info) (*Package, error) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, source, 0) + if err != nil { + return nil, err + } + + conf := Config{Importer: importer.Default()} + return conf.Check(f.Name.Name, fset, []*ast.File{f}, info) +} + +func mustTypecheck(t *testing.T, path, source string, info *Info) string { + pkg, err := pkgFor(path, source, info) + if err != nil { + name := path + if pkg != nil { + name = "package " + pkg.Name() + } + t.Fatalf("%s: didn't type-check (%s)", name, err) + } + return pkg.Name() +} + +func TestValuesInfo(t *testing.T) { + var tests = []struct { + src string + expr string // constant expression + typ string // constant type + val string // constant value + }{ + {`package a0; const _ = false`, `false`, `untyped bool`, `false`}, + {`package a1; const _ = 0`, `0`, `untyped int`, `0`}, + {`package a2; const _ = 'A'`, `'A'`, `untyped rune`, `65`}, + {`package a3; const _ = 0.`, `0.`, `untyped float`, `0`}, + {`package a4; const _ = 0i`, `0i`, `untyped complex`, `0`}, + {`package a5; const _ = "foo"`, `"foo"`, `untyped string`, `"foo"`}, + + {`package b0; var _ = false`, `false`, `bool`, `false`}, + {`package b1; var _ = 0`, `0`, `int`, `0`}, + {`package b2; var _ = 'A'`, `'A'`, `rune`, `65`}, + {`package b3; var _ = 0.`, `0.`, `float64`, `0`}, + {`package b4; var _ = 0i`, `0i`, `complex128`, `0`}, + {`package b5; var _ = "foo"`, `"foo"`, `string`, `"foo"`}, + + {`package c0a; var _ = bool(false)`, `false`, `bool`, `false`}, + {`package c0b; var _ = bool(false)`, `bool(false)`, `bool`, `false`}, + {`package c0c; type T bool; var _ = T(false)`, `T(false)`, `c0c.T`, `false`}, + + {`package c1a; var _ = int(0)`, `0`, `int`, `0`}, + {`package c1b; var _ = int(0)`, `int(0)`, `int`, `0`}, + {`package c1c; type T int; var _ = T(0)`, `T(0)`, `c1c.T`, `0`}, + + {`package c2a; var _ = rune('A')`, `'A'`, `rune`, `65`}, + {`package c2b; var _ = rune('A')`, `rune('A')`, `rune`, `65`}, + {`package c2c; type T rune; var _ = T('A')`, `T('A')`, `c2c.T`, `65`}, + + {`package c3a; var _ = float32(0.)`, `0.`, `float32`, `0`}, + {`package c3b; var _ = float32(0.)`, `float32(0.)`, `float32`, `0`}, + {`package c3c; type T float32; var _ = T(0.)`, `T(0.)`, `c3c.T`, `0`}, + + {`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `0`}, + {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `0`}, + {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `0`}, + + {`package c5a; var _ = string("foo")`, `"foo"`, `string`, `"foo"`}, + {`package c5b; var _ = string("foo")`, `string("foo")`, `string`, `"foo"`}, + {`package c5c; type T string; var _ = T("foo")`, `T("foo")`, `c5c.T`, `"foo"`}, + + {`package d0; var _ = []byte("foo")`, `"foo"`, `string`, `"foo"`}, + {`package d1; var _ = []byte(string("foo"))`, `"foo"`, `string`, `"foo"`}, + {`package d2; var _ = []byte(string("foo"))`, `string("foo")`, `string`, `"foo"`}, + {`package d3; type T []byte; var _ = T("foo")`, `"foo"`, `string`, `"foo"`}, + + {`package e0; const _ = float32( 1e-200)`, `float32(1e-200)`, `float32`, `0`}, + {`package e1; const _ = float32(-1e-200)`, `float32(-1e-200)`, `float32`, `0`}, + {`package e2; const _ = float64( 1e-2000)`, `float64(1e-2000)`, `float64`, `0`}, + {`package e3; const _ = float64(-1e-2000)`, `float64(-1e-2000)`, `float64`, `0`}, + {`package e4; const _ = complex64( 1e-200)`, `complex64(1e-200)`, `complex64`, `0`}, + {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `0`}, + {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `0`}, + {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `0`}, + + {`package f0 ; var _ float32 = 1e-200`, `1e-200`, `float32`, `0`}, + {`package f1 ; var _ float32 = -1e-200`, `-1e-200`, `float32`, `0`}, + {`package f2a; var _ float64 = 1e-2000`, `1e-2000`, `float64`, `0`}, + {`package f3a; var _ float64 = -1e-2000`, `-1e-2000`, `float64`, `0`}, + {`package f2b; var _ = 1e-2000`, `1e-2000`, `float64`, `0`}, + {`package f3b; var _ = -1e-2000`, `-1e-2000`, `float64`, `0`}, + {`package f4 ; var _ complex64 = 1e-200 `, `1e-200`, `complex64`, `0`}, + {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `0`}, + {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, + {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, + {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, + {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, + } + + for _, test := range tests { + info := Info{ + Types: make(map[ast.Expr]TypeAndValue), + } + name := mustTypecheck(t, "ValuesInfo", test.src, &info) + + // look for constant expression + var expr ast.Expr + for e := range info.Types { + if ExprString(e) == test.expr { + expr = e + break + } + } + if expr == nil { + t.Errorf("package %s: no expression found for %s", name, test.expr) + continue + } + tv := info.Types[expr] + + // check that type is correct + if got := tv.Type.String(); got != test.typ { + t.Errorf("package %s: got type %s; want %s", name, got, test.typ) + continue + } + + // check that value is correct + if got := tv.Value.String(); got != test.val { + t.Errorf("package %s: got value %s; want %s", name, got, test.val) + } + } +} + +func TestTypesInfo(t *testing.T) { + var tests = []struct { + src string + expr string // expression + typ string // value type + }{ + // single-valued expressions of untyped constants + {`package b0; var x interface{} = false`, `false`, `bool`}, + {`package b1; var x interface{} = 0`, `0`, `int`}, + {`package b2; var x interface{} = 0.`, `0.`, `float64`}, + {`package b3; var x interface{} = 0i`, `0i`, `complex128`}, + {`package b4; var x interface{} = "foo"`, `"foo"`, `string`}, + + // comma-ok expressions + {`package p0; var x interface{}; var _, _ = x.(int)`, + `x.(int)`, + `(int, bool)`, + }, + {`package p1; var x interface{}; func _() { _, _ = x.(int) }`, + `x.(int)`, + `(int, bool)`, + }, + // TODO(gri): uncomment if we accept issue 8189. + // {`package p2; type mybool bool; var m map[string]complex128; var b mybool; func _() { _, b = m["foo"] }`, + // `m["foo"]`, + // `(complex128, p2.mybool)`, + // }, + // TODO(gri): remove if we accept issue 8189. + {`package p2; var m map[string]complex128; var b bool; func _() { _, b = m["foo"] }`, + `m["foo"]`, + `(complex128, bool)`, + }, + {`package p3; var c chan string; var _, _ = <-c`, + `<-c`, + `(string, bool)`, + }, + + // issue 6796 + {`package issue6796_a; var x interface{}; var _, _ = (x.(int))`, + `x.(int)`, + `(int, bool)`, + }, + {`package issue6796_b; var c chan string; var _, _ = (<-c)`, + `(<-c)`, + `(string, bool)`, + }, + {`package issue6796_c; var c chan string; var _, _ = (<-c)`, + `<-c`, + `(string, bool)`, + }, + {`package issue6796_d; var c chan string; var _, _ = ((<-c))`, + `(<-c)`, + `(string, bool)`, + }, + {`package issue6796_e; func f(c chan string) { _, _ = ((<-c)) }`, + `(<-c)`, + `(string, bool)`, + }, + + // issue 7060 + {`package issue7060_a; var ( m map[int]string; x, ok = m[0] )`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_b; var ( m map[int]string; x, ok interface{} = m[0] )`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_c; func f(x interface{}, ok bool, m map[int]string) { x, ok = m[0] }`, + `m[0]`, + `(string, bool)`, + }, + {`package issue7060_d; var ( ch chan string; x, ok = <-ch )`, + `<-ch`, + `(string, bool)`, + }, + {`package issue7060_e; var ( ch chan string; x, ok interface{} = <-ch )`, + `<-ch`, + `(string, bool)`, + }, + {`package issue7060_f; func f(x interface{}, ok bool, ch chan string) { x, ok = <-ch }`, + `<-ch`, + `(string, bool)`, + }, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mustTypecheck(t, "TypesInfo", test.src, &info) + + // look for expression type + var typ Type + for e, tv := range info.Types { + if ExprString(e) == test.expr { + typ = tv.Type + break + } + } + if typ == nil { + t.Errorf("package %s: no type found for %s", name, test.expr) + continue + } + + // check that type is correct + if got := typ.String(); got != test.typ { + t.Errorf("package %s: got %s; want %s", name, got, test.typ) + } + } +} + +func predString(tv TypeAndValue) string { + var buf bytes.Buffer + pred := func(b bool, s string) { + if b { + if buf.Len() > 0 { + buf.WriteString(", ") + } + buf.WriteString(s) + } + } + + pred(tv.IsVoid(), "void") + pred(tv.IsType(), "type") + pred(tv.IsBuiltin(), "builtin") + pred(tv.IsValue() && tv.Value != nil, "const") + pred(tv.IsValue() && tv.Value == nil, "value") + pred(tv.IsNil(), "nil") + pred(tv.Addressable(), "addressable") + pred(tv.Assignable(), "assignable") + pred(tv.HasOk(), "hasOk") + + if buf.Len() == 0 { + return "invalid" + } + return buf.String() +} + +func TestPredicatesInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + expr string + pred string + }{ + // void + {`package n0; func f() { f() }`, `f()`, `void`}, + + // types + {`package t0; type _ int`, `int`, `type`}, + {`package t1; type _ []int`, `[]int`, `type`}, + {`package t2; type _ func()`, `func()`, `type`}, + + // built-ins + {`package b0; var _ = len("")`, `len`, `builtin`}, + {`package b1; var _ = (len)("")`, `(len)`, `builtin`}, + + // constants + {`package c0; var _ = 42`, `42`, `const`}, + {`package c1; var _ = "foo" + "bar"`, `"foo" + "bar"`, `const`}, + {`package c2; const (i = 1i; _ = i)`, `i`, `const`}, + + // values + {`package v0; var (a, b int; _ = a + b)`, `a + b`, `value`}, + {`package v1; var _ = &[]int{1}`, `([]int literal)`, `value`}, + {`package v2; var _ = func(){}`, `(func() literal)`, `value`}, + {`package v4; func f() { _ = f }`, `f`, `value`}, + {`package v3; var _ *int = nil`, `nil`, `value, nil`}, + {`package v3; var _ *int = (nil)`, `(nil)`, `value, nil`}, + + // addressable (and thus assignable) operands + {`package a0; var (x int; _ = x)`, `x`, `value, addressable, assignable`}, + {`package a1; var (p *int; _ = *p)`, `*p`, `value, addressable, assignable`}, + {`package a2; var (s []int; _ = s[0])`, `s[0]`, `value, addressable, assignable`}, + {`package a3; var (s struct{f int}; _ = s.f)`, `s.f`, `value, addressable, assignable`}, + {`package a4; var (a [10]int; _ = a[0])`, `a[0]`, `value, addressable, assignable`}, + {`package a5; func _(x int) { _ = x }`, `x`, `value, addressable, assignable`}, + {`package a6; func _()(x int) { _ = x; return }`, `x`, `value, addressable, assignable`}, + {`package a7; type T int; func (x T) _() { _ = x }`, `x`, `value, addressable, assignable`}, + // composite literals are not addressable + + // assignable but not addressable values + {`package s0; var (m map[int]int; _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + {`package s1; var (m map[int]int; _, _ = m[0])`, `m[0]`, `value, assignable, hasOk`}, + + // hasOk expressions + {`package k0; var (ch chan int; _ = <-ch)`, `<-ch`, `value, hasOk`}, + {`package k1; var (ch chan int; _, _ = <-ch)`, `<-ch`, `value, hasOk`}, + + // missing entries + // - package names are collected in the Uses map + // - identifiers being declared are collected in the Defs map + {`package m0; import "os"; func _() { _ = os.Stdout }`, `os`, `<missing>`}, + {`package m1; import p "os"; func _() { _ = p.Stdout }`, `p`, `<missing>`}, + {`package m2; const c = 0`, `c`, `<missing>`}, + {`package m3; type T int`, `T`, `<missing>`}, + {`package m4; var v int`, `v`, `<missing>`}, + {`package m5; func f() {}`, `f`, `<missing>`}, + {`package m6; func _(x int) {}`, `x`, `<missing>`}, + {`package m6; func _()(x int) { return }`, `x`, `<missing>`}, + {`package m6; type T int; func (x T) _() {}`, `x`, `<missing>`}, + } + + for _, test := range tests { + info := Info{Types: make(map[ast.Expr]TypeAndValue)} + name := mustTypecheck(t, "PredicatesInfo", test.src, &info) + + // look for expression predicates + got := "<missing>" + for e, tv := range info.Types { + //println(name, ExprString(e)) + if ExprString(e) == test.expr { + got = predString(tv) + break + } + } + + if got != test.pred { + t.Errorf("package %s: got %s; want %s", name, got, test.pred) + } + } +} + +func TestScopesInfo(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests = []struct { + src string + scopes []string // list of scope descriptors of the form kind:varlist + }{ + {`package p0`, []string{ + "file:", + }}, + {`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{ + "file:fmt m", + }}, + {`package p2; func _() {}`, []string{ + "file:", "func:", + }}, + {`package p3; func _(x, y int) {}`, []string{ + "file:", "func:x y", + }}, + {`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{ + "file:", "func:x y z", // redeclaration of x + }}, + {`package p5; func _(x, y int) (u, _ int) { return }`, []string{ + "file:", "func:u x y", + }}, + {`package p6; func _() { { var x int; _ = x } }`, []string{ + "file:", "func:", "block:x", + }}, + {`package p7; func _() { if true {} }`, []string{ + "file:", "func:", "if:", "block:", + }}, + {`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{ + "file:", "func:", "if:x", "block:y", + }}, + {`package p9; func _() { switch x := 0; x {} }`, []string{ + "file:", "func:", "switch:x", + }}, + {`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{ + "file:", "func:", "switch:x", "case:y", "case:", + }}, + {`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{ + "file:", "func:t", "type switch:", + }}, + {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ + "file:", "func:t", "type switch:t", + }}, + {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ + "file:", "func:t", "type switch:", "case:x", // x implicitly declared + }}, + {`package p14; func _() { select{} }`, []string{ + "file:", "func:", + }}, + {`package p15; func _(c chan int) { select{ case <-c: } }`, []string{ + "file:", "func:c", "comm:", + }}, + {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ + "file:", "func:c", "comm:i x", + }}, + {`package p17; func _() { for{} }`, []string{ + "file:", "func:", "for:", "block:", + }}, + {`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{ + "file:", "func:n", "for:i", "block:", + }}, + {`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{ + "file:", "func:a", "range:i", "block:", + }}, + {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ + "file:", "func:a", "range:i x", "block:", + }}, + } + + for _, test := range tests { + info := Info{Scopes: make(map[ast.Node]*Scope)} + name := mustTypecheck(t, "ScopesInfo", test.src, &info) + + // number of scopes must match + if len(info.Scopes) != len(test.scopes) { + t.Errorf("package %s: got %d scopes; want %d", name, len(info.Scopes), len(test.scopes)) + } + + // scope descriptions must match + for node, scope := range info.Scopes { + kind := "<unknown node kind>" + switch node.(type) { + case *ast.File: + kind = "file" + case *ast.FuncType: + kind = "func" + case *ast.BlockStmt: + kind = "block" + case *ast.IfStmt: + kind = "if" + case *ast.SwitchStmt: + kind = "switch" + case *ast.TypeSwitchStmt: + kind = "type switch" + case *ast.CaseClause: + kind = "case" + case *ast.CommClause: + kind = "comm" + case *ast.ForStmt: + kind = "for" + case *ast.RangeStmt: + kind = "range" + } + + // look for matching scope description + desc := kind + ":" + strings.Join(scope.Names(), " ") + found := false + for _, d := range test.scopes { + if desc == d { + found = true + break + } + } + if !found { + t.Errorf("package %s: no matching scope found for %s", name, desc) + } + } + } +} + +func TestInitOrderInfo(t *testing.T) { + var tests = []struct { + src string + inits []string + }{ + {`package p0; var (x = 1; y = x)`, []string{ + "x = 1", "y = x", + }}, + {`package p1; var (a = 1; b = 2; c = 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p2; var (a, b, c = 1, 2, 3)`, []string{ + "a = 1", "b = 2", "c = 3", + }}, + {`package p3; var _ = f(); func f() int { return 1 }`, []string{ + "_ = f()", // blank var + }}, + {`package p4; var (a = 0; x = y; y = z; z = 0)`, []string{ + "a = 0", "z = 0", "y = z", "x = y", + }}, + {`package p5; var (a, _ = m[0]; m map[int]string)`, []string{ + "a, _ = m[0]", // blank var + }}, + {`package p6; var a, b = f(); func f() (_, _ int) { return z, z }; var z = 0`, []string{ + "z = 0", "a, b = f()", + }}, + {`package p7; var (a = func() int { return b }(); b = 1)`, []string{ + "b = 1", "a = (func() int literal)()", + }}, + {`package p8; var (a, b = func() (_, _ int) { return c, c }(); c = 1)`, []string{ + "c = 1", "a, b = (func() (_, _ int) literal)()", + }}, + {`package p9; type T struct{}; func (T) m() int { _ = y; return 0 }; var x, y = T.m, 1`, []string{ + "y = 1", "x = T.m", + }}, + {`package p10; var (d = c + b; a = 0; b = 0; c = 0)`, []string{ + "a = 0", "b = 0", "c = 0", "d = c + b", + }}, + {`package p11; var (a = e + c; b = d + c; c = 0; d = 0; e = 0)`, []string{ + "c = 0", "d = 0", "b = d + c", "e = 0", "a = e + c", + }}, + // emit an initializer for n:1 initializations only once (not for each node + // on the lhs which may appear in different order in the dependency graph) + {`package p12; var (a = x; b = 0; x, y = m[0]; m map[int]int)`, []string{ + "b = 0", "x, y = m[0]", "a = x", + }}, + // test case from spec section on package initialization + {`package p12 + + var ( + a = c + b + b = f() + c = f() + d = 3 + ) + + func f() int { + d++ + return d + }`, []string{ + "d = 3", "b = f()", "c = f()", "a = c + b", + }}, + // test case for issue 7131 + {`package main + + var counter int + func next() int { counter++; return counter } + + var _ = makeOrder() + func makeOrder() []int { return []int{f, b, d, e, c, a} } + + var a = next() + var b, c = next(), next() + var d, e, f = next(), next(), next() + `, []string{ + "a = next()", "b = next()", "c = next()", "d = next()", "e = next()", "f = next()", "_ = makeOrder()", + }}, + } + + for _, test := range tests { + info := Info{} + name := mustTypecheck(t, "InitOrderInfo", test.src, &info) + + // number of initializers must match + if len(info.InitOrder) != len(test.inits) { + t.Errorf("package %s: got %d initializers; want %d", name, len(info.InitOrder), len(test.inits)) + continue + } + + // initializers must match + for i, want := range test.inits { + got := info.InitOrder[i].String() + if got != want { + t.Errorf("package %s, init %d: got %s; want %s", name, i, got, want) + continue + } + } + } +} + +func TestMultiFileInitOrder(t *testing.T) { + fset := token.NewFileSet() + mustParse := func(src string) *ast.File { + f, err := parser.ParseFile(fset, "main", src, 0) + if err != nil { + t.Fatal(err) + } + return f + } + + fileA := mustParse(`package main; var a = 1`) + fileB := mustParse(`package main; var b = 2`) + + // The initialization order must not depend on the parse + // order of the files, only on the presentation order to + // the type-checker. + for _, test := range []struct { + files []*ast.File + want string + }{ + {[]*ast.File{fileA, fileB}, "[a = 1 b = 2]"}, + {[]*ast.File{fileB, fileA}, "[b = 2 a = 1]"}, + } { + var info Info + if _, err := new(Config).Check("main", fset, test.files, &info); err != nil { + t.Fatal(err) + } + if got := fmt.Sprint(info.InitOrder); got != test.want { + t.Fatalf("got %s; want %s", got, test.want) + } + } +} + +func TestFiles(t *testing.T) { + var sources = []string{ + "package p; type T struct{}; func (T) m1() {}", + "package p; func (T) m2() {}; var x interface{ m1(); m2() } = T{}", + "package p; func (T) m3() {}; var y interface{ m1(); m2(); m3() } = T{}", + "package p", + } + + var conf Config + fset := token.NewFileSet() + pkg := NewPackage("p", "p") + var info Info + check := NewChecker(&conf, fset, pkg, &info) + + for i, src := range sources { + filename := fmt.Sprintf("sources%d", i) + f, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + t.Fatal(err) + } + if err := check.Files([]*ast.File{f}); err != nil { + t.Error(err) + } + } + + // check InitOrder is [x y] + var vars []string + for _, init := range info.InitOrder { + for _, v := range init.Lhs { + vars = append(vars, v.Name()) + } + } + if got, want := fmt.Sprint(vars), "[x y]"; got != want { + t.Errorf("InitOrder == %s, want %s", got, want) + } +} + +type testImporter map[string]*Package + +func (m testImporter) Import(path string) (*Package, error) { + if pkg := m[path]; pkg != nil { + return pkg, nil + } + return nil, fmt.Errorf("package %q not found", path) +} + +func TestSelection(t *testing.T) { + selections := make(map[*ast.SelectorExpr]*Selection) + + fset := token.NewFileSet() + imports := make(testImporter) + conf := Config{Importer: imports} + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path+".go", src, 0) + if err != nil { + t.Fatal(err) + } + pkg, err := conf.Check(path, fset, []*ast.File{f}, &Info{Selections: selections}) + if err != nil { + t.Fatal(err) + } + imports[path] = pkg + } + + const libSrc = ` +package lib +type T float64 +const C T = 3 +var V T +func F() {} +func (T) M() {} +` + const mainSrc = ` +package main +import "lib" + +type A struct { + *B + C +} + +type B struct { + b int +} + +func (B) f(int) + +type C struct { + c int +} + +func (C) g() +func (*C) h() + +func main() { + // qualified identifiers + var _ lib.T + _ = lib.C + _ = lib.F + _ = lib.V + _ = lib.T.M + + // fields + _ = A{}.B + _ = new(A).B + + _ = A{}.C + _ = new(A).C + + _ = A{}.b + _ = new(A).b + + _ = A{}.c + _ = new(A).c + + // methods + _ = A{}.f + _ = new(A).f + _ = A{}.g + _ = new(A).g + _ = new(A).h + + _ = B{}.f + _ = new(B).f + + _ = C{}.g + _ = new(C).g + _ = new(C).h + + // method expressions + _ = A.f + _ = (*A).f + _ = B.f + _ = (*B).f +}` + + wantOut := map[string][2]string{ + "lib.T.M": {"method expr (lib.T) M(lib.T)", ".[0]"}, + + "A{}.B": {"field (main.A) B *main.B", ".[0]"}, + "new(A).B": {"field (*main.A) B *main.B", "->[0]"}, + "A{}.C": {"field (main.A) C main.C", ".[1]"}, + "new(A).C": {"field (*main.A) C main.C", "->[1]"}, + "A{}.b": {"field (main.A) b int", "->[0 0]"}, + "new(A).b": {"field (*main.A) b int", "->[0 0]"}, + "A{}.c": {"field (main.A) c int", ".[1 0]"}, + "new(A).c": {"field (*main.A) c int", "->[1 0]"}, + + "A{}.f": {"method (main.A) f(int)", "->[0 0]"}, + "new(A).f": {"method (*main.A) f(int)", "->[0 0]"}, + "A{}.g": {"method (main.A) g()", ".[1 0]"}, + "new(A).g": {"method (*main.A) g()", "->[1 0]"}, + "new(A).h": {"method (*main.A) h()", "->[1 1]"}, // TODO(gri) should this report .[1 1] ? + "B{}.f": {"method (main.B) f(int)", ".[0]"}, + "new(B).f": {"method (*main.B) f(int)", "->[0]"}, + "C{}.g": {"method (main.C) g()", ".[0]"}, + "new(C).g": {"method (*main.C) g()", "->[0]"}, + "new(C).h": {"method (*main.C) h()", "->[1]"}, // TODO(gri) should this report .[1] ? + + "A.f": {"method expr (main.A) f(main.A, int)", "->[0 0]"}, + "(*A).f": {"method expr (*main.A) f(*main.A, int)", "->[0 0]"}, + "B.f": {"method expr (main.B) f(main.B, int)", ".[0]"}, + "(*B).f": {"method expr (*main.B) f(*main.B, int)", "->[0]"}, + } + + makePkg("lib", libSrc) + makePkg("main", mainSrc) + + for e, sel := range selections { + sel.String() // assertion: must not panic + + start := fset.Position(e.Pos()).Offset + end := fset.Position(e.End()).Offset + syntax := mainSrc[start:end] // (all SelectorExprs are in main, not lib) + + direct := "." + if sel.Indirect() { + direct = "->" + } + got := [2]string{ + sel.String(), + fmt.Sprintf("%s%v", direct, sel.Index()), + } + want := wantOut[syntax] + if want != got { + t.Errorf("%s: got %q; want %q", syntax, got, want) + } + delete(wantOut, syntax) + + // We must explicitly assert properties of the + // Signature's receiver since it doesn't participate + // in Identical() or String(). + sig, _ := sel.Type().(*Signature) + if sel.Kind() == MethodVal { + got := sig.Recv().Type() + want := sel.Recv() + if !Identical(got, want) { + t.Errorf("%s: Recv() = %s, want %s", syntax, got, want) + } + } else if sig != nil && sig.Recv() != nil { + t.Errorf("%s: signature has receiver %s", sig, sig.Recv().Type()) + } + } + // Assert that all wantOut entries were used exactly once. + for syntax := range wantOut { + t.Errorf("no ast.Selection found with syntax %q", syntax) + } +} + +func TestIssue8518(t *testing.T) { + fset := token.NewFileSet() + imports := make(testImporter) + conf := Config{ + Error: func(err error) { t.Log(err) }, // don't exit after first error + Importer: imports, + } + makePkg := func(path, src string) { + f, err := parser.ParseFile(fset, path, src, 0) + if err != nil { + t.Fatal(err) + } + pkg, _ := conf.Check(path, fset, []*ast.File{f}, nil) // errors logged via conf.Error + imports[path] = pkg + } + + const libSrc = ` +package a +import "missing" +const C1 = foo +const C2 = missing.C +` + + const mainSrc = ` +package main +import "a" +var _ = a.C1 +var _ = a.C2 +` + + makePkg("a", libSrc) + makePkg("main", mainSrc) // don't crash when type-checking this package +} + +func TestLookupFieldOrMethod(t *testing.T) { + // Test cases assume a lookup of the form a.f or x.f, where a stands for an + // addressable value, and x for a non-addressable value (even though a variable + // for ease of test case writing). + var tests = []struct { + src string + found bool + index []int + indirect bool + }{ + // field lookups + {"var x T; type T struct{}", false, nil, false}, + {"var x T; type T struct{ f int }", true, []int{0}, false}, + {"var x T; type T struct{ a, b, f, c int }", true, []int{2}, false}, + + // method lookups + {"var a T; type T struct{}; func (T) f() {}", true, []int{0}, false}, + {"var a *T; type T struct{}; func (T) f() {}", true, []int{0}, true}, + {"var a T; type T struct{}; func (*T) f() {}", true, []int{0}, false}, + {"var a *T; type T struct{}; func (*T) f() {}", true, []int{0}, true}, // TODO(gri) should this report indirect = false? + + // collisions + {"type ( E1 struct{ f int }; E2 struct{ f int }; x struct{ E1; *E2 })", false, []int{1, 0}, false}, + {"type ( E1 struct{ f int }; E2 struct{}; x struct{ E1; *E2 }); func (E2) f() {}", false, []int{1, 0}, false}, + + // outside methodset + // (*T).f method exists, but value of type T is not addressable + {"var x T; type T struct{}; func (*T) f() {}", false, nil, true}, + } + + for _, test := range tests { + pkg, err := pkgFor("test", "package p;"+test.src, nil) + if err != nil { + t.Errorf("%s: incorrect test case: %s", test.src, err) + continue + } + + obj := pkg.Scope().Lookup("a") + if obj == nil { + if obj = pkg.Scope().Lookup("x"); obj == nil { + t.Errorf("%s: incorrect test case - no object a or x", test.src) + continue + } + } + + f, index, indirect := LookupFieldOrMethod(obj.Type(), obj.Name() == "a", pkg, "f") + if (f != nil) != test.found { + if f == nil { + t.Errorf("%s: got no object; want one", test.src) + } else { + t.Errorf("%s: got object = %v; want none", test.src, f) + } + } + if !sameSlice(index, test.index) { + t.Errorf("%s: got index = %v; want %v", test.src, index, test.index) + } + if indirect != test.indirect { + t.Errorf("%s: got indirect = %v; want %v", test.src, indirect, test.indirect) + } + } +} + +func sameSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, x := range a { + if x != b[i] { + return false + } + } + return true +} + +// TestScopeLookupParent ensures that (*Scope).LookupParent returns +// the correct result at various positions with the source. +func TestScopeLookupParent(t *testing.T) { + fset := token.NewFileSet() + imports := make(testImporter) + conf := Config{Importer: imports} + mustParse := func(src string) *ast.File { + f, err := parser.ParseFile(fset, "dummy.go", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + return f + } + var info Info + makePkg := func(path string, files ...*ast.File) { + imports[path], _ = conf.Check(path, fset, files, &info) + } + + makePkg("lib", mustParse("package lib; var X int")) + // Each /*name=kind:line*/ comment makes the test look up the + // name at that point and checks that it resolves to a decl of + // the specified kind and line number. "undef" means undefined. + mainSrc := ` +package main +import "lib" +var Y = lib.X +func f() { + print(Y) /*Y=var:4*/ + z /*z=undef*/ := /*z=undef*/ 1 /*z=var:7*/ + print(z) + /*f=func:5*/ /*lib=pkgname:3*/ + type /*T=undef*/ T /*T=typename:10*/ *T +} +` + info.Uses = make(map[*ast.Ident]Object) + f := mustParse(mainSrc) + makePkg("main", f) + mainScope := imports["main"].Scope() + rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`) + for _, group := range f.Comments { + for _, comment := range group.List { + // Parse the assertion in the comment. + m := rx.FindStringSubmatch(comment.Text) + if m == nil { + t.Errorf("%s: bad comment: %s", + fset.Position(comment.Pos()), comment.Text) + continue + } + name, want := m[1], m[2] + + // Look up the name in the innermost enclosing scope. + inner := mainScope.Innermost(comment.Pos()) + if inner == nil { + t.Errorf("%s: at %s: can't find innermost scope", + fset.Position(comment.Pos()), comment.Text) + continue + } + got := "undef" + if _, obj := inner.LookupParent(name, comment.Pos()); obj != nil { + kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) + got = fmt.Sprintf("%s:%d", kind, fset.Position(obj.Pos()).Line) + } + if got != want { + t.Errorf("%s: at %s: %s resolved to %s, want %s", + fset.Position(comment.Pos()), comment.Text, name, got, want) + } + } + } + + // Check that for each referring identifier, + // a lookup of its name on the innermost + // enclosing scope returns the correct object. + + for id, wantObj := range info.Uses { + inner := mainScope.Innermost(id.Pos()) + if inner == nil { + t.Errorf("%s: can't find innermost scope enclosing %q", + fset.Position(id.Pos()), id.Name) + continue + } + + // Exclude selectors and qualified identifiers---lexical + // refs only. (Ideally, we'd see if the AST parent is a + // SelectorExpr, but that requires PathEnclosingInterval + // from golang.org/x/tools/go/ast/astutil.) + if id.Name == "X" { + continue + } + + _, gotObj := inner.LookupParent(id.Name, id.Pos()) + if gotObj != wantObj { + t.Errorf("%s: got %v, want %v", + fset.Position(id.Pos()), gotObj, wantObj) + continue + } + } +} diff --git a/libgo/go/go/types/assignments.go b/libgo/go/go/types/assignments.go new file mode 100644 index 00000000000..e88de56a0da --- /dev/null +++ b/libgo/go/go/types/assignments.go @@ -0,0 +1,328 @@ +// Copyright 2013 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 initialization and assignment checks. + +package types + +import ( + "go/ast" + "go/token" +) + +// assignment reports whether x can be assigned to a variable of type T, +// if necessary by attempting to convert untyped values to the appropriate +// type. If x.mode == invalid upon return, then assignment has already +// issued an error message and the caller doesn't have to report another. +// Use T == nil to indicate assignment to an untyped blank identifier. +// +// TODO(gri) Should find a better way to handle in-band errors. +// +func (check *Checker) assignment(x *operand, T Type) bool { + switch x.mode { + case invalid: + return true // error reported before + case constant_, variable, mapindex, value, commaok: + // ok + default: + unreachable() + } + + // x must be a single value + // (tuple types are never named - no need for underlying type) + if t, _ := x.typ.(*Tuple); t != nil { + assert(t.Len() > 1) + check.errorf(x.pos(), "%d-valued expression %s used as single value", t.Len(), x) + x.mode = invalid + return false + } + + if isUntyped(x.typ) { + target := T + // spec: "If an untyped constant is assigned to a variable of interface + // type or the blank identifier, the constant is first converted to type + // bool, rune, int, float64, complex128 or string respectively, depending + // on whether the value is a boolean, rune, integer, floating-point, complex, + // or string constant." + if T == nil || IsInterface(T) { + if T == nil && x.typ == Typ[UntypedNil] { + check.errorf(x.pos(), "use of untyped nil") + x.mode = invalid + return false + } + target = defaultType(x.typ) + } + check.convertUntyped(x, target) + if x.mode == invalid { + return false + } + } + + // spec: "If a left-hand side is the blank identifier, any typed or + // non-constant value except for the predeclared identifier nil may + // be assigned to it." + return T == nil || x.assignableTo(check.conf, T) +} + +func (check *Checker) initConst(lhs *Const, x *operand) { + if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return + } + + // rhs must be a constant + if x.mode != constant_ { + check.errorf(x.pos(), "%s is not constant", x) + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return + } + assert(isConstType(x.typ)) + + // If the lhs doesn't have a type yet, use the type of x. + if lhs.typ == nil { + lhs.typ = x.typ + } + + if !check.assignment(x, lhs.typ) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot define constant %s (type %s) as %s", lhs.Name(), lhs.typ, x) + } + return + } + + lhs.val = x.val +} + +// If result is set, lhs is a function result parameter and x is a return result. +func (check *Checker) initVar(lhs *Var, x *operand, result bool) Type { + if x.mode == invalid || x.typ == Typ[Invalid] || lhs.typ == Typ[Invalid] { + if lhs.typ == nil { + lhs.typ = Typ[Invalid] + } + return nil + } + + // If the lhs doesn't have a type yet, use the type of x. + if lhs.typ == nil { + typ := x.typ + if isUntyped(typ) { + // convert untyped types to default types + if typ == Typ[UntypedNil] { + check.errorf(x.pos(), "use of untyped nil") + lhs.typ = Typ[Invalid] + return nil + } + typ = defaultType(typ) + } + lhs.typ = typ + } + + if !check.assignment(x, lhs.typ) { + if x.mode != invalid { + if result { + // don't refer to lhs.name because it may be an anonymous result parameter + check.errorf(x.pos(), "cannot return %s as value of type %s", x, lhs.typ) + } else { + check.errorf(x.pos(), "cannot initialize %s with %s", lhs, x) + } + } + return nil + } + + return x.typ +} + +func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type { + if x.mode == invalid || x.typ == Typ[Invalid] { + return nil + } + + // Determine if the lhs is a (possibly parenthesized) identifier. + ident, _ := unparen(lhs).(*ast.Ident) + + // Don't evaluate lhs if it is the blank identifier. + if ident != nil && ident.Name == "_" { + check.recordDef(ident, nil) + if !check.assignment(x, nil) { + assert(x.mode == invalid) + x.typ = nil + } + return x.typ + } + + // If the lhs is an identifier denoting a variable v, this assignment + // is not a 'use' of v. Remember current value of v.used and restore + // after evaluating the lhs via check.expr. + var v *Var + var v_used bool + if ident != nil { + if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil { + v, _ = obj.(*Var) + if v != nil { + v_used = v.used + } + } + } + + var z operand + check.expr(&z, lhs) + if v != nil { + v.used = v_used // restore v.used + } + + if z.mode == invalid || z.typ == Typ[Invalid] { + return nil + } + + // spec: "Each left-hand side operand must be addressable, a map index + // expression, or the blank identifier. Operands may be parenthesized." + switch z.mode { + case invalid: + return nil + case variable, mapindex: + // ok + default: + check.errorf(z.pos(), "cannot assign to %s", &z) + return nil + } + + if !check.assignment(x, z.typ) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot assign %s to %s", x, &z) + } + return nil + } + + return x.typ +} + +// If returnPos is valid, initVars is called to type-check the assignment of +// return expressions, and returnPos is the position of the return statement. +func (check *Checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { + l := len(lhs) + get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid()) + if get == nil || l != r { + // invalidate lhs and use rhs + for _, obj := range lhs { + if obj.typ == nil { + obj.typ = Typ[Invalid] + } + } + if get == nil { + return // error reported by unpack + } + check.useGetter(get, r) + if returnPos.IsValid() { + check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) + return + } + check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) + return + } + + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.initVar(lhs[i], &x, returnPos.IsValid()) + } + check.recordCommaOkTypes(rhs[0], a) + return + } + + for i, lhs := range lhs { + get(&x, i) + check.initVar(lhs, &x, returnPos.IsValid()) + } +} + +func (check *Checker) assignVars(lhs, rhs []ast.Expr) { + l := len(lhs) + get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2) + if get == nil { + return // error reported by unpack + } + if l != r { + check.useGetter(get, r) + check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) + return + } + + var x operand + if commaOk { + var a [2]Type + for i := range a { + get(&x, i) + a[i] = check.assignVar(lhs[i], &x) + } + check.recordCommaOkTypes(rhs[0], a) + return + } + + for i, lhs := range lhs { + get(&x, i) + check.assignVar(lhs, &x) + } +} + +func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) { + scope := check.scope + + // collect lhs variables + var newVars []*Var + var lhsVars = make([]*Var, len(lhs)) + for i, lhs := range lhs { + var obj *Var + if ident, _ := lhs.(*ast.Ident); ident != nil { + // Use the correct obj if the ident is redeclared. The + // variable's scope starts after the declaration; so we + // must use Scope.Lookup here and call Scope.Insert + // (via check.declare) later. + name := ident.Name + if alt := scope.Lookup(name); alt != nil { + // redeclared object must be a variable + if alt, _ := alt.(*Var); alt != nil { + obj = alt + } else { + check.errorf(lhs.Pos(), "cannot assign to %s", lhs) + } + check.recordUse(ident, alt) + } else { + // declare new variable, possibly a blank (_) variable + obj = NewVar(ident.Pos(), check.pkg, name, nil) + if name != "_" { + newVars = append(newVars, obj) + } + check.recordDef(ident, obj) + } + } else { + check.errorf(lhs.Pos(), "cannot declare %s", lhs) + } + if obj == nil { + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + lhsVars[i] = obj + } + + check.initVars(lhsVars, rhs, token.NoPos) + + // declare new variables + if len(newVars) > 0 { + // spec: "The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl + // for short variable declarations) and ends at the end of the innermost + // containing block." + scopePos := rhs[len(rhs)-1].End() + for _, obj := range newVars { + check.declare(scope, nil, obj, scopePos) // recordObject already called + } + } else { + check.softErrorf(pos, "no new variables on left side of :=") + } +} diff --git a/libgo/go/go/types/builtins.go b/libgo/go/go/types/builtins.go new file mode 100644 index 00000000000..47295914d00 --- /dev/null +++ b/libgo/go/go/types/builtins.go @@ -0,0 +1,627 @@ +// 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 implements typechecking of builtin function calls. + +package types + +import ( + "go/ast" + "go/constant" + "go/token" +) + +// builtin type-checks a call to the built-in specified by id and +// returns true if the call is valid, with *x holding the result; +// but x.expr is not set. If the call is invalid, the result is +// false, and *x is undefined. +// +func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) { + // append is the only built-in that permits the use of ... for the last argument + bin := predeclaredFuncs[id] + if call.Ellipsis.IsValid() && id != _Append { + check.invalidOp(call.Ellipsis, "invalid use of ... with built-in %s", bin.name) + check.use(call.Args...) + return + } + + // For len(x) and cap(x) we need to know if x contains any function calls or + // receive operations. Save/restore current setting and set hasCallOrRecv to + // false for the evaluation of x so that we can check it afterwards. + // Note: We must do this _before_ calling unpack because unpack evaluates the + // first argument before we even call arg(x, 0)! + if id == _Len || id == _Cap { + defer func(b bool) { + check.hasCallOrRecv = b + }(check.hasCallOrRecv) + check.hasCallOrRecv = false + } + + // determine actual arguments + var arg getter + nargs := len(call.Args) + switch id { + default: + // make argument getter + arg, nargs, _ = unpack(func(x *operand, i int) { check.expr(x, call.Args[i]) }, nargs, false) + if arg == nil { + return + } + // evaluate first argument, if present + if nargs > 0 { + arg(x, 0) + if x.mode == invalid { + return + } + } + case _Make, _New, _Offsetof, _Trace: + // arguments require special handling + } + + // check argument count + { + msg := "" + if nargs < bin.nargs { + msg = "not enough" + } else if !bin.variadic && nargs > bin.nargs { + msg = "too many" + } + if msg != "" { + check.invalidOp(call.Rparen, "%s arguments for %s (expected %d, found %d)", msg, call, bin.nargs, nargs) + return + } + } + + switch id { + case _Append: + // append(s S, x ...T) S, where T is the element type of S + // spec: "The variadic function append appends zero or more values x to s of type + // S, which must be a slice type, and returns the resulting slice, also of type S. + // The values x are passed to a parameter of type ...T where T is the element type + // of S and the respective parameter passing rules apply." + S := x.typ + var T Type + if s, _ := S.Underlying().(*Slice); s != nil { + T = s.elem + } else { + check.invalidArg(x.pos(), "%s is not a slice", x) + return + } + + // remember arguments that have been evaluated already + alist := []operand{*x} + + // spec: "As a special case, append also accepts a first argument assignable + // to type []byte with a second argument of string type followed by ... . + // This form appends the bytes of the string. + if nargs == 2 && call.Ellipsis.IsValid() && x.assignableTo(check.conf, NewSlice(universeByte)) { + arg(x, 1) + if x.mode == invalid { + return + } + if isString(x.typ) { + if check.Types != nil { + sig := makeSig(S, S, x.typ) + sig.variadic = true + check.recordBuiltinType(call.Fun, sig) + } + x.mode = value + x.typ = S + break + } + alist = append(alist, *x) + // fallthrough + } + + // check general case by creating custom signature + sig := makeSig(S, S, NewSlice(T)) // []T required for variadic signature + sig.variadic = true + check.arguments(x, call, sig, func(x *operand, i int) { + // only evaluate arguments that have not been evaluated before + if i < len(alist) { + *x = alist[i] + return + } + arg(x, i) + }, nargs) + // ok to continue even if check.arguments reported errors + + x.mode = value + x.typ = S + if check.Types != nil { + check.recordBuiltinType(call.Fun, sig) + } + + case _Cap, _Len: + // cap(x) + // len(x) + mode := invalid + var typ Type + var val constant.Value + switch typ = implicitArrayDeref(x.typ.Underlying()); t := typ.(type) { + case *Basic: + if isString(t) && id == _Len { + if x.mode == constant_ { + mode = constant_ + val = constant.MakeInt64(int64(len(constant.StringVal(x.val)))) + } else { + mode = value + } + } + + case *Array: + mode = value + // spec: "The expressions len(s) and cap(s) are constants + // if the type of s is an array or pointer to an array and + // the expression s does not contain channel receives or + // function calls; in this case s is not evaluated." + if !check.hasCallOrRecv { + mode = constant_ + val = constant.MakeInt64(t.len) + } + + case *Slice, *Chan: + mode = value + + case *Map: + if id == _Len { + mode = value + } + } + + if mode == invalid { + check.invalidArg(x.pos(), "%s for %s", x, bin.name) + return + } + + x.mode = mode + x.typ = Typ[Int] + x.val = val + if check.Types != nil && mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(x.typ, typ)) + } + + case _Close: + // close(c) + c, _ := x.typ.Underlying().(*Chan) + if c == nil { + check.invalidArg(x.pos(), "%s is not a channel", x) + return + } + if c.dir == RecvOnly { + check.invalidArg(x.pos(), "%s must not be a receive-only channel", x) + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, c)) + } + + case _Complex: + // complex(x, y realT) complexT + if !check.complexArg(x) { + return + } + + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + if !check.complexArg(&y) { + return + } + + check.convertUntyped(x, y.typ) + if x.mode == invalid { + return + } + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + return + } + + if !Identical(x.typ, y.typ) { + check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + return + } + + if x.mode == constant_ && y.mode == constant_ { + x.val = constant.BinaryOp(x.val, token.ADD, constant.MakeImag(y.val)) + } else { + x.mode = value + } + + realT := x.typ + complexT := Typ[Invalid] + switch realT.Underlying().(*Basic).kind { + case Float32: + complexT = Typ[Complex64] + case Float64: + complexT = Typ[Complex128] + case UntypedInt, UntypedRune, UntypedFloat: + if x.mode == constant_ { + realT = defaultType(realT).(*Basic) + complexT = Typ[UntypedComplex] + } else { + // untyped but not constant; probably because one + // operand is a non-constant shift of untyped lhs + realT = Typ[Float64] + complexT = Typ[Complex128] + } + default: + check.invalidArg(x.pos(), "float32 or float64 arguments expected") + return + } + + x.typ = complexT + if check.Types != nil && x.mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(complexT, realT, realT)) + } + + if x.mode != constant_ { + // The arguments have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + // (If the result is constant, the arguments are never + // materialized and there is nothing to do.) + check.updateExprType(x.expr, realT, true) + check.updateExprType(y.expr, realT, true) + } + + case _Copy: + // copy(x, y []T) int + var dst Type + if t, _ := x.typ.Underlying().(*Slice); t != nil { + dst = t.elem + } + + var y operand + arg(&y, 1) + if y.mode == invalid { + return + } + var src Type + switch t := y.typ.Underlying().(type) { + case *Basic: + if isString(y.typ) { + src = universeByte + } + case *Slice: + src = t.elem + } + + if dst == nil || src == nil { + check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y) + return + } + + if !Identical(dst, src) { + check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src) + return + } + + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(Typ[Int], x.typ, y.typ)) + } + x.mode = value + x.typ = Typ[Int] + + case _Delete: + // delete(m, k) + m, _ := x.typ.Underlying().(*Map) + if m == nil { + check.invalidArg(x.pos(), "%s is not a map", x) + return + } + arg(x, 1) // k + if x.mode == invalid { + return + } + + if !x.assignableTo(check.conf, m.key) { + check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.key) + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, m, m.key)) + } + + case _Imag, _Real: + // imag(complexT) realT + // real(complexT) realT + if !isComplex(x.typ) { + check.invalidArg(x.pos(), "%s must be a complex number", x) + return + } + if x.mode == constant_ { + if id == _Real { + x.val = constant.Real(x.val) + } else { + x.val = constant.Imag(x.val) + } + } else { + x.mode = value + } + var k BasicKind + switch x.typ.Underlying().(*Basic).kind { + case Complex64: + k = Float32 + case Complex128: + k = Float64 + case UntypedComplex: + k = UntypedFloat + default: + unreachable() + } + + if check.Types != nil && x.mode != constant_ { + check.recordBuiltinType(call.Fun, makeSig(Typ[k], x.typ)) + } + x.typ = Typ[k] + + case _Make: + // make(T, n) + // make(T, n, m) + // (no argument evaluated yet) + arg0 := call.Args[0] + T := check.typ(arg0) + if T == Typ[Invalid] { + return + } + + var min int // minimum number of arguments + switch T.Underlying().(type) { + case *Slice: + min = 2 + case *Map, *Chan: + min = 1 + default: + check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0) + return + } + if nargs < min || min+1 < nargs { + check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, nargs) + return + } + var sizes []int64 // constant integer arguments, if any + for _, arg := range call.Args[1:] { + if s, ok := check.index(arg, -1); ok && s >= 0 { + sizes = append(sizes, s) + } + } + if len(sizes) == 2 && sizes[0] > sizes[1] { + check.invalidArg(call.Args[1].Pos(), "length and capacity swapped") + // safe to continue + } + x.mode = value + x.typ = T + if check.Types != nil { + params := [...]Type{T, Typ[Int], Typ[Int]} + check.recordBuiltinType(call.Fun, makeSig(x.typ, params[:1+len(sizes)]...)) + } + + case _New: + // new(T) + // (no argument evaluated yet) + T := check.typ(call.Args[0]) + if T == Typ[Invalid] { + return + } + + x.mode = value + x.typ = &Pointer{base: T} + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ, T)) + } + + case _Panic: + // panic(x) + T := new(Interface) + if !check.assignment(x, T) { + assert(x.mode == invalid) + return + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, T)) + } + + case _Print, _Println: + // print(x, y, ...) + // println(x, y, ...) + var params []Type + if nargs > 0 { + params = make([]Type, nargs) + for i := 0; i < nargs; i++ { + if i > 0 { + arg(x, i) // first argument already evaluated + } + if !check.assignment(x, nil) { + assert(x.mode == invalid) + return + } + params[i] = x.typ + } + } + + x.mode = novalue + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(nil, params...)) + } + + case _Recover: + // recover() interface{} + x.mode = value + x.typ = new(Interface) + if check.Types != nil { + check.recordBuiltinType(call.Fun, makeSig(x.typ)) + } + + case _Alignof: + // unsafe.Alignof(x T) uintptr + if !check.assignment(x, nil) { + assert(x.mode == invalid) + return + } + + x.mode = constant_ + x.val = constant.MakeInt64(check.conf.alignof(x.typ)) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Offsetof: + // unsafe.Offsetof(x T) uintptr, where x must be a selector + // (no argument evaluated yet) + arg0 := call.Args[0] + selx, _ := unparen(arg0).(*ast.SelectorExpr) + if selx == nil { + check.invalidArg(arg0.Pos(), "%s is not a selector expression", arg0) + check.use(arg0) + return + } + + check.expr(x, selx.X) + if x.mode == invalid { + return + } + + base := derefStructPtr(x.typ) + sel := selx.Sel.Name + obj, index, indirect := LookupFieldOrMethod(base, false, check.pkg, sel) + switch obj.(type) { + case nil: + check.invalidArg(x.pos(), "%s has no single field %s", base, sel) + return + case *Func: + // TODO(gri) Using derefStructPtr may result in methods being found + // that don't actually exist. An error either way, but the error + // message is confusing. See: https://play.golang.org/p/al75v23kUy , + // but go/types reports: "invalid argument: x.m is a method value". + check.invalidArg(arg0.Pos(), "%s is a method value", arg0) + return + } + if indirect { + check.invalidArg(x.pos(), "field %s is embedded via a pointer in %s", sel, base) + return + } + + // TODO(gri) Should we pass x.typ instead of base (and indirect report if derefStructPtr indirected)? + check.recordSelection(selx, FieldVal, base, obj, index, false) + + offs := check.conf.offsetof(base, index) + x.mode = constant_ + x.val = constant.MakeInt64(offs) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Sizeof: + // unsafe.Sizeof(x T) uintptr + if !check.assignment(x, nil) { + assert(x.mode == invalid) + return + } + + x.mode = constant_ + x.val = constant.MakeInt64(check.conf.sizeof(x.typ)) + x.typ = Typ[Uintptr] + // result is constant - no need to record signature + + case _Assert: + // assert(pred) causes a typechecker error if pred is false. + // The result of assert is the value of pred if there is no error. + // Note: assert is only available in self-test mode. + if x.mode != constant_ || !isBoolean(x.typ) { + check.invalidArg(x.pos(), "%s is not a boolean constant", x) + return + } + if x.val.Kind() != constant.Bool { + check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x) + return + } + if !constant.BoolVal(x.val) { + check.errorf(call.Pos(), "%s failed", call) + // compile-time assertion failure - safe to continue + } + // result is constant - no need to record signature + + case _Trace: + // trace(x, y, z, ...) dumps the positions, expressions, and + // values of its arguments. The result of trace is the value + // of the first argument. + // Note: trace is only available in self-test mode. + // (no argument evaluated yet) + if nargs == 0 { + check.dump("%s: trace() without arguments", call.Pos()) + x.mode = novalue + break + } + var t operand + x1 := x + for _, arg := range call.Args { + check.rawExpr(x1, arg, nil) // permit trace for types, e.g.: new(trace(T)) + check.dump("%s: %s", x1.pos(), x1) + x1 = &t // use incoming x only for first argument + } + // trace is only available in test mode - no need to record signature + + default: + unreachable() + } + + return true +} + +// makeSig makes a signature for the given argument and result types. +// Default types are used for untyped arguments, and res may be nil. +func makeSig(res Type, args ...Type) *Signature { + list := make([]*Var, len(args)) + for i, param := range args { + list[i] = NewVar(token.NoPos, nil, "", defaultType(param)) + } + params := NewTuple(list...) + var result *Tuple + if res != nil { + assert(!isUntyped(res)) + result = NewTuple(NewVar(token.NoPos, nil, "", res)) + } + return &Signature{params: params, results: result} +} + +// implicitArrayDeref returns A if typ is of the form *A and A is an array; +// otherwise it returns typ. +// +func implicitArrayDeref(typ Type) Type { + if p, ok := typ.(*Pointer); ok { + if a, ok := p.base.Underlying().(*Array); ok { + return a + } + } + return typ +} + +// unparen returns e with any enclosing parentheses stripped. +func unparen(e ast.Expr) ast.Expr { + for { + p, ok := e.(*ast.ParenExpr) + if !ok { + return e + } + e = p.X + } +} + +func (check *Checker) complexArg(x *operand) bool { + t, _ := x.typ.Underlying().(*Basic) + if t != nil && (t.info&IsFloat != 0 || t.kind == UntypedInt || t.kind == UntypedRune) { + return true + } + check.invalidArg(x.pos(), "%s must be a float32, float64, or an untyped non-complex numeric constant", x) + return false +} diff --git a/libgo/go/go/types/builtins_test.go b/libgo/go/go/types/builtins_test.go new file mode 100644 index 00000000000..9835a482670 --- /dev/null +++ b/libgo/go/go/types/builtins_test.go @@ -0,0 +1,204 @@ +// Copyright 2013 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" + "testing" + + . "go/types" +) + +var builtinCalls = []struct { + name, src, sig string +}{ + {"append", `var s []int; _ = append(s)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = append(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []int; _ = (append)(s, 0)`, `func([]int, ...int) []int`}, + {"append", `var s []byte; _ = ((append))(s, 0)`, `func([]byte, ...byte) []byte`}, + {"append", `var s []byte; _ = append(s, "foo"...)`, `func([]byte, string...) []byte`}, + {"append", `type T []byte; var s T; var str string; _ = append(s, str...)`, `func(p.T, string...) p.T`}, + {"append", `type T []byte; type U string; var s T; var str U; _ = append(s, str...)`, `func(p.T, p.U...) p.T`}, + + {"cap", `var s [10]int; _ = cap(s)`, `invalid type`}, // constant + {"cap", `var s [10]int; _ = cap(&s)`, `invalid type`}, // constant + {"cap", `var s []int64; _ = cap(s)`, `func([]int64) int`}, + {"cap", `var c chan<-bool; _ = cap(c)`, `func(chan<- bool) int`}, + + {"len", `_ = len("foo")`, `invalid type`}, // constant + {"len", `var s string; _ = len(s)`, `func(string) int`}, + {"len", `var s [10]int; _ = len(s)`, `invalid type`}, // constant + {"len", `var s [10]int; _ = len(&s)`, `invalid type`}, // constant + {"len", `var s []int64; _ = len(s)`, `func([]int64) int`}, + {"len", `var c chan<-bool; _ = len(c)`, `func(chan<- bool) int`}, + {"len", `var m map[string]float32; _ = len(m)`, `func(map[string]float32) int`}, + + {"close", `var c chan int; close(c)`, `func(chan int)`}, + {"close", `var c chan<- chan string; close(c)`, `func(chan<- chan string)`}, + + {"complex", `_ = complex(1, 0)`, `invalid type`}, // constant + {"complex", `var re float32; _ = complex(re, 1.0)`, `func(float32, float32) complex64`}, + {"complex", `var im float64; _ = complex(1, im)`, `func(float64, float64) complex128`}, + {"complex", `type F32 float32; var re, im F32; _ = complex(re, im)`, `func(p.F32, p.F32) complex64`}, + {"complex", `type F64 float64; var re, im F64; _ = complex(re, im)`, `func(p.F64, p.F64) complex128`}, + + {"copy", `var src, dst []byte; copy(dst, src)`, `func([]byte, []byte) int`}, + {"copy", `type T [][]int; var src, dst T; _ = copy(dst, src)`, `func(p.T, p.T) int`}, + {"copy", `var src string; var dst []byte; copy(dst, src)`, `func([]byte, string) int`}, + {"copy", `type T string; type U []byte; var src T; var dst U; copy(dst, src)`, `func(p.U, p.T) int`}, + {"copy", `var dst []byte; copy(dst, "hello")`, `func([]byte, string) int`}, + + {"delete", `var m map[string]bool; delete(m, "foo")`, `func(map[string]bool, string)`}, + {"delete", `type (K string; V int); var m map[K]V; delete(m, "foo")`, `func(map[p.K]p.V, p.K)`}, + + {"imag", `_ = imag(1i)`, `invalid type`}, // constant + {"imag", `var c complex64; _ = imag(c)`, `func(complex64) float32`}, + {"imag", `var c complex128; _ = imag(c)`, `func(complex128) float64`}, + {"imag", `type C64 complex64; var c C64; _ = imag(c)`, `func(p.C64) float32`}, + {"imag", `type C128 complex128; var c C128; _ = imag(c)`, `func(p.C128) float64`}, + + {"real", `_ = real(1i)`, `invalid type`}, // constant + {"real", `var c complex64; _ = real(c)`, `func(complex64) float32`}, + {"real", `var c complex128; _ = real(c)`, `func(complex128) float64`}, + {"real", `type C64 complex64; var c C64; _ = real(c)`, `func(p.C64) float32`}, + {"real", `type C128 complex128; var c C128; _ = real(c)`, `func(p.C128) float64`}, + + {"make", `_ = make([]int, 10)`, `func([]int, int) []int`}, + {"make", `type T []byte; _ = make(T, 10, 20)`, `func(p.T, int, int) p.T`}, + + {"new", `_ = new(int)`, `func(int) *int`}, + {"new", `type T struct{}; _ = new(T)`, `func(p.T) *p.T`}, + + {"panic", `panic(0)`, `func(interface{})`}, + {"panic", `panic("foo")`, `func(interface{})`}, + + {"print", `print()`, `func()`}, + {"print", `print(0)`, `func(int)`}, + {"print", `print(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"println", `println()`, `func()`}, + {"println", `println(0)`, `func(int)`}, + {"println", `println(1, 2.0, "foo", true)`, `func(int, float64, string, bool)`}, + + {"recover", `recover()`, `func() interface{}`}, + {"recover", `_ = recover()`, `func() interface{}`}, + + {"Alignof", `_ = unsafe.Alignof(0)`, `invalid type`}, // constant + {"Alignof", `var x struct{}; _ = unsafe.Alignof(x)`, `invalid type`}, // constant + + {"Offsetof", `var x struct{f bool}; _ = unsafe.Offsetof(x.f)`, `invalid type`}, // constant + {"Offsetof", `var x struct{_ int; f bool}; _ = unsafe.Offsetof((&x).f)`, `invalid type`}, // constant + + {"Sizeof", `_ = unsafe.Sizeof(0)`, `invalid type`}, // constant + {"Sizeof", `var x struct{}; _ = unsafe.Sizeof(x)`, `invalid type`}, // constant + + {"assert", `assert(true)`, `invalid type`}, // constant + {"assert", `type B bool; const pred B = 1 < 2; assert(pred)`, `invalid type`}, // constant + + // no tests for trace since it produces output as a side-effect +} + +func TestBuiltinSignatures(t *testing.T) { + DefPredeclaredTestFuncs() + + seen := map[string]bool{"trace": true} // no test for trace built-in; add it manually + for _, call := range builtinCalls { + testBuiltinSignature(t, call.name, call.src, call.sig) + seen[call.name] = true + } + + // make sure we didn't miss one + for _, name := range Universe.Names() { + if _, ok := Universe.Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for %s", name) + } + } + for _, name := range Unsafe.Scope().Names() { + if _, ok := Unsafe.Scope().Lookup(name).(*Builtin); ok && !seen[name] { + t.Errorf("missing test for unsafe.%s", name) + } + } +} + +func testBuiltinSignature(t *testing.T, name, src0, want string) { + src := fmt.Sprintf(`package p; import "unsafe"; type _ unsafe.Pointer /* use unsafe */; func _() { %s }`, src0) + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Errorf("%s: %s", src0, err) + return + } + + conf := Config{Importer: importer.Default()} + uses := make(map[*ast.Ident]Object) + types := make(map[ast.Expr]TypeAndValue) + _, 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 + } + + // find called function + n := 0 + var fun ast.Expr + for x := range types { + if call, _ := x.(*ast.CallExpr); call != nil { + fun = call.Fun + n++ + } + } + if n != 1 { + t.Errorf("%s: got %d CallExprs; want 1", src0, n) + return + } + + // check recorded types for fun and descendents (may be parenthesized) + for { + // the recorded type for the built-in must match the wanted signature + typ := types[fun].Type + if typ == nil { + t.Errorf("%s: no type recorded for %s", src0, ExprString(fun)) + return + } + if got := typ.String(); got != want { + t.Errorf("%s: got type %s; want %s", src0, got, want) + return + } + + // called function must be a (possibly parenthesized, qualified) + // identifier denoting the expected built-in + switch p := fun.(type) { + case *ast.Ident: + obj := uses[p] + if obj == nil { + t.Errorf("%s: no object found for %s", src0, p) + return + } + bin, _ := obj.(*Builtin) + if bin == nil { + t.Errorf("%s: %s does not denote a built-in", src0, p) + return + } + if bin.Name() != name { + t.Errorf("%s: got built-in %s; want %s", src0, bin.Name(), name) + return + } + return // we're done + + case *ast.ParenExpr: + fun = p.X // unpack + + case *ast.SelectorExpr: + // built-in from package unsafe - ignore details + return // we're done + + default: + t.Errorf("%s: invalid function call", src0) + return + } + } +} diff --git a/libgo/go/go/types/call.go b/libgo/go/go/types/call.go new file mode 100644 index 00000000000..62cefc047ef --- /dev/null +++ b/libgo/go/go/types/call.go @@ -0,0 +1,441 @@ +// Copyright 2013 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 typechecking of call and selector expressions. + +package types + +import ( + "go/ast" + "go/token" +) + +func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind { + check.exprOrType(x, e.Fun) + + switch x.mode { + case invalid: + check.use(e.Args...) + x.mode = invalid + x.expr = e + return statement + + case typexpr: + // conversion + T := x.typ + x.mode = invalid + switch n := len(e.Args); n { + case 0: + check.errorf(e.Rparen, "missing argument in conversion to %s", T) + case 1: + check.expr(x, e.Args[0]) + if x.mode != invalid { + check.conversion(x, T) + } + default: + check.errorf(e.Args[n-1].Pos(), "too many arguments in conversion to %s", T) + } + x.expr = e + return conversion + + case builtin: + id := x.id + if !check.builtin(x, e, id) { + x.mode = invalid + } + x.expr = e + // a non-constant result implies a function call + if x.mode != invalid && x.mode != constant_ { + check.hasCallOrRecv = true + } + return predeclaredFuncs[id].kind + + default: + // function/method call + sig, _ := x.typ.Underlying().(*Signature) + if sig == nil { + check.invalidOp(x.pos(), "cannot call non-function %s", x) + x.mode = invalid + x.expr = e + return statement + } + + arg, n, _ := unpack(func(x *operand, i int) { check.expr(x, e.Args[i]) }, len(e.Args), false) + if arg == nil { + x.mode = invalid + x.expr = e + return statement + } + + check.arguments(x, e, sig, arg, n) + + // determine result + switch sig.results.Len() { + case 0: + x.mode = novalue + case 1: + x.mode = value + x.typ = sig.results.vars[0].typ // unpack tuple + default: + x.mode = value + x.typ = sig.results + } + x.expr = e + check.hasCallOrRecv = true + + return statement + } +} + +// use type-checks each argument. +// Useful to make sure expressions are evaluated +// (and variables are "used") in the presence of other errors. +func (check *Checker) use(arg ...ast.Expr) { + var x operand + for _, e := range arg { + check.rawExpr(&x, e, nil) + } +} + +// useGetter is like use, but takes a getter instead of a list of expressions. +// It should be called instead of use if a getter is present to avoid repeated +// evaluation of the first argument (since the getter was likely obtained via +// unpack, which may have evaluated the first argument already). +func (check *Checker) useGetter(get getter, n int) { + var x operand + for i := 0; i < n; i++ { + get(&x, i) + } +} + +// A getter sets x as the i'th operand, where 0 <= i < n and n is the total +// number of operands (context-specific, and maintained elsewhere). A getter +// type-checks the i'th operand; the details of the actual check are getter- +// specific. +type getter func(x *operand, i int) + +// unpack takes a getter get and a number of operands n. If n == 1, unpack +// calls the incoming getter for the first operand. If that operand is +// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a +// function call, or a comma-ok expression and allowCommaOk is set, the result +// is a new getter and operand count providing access to the function results, +// or comma-ok values, respectively. The third result value reports if it +// is indeed the comma-ok case. In all other cases, the incoming getter and +// operand count are returned unchanged, and the third result value is false. +// +// In other words, if there's exactly one operand that - after type-checking +// by calling get - stands for multiple operands, the resulting getter provides +// access to those operands instead. +// +// If the returned getter is called at most once for a given operand index i +// (including i == 0), that operand is guaranteed to cause only one call of +// the incoming getter with that i. +// +func unpack(get getter, n int, allowCommaOk bool) (getter, int, bool) { + if n == 1 { + // possibly result of an n-valued function call or comma,ok value + var x0 operand + get(&x0, 0) + if x0.mode == invalid { + return nil, 0, false + } + + if t, ok := x0.typ.(*Tuple); ok { + // result of an n-valued function call + return func(x *operand, i int) { + x.mode = value + x.expr = x0.expr + x.typ = t.At(i).typ + }, t.Len(), false + } + + if x0.mode == mapindex || x0.mode == commaok { + // comma-ok value + if allowCommaOk { + a := [2]Type{x0.typ, Typ[UntypedBool]} + return func(x *operand, i int) { + x.mode = value + x.expr = x0.expr + x.typ = a[i] + }, 2, true + } + x0.mode = value + } + + // single value + return func(x *operand, i int) { + if i != 0 { + unreachable() + } + *x = x0 + }, 1, false + } + + // zero or multiple values + return get, n, false +} + +// arguments checks argument passing for the call with the given signature. +// The arg function provides the operand for the i'th argument. +func (check *Checker) arguments(x *operand, call *ast.CallExpr, sig *Signature, arg getter, n int) { + if call.Ellipsis.IsValid() { + // last argument is of the form x... + if len(call.Args) == 1 && n > 1 { + // f()... is not permitted if f() is multi-valued + check.errorf(call.Ellipsis, "cannot use ... with %d-valued expression %s", n, call.Args[0]) + check.useGetter(arg, n) + return + } + if !sig.variadic { + check.errorf(call.Ellipsis, "cannot use ... in call to non-variadic %s", call.Fun) + check.useGetter(arg, n) + return + } + } + + // evaluate arguments + for i := 0; i < n; i++ { + arg(x, i) + if x.mode != invalid { + var ellipsis token.Pos + if i == n-1 && call.Ellipsis.IsValid() { + ellipsis = call.Ellipsis + } + check.argument(sig, i, x, ellipsis) + } + } + + // check argument count + if sig.variadic { + // a variadic function accepts an "empty" + // last argument: count one extra + n++ + } + if n < sig.params.Len() { + check.errorf(call.Rparen, "too few arguments in call to %s", call.Fun) + // ok to continue + } +} + +// argument checks passing of argument x to the i'th parameter of the given signature. +// If ellipsis is valid, the argument is followed by ... at that position in the call. +func (check *Checker) argument(sig *Signature, i int, x *operand, ellipsis token.Pos) { + n := sig.params.Len() + + // determine parameter type + var typ Type + switch { + case i < n: + typ = sig.params.vars[i].typ + case sig.variadic: + typ = sig.params.vars[n-1].typ + if debug { + if _, ok := typ.(*Slice); !ok { + check.dump("%s: expected unnamed slice type, got %s", sig.params.vars[n-1].Pos(), typ) + } + } + default: + check.errorf(x.pos(), "too many arguments") + return + } + + if ellipsis.IsValid() { + // argument is of the form x... + if i != n-1 { + check.errorf(ellipsis, "can only use ... with matching parameter") + return + } + switch t := x.typ.Underlying().(type) { + case *Slice: + // ok + case *Tuple: + check.errorf(ellipsis, "cannot use ... with %d-valued expression %s", t.Len(), x) + return + default: + check.errorf(x.pos(), "cannot use %s as parameter of type %s", x, typ) + return + } + } else if sig.variadic && i >= n-1 { + // use the variadic parameter slice's element type + typ = typ.(*Slice).elem + } + + if !check.assignment(x, typ) && x.mode != invalid { + check.errorf(x.pos(), "cannot pass argument %s to parameter of type %s", x, typ) + } +} + +func (check *Checker) selector(x *operand, e *ast.SelectorExpr) { + // these must be declared before the "goto Error" statements + var ( + obj Object + index []int + indirect bool + ) + + sel := e.Sel.Name + // If the identifier refers to a package, handle everything here + // so we don't need a "package" mode for operands: package names + // can only appear in qualified identifiers which are mapped to + // selector expressions. + if ident, ok := e.X.(*ast.Ident); ok { + _, obj := check.scope.LookupParent(ident.Name, check.pos) + if pkg, _ := obj.(*PkgName); pkg != nil { + assert(pkg.pkg == check.pkg) + check.recordUse(ident, pkg) + pkg.used = true + exp := pkg.imported.scope.Lookup(sel) + if exp == nil { + if !pkg.imported.fake { + check.errorf(e.Pos(), "%s not declared by package %s", sel, ident) + } + goto Error + } + if !exp.Exported() { + check.errorf(e.Pos(), "%s not exported by package %s", sel, ident) + // ok to continue + } + check.recordUse(e.Sel, exp) + // Simplified version of the code for *ast.Idents: + // - imported objects are always fully initialized + switch exp := exp.(type) { + case *Const: + assert(exp.Val() != nil) + x.mode = constant_ + x.typ = exp.typ + x.val = exp.val + case *TypeName: + x.mode = typexpr + x.typ = exp.typ + case *Var: + x.mode = variable + x.typ = exp.typ + case *Func: + x.mode = value + x.typ = exp.typ + case *Builtin: + x.mode = builtin + x.typ = exp.typ + x.id = exp.id + default: + unreachable() + } + x.expr = e + return + } + } + + check.exprOrType(x, e.X) + if x.mode == invalid { + goto Error + } + + obj, index, indirect = LookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel) + if obj == nil { + switch { + case index != nil: + // TODO(gri) should provide actual type where the conflict happens + check.invalidOp(e.Pos(), "ambiguous selector %s", sel) + case indirect: + check.invalidOp(e.Pos(), "%s is not in method set of %s", sel, x.typ) + default: + check.invalidOp(e.Pos(), "%s has no field or method %s", x, sel) + } + goto Error + } + + if x.mode == typexpr { + // method expression + m, _ := obj.(*Func) + if m == nil { + check.invalidOp(e.Pos(), "%s has no method %s", x, sel) + goto Error + } + + check.recordSelection(e, MethodExpr, x.typ, m, index, indirect) + + // the receiver type becomes the type of the first function + // argument of the method expression's function type + var params []*Var + sig := m.typ.(*Signature) + if sig.params != nil { + params = sig.params.vars + } + x.mode = value + x.typ = &Signature{ + params: NewTuple(append([]*Var{NewVar(token.NoPos, check.pkg, "", x.typ)}, params...)...), + results: sig.results, + variadic: sig.variadic, + } + + check.addDeclDep(m) + + } else { + // regular selector + switch obj := obj.(type) { + case *Var: + check.recordSelection(e, FieldVal, x.typ, obj, index, indirect) + if x.mode == variable || indirect { + x.mode = variable + } else { + x.mode = value + } + x.typ = obj.typ + + case *Func: + // TODO(gri) If we needed to take into account the receiver's + // addressability, should we report the type &(x.typ) instead? + check.recordSelection(e, MethodVal, x.typ, obj, index, indirect) + + if debug { + // Verify that LookupFieldOrMethod and MethodSet.Lookup agree. + typ := x.typ + if x.mode == variable { + // If typ is not an (unnamed) pointer or an interface, + // use *typ instead, because the method set of *typ + // includes the methods of typ. + // Variables are addressable, so we can always take their + // address. + if _, ok := typ.(*Pointer); !ok && !IsInterface(typ) { + typ = &Pointer{base: typ} + } + } + // If we created a synthetic pointer type above, we will throw + // away the method set computed here after use. + // TODO(gri) Method set computation should probably always compute + // both, the value and the pointer receiver method set and represent + // them in a single structure. + // TODO(gri) Consider also using a method set cache for the lifetime + // of checker once we rely on MethodSet lookup instead of individual + // lookup. + mset := NewMethodSet(typ) + if m := mset.Lookup(check.pkg, sel); m == nil || m.obj != obj { + check.dump("%s: (%s).%v -> %s", e.Pos(), typ, obj.name, m) + check.dump("%s\n", mset) + panic("method sets and lookup don't agree") + } + } + + x.mode = value + + // remove receiver + sig := *obj.typ.(*Signature) + sig.recv = nil + x.typ = &sig + + check.addDeclDep(obj) + + default: + unreachable() + } + } + + // everything went well + x.expr = e + return + +Error: + x.mode = invalid + x.expr = e +} diff --git a/libgo/go/go/types/check.go b/libgo/go/go/types/check.go new file mode 100644 index 00000000000..bb0b07415e8 --- /dev/null +++ b/libgo/go/go/types/check.go @@ -0,0 +1,358 @@ +// 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 the Check function, which drives type-checking. + +package types + +import ( + "go/ast" + "go/constant" + "go/token" +) + +// debugging/development support +const ( + debug = false // leave on during development + trace = false // turn on for detailed type resolution traces +) + +// If Strict is set, the type-checker enforces additional +// rules not specified by the Go 1 spec, but which will +// catch guaranteed run-time errors if the respective +// code is executed. In other words, programs passing in +// Strict mode are Go 1 compliant, but not all Go 1 programs +// will pass in Strict mode. The additional rules are: +// +// - A type assertion x.(T) where T is an interface type +// is invalid if any (statically known) method that exists +// for both x and T have different signatures. +// +const strict = false + +// exprInfo stores information about an untyped expression. +type exprInfo struct { + isLhs bool // expression is lhs operand of a shift with delayed type-check + mode operandMode + typ *Basic + val constant.Value // constant value; or nil (if not a constant) +} + +// funcInfo stores the information required for type-checking a function. +type funcInfo struct { + name string // for debugging/tracing only + decl *declInfo // for cycle detection + sig *Signature + body *ast.BlockStmt +} + +// A context represents the context within which an object is type-checked. +type context struct { + decl *declInfo // package-level declaration whose init expression/function body is checked + scope *Scope // top-most scope for lookups + iota constant.Value // value of iota in a constant declaration; nil otherwise + sig *Signature // function signature if inside a function; nil otherwise + hasLabel bool // set if a function makes use of labels (only ~1% of functions); unused outside functions + hasCallOrRecv bool // set if an expression contains a function call or channel receive operation +} + +// A Checker maintains the state of the type checker. +// It must be created with NewChecker. +type Checker struct { + // package information + // (initialized by NewChecker, valid for the life-time of checker) + conf *Config + fset *token.FileSet + pkg *Package + *Info + objMap map[Object]*declInfo // maps package-level object to declaration info + + // information collected during type-checking of a set of package files + // (initialized by Files, valid only for the duration of check.Files; + // maps and lists are allocated on demand) + files []*ast.File // package files + unusedDotImports map[*Scope]map[*Package]token.Pos // positions of unused dot-imported packages for each file scope + + firstErr error // first error encountered + methods map[string][]*Func // maps type names to associated methods + untyped map[ast.Expr]exprInfo // map of expressions without final type + funcs []funcInfo // list of functions to type-check + delayed []func() // delayed checks requiring fully setup types + + // context within which the current object is type-checked + // (valid only for the duration of type-checking a specific object) + context + pos token.Pos // if valid, identifiers are looked up as if at position pos (used by Eval) + + // debugging + indent int // indentation for tracing +} + +// addUnusedImport adds the position of a dot-imported package +// pkg to the map of dot imports for the given file scope. +func (check *Checker) addUnusedDotImport(scope *Scope, pkg *Package, pos token.Pos) { + mm := check.unusedDotImports + if mm == nil { + mm = make(map[*Scope]map[*Package]token.Pos) + check.unusedDotImports = mm + } + m := mm[scope] + if m == nil { + m = make(map[*Package]token.Pos) + mm[scope] = m + } + m[pkg] = pos +} + +// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists +func (check *Checker) addDeclDep(to Object) { + from := check.decl + if from == nil { + return // not in a package-level init expression + } + if _, found := check.objMap[to]; !found { + return // to is not a package-level object + } + from.addDep(to) +} + +func (check *Checker) assocMethod(tname string, meth *Func) { + m := check.methods + if m == nil { + m = make(map[string][]*Func) + check.methods = m + } + m[tname] = append(m[tname], meth) +} + +func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, mode operandMode, typ *Basic, val constant.Value) { + m := check.untyped + if m == nil { + m = make(map[ast.Expr]exprInfo) + check.untyped = m + } + m[e] = exprInfo{lhs, mode, typ, val} +} + +func (check *Checker) later(name string, decl *declInfo, sig *Signature, body *ast.BlockStmt) { + check.funcs = append(check.funcs, funcInfo{name, decl, sig, body}) +} + +func (check *Checker) delay(f func()) { + check.delayed = append(check.delayed, f) +} + +// NewChecker returns a new Checker instance for a given package. +// Package files may be added incrementally via checker.Files. +func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker { + // make sure we have a configuration + if conf == nil { + conf = new(Config) + } + + // make sure we have an info struct + if info == nil { + info = new(Info) + } + + return &Checker{ + conf: conf, + fset: fset, + pkg: pkg, + Info: info, + objMap: make(map[Object]*declInfo), + } +} + +// initFiles initializes the files-specific portion of checker. +// The provided files must all belong to the same package. +func (check *Checker) initFiles(files []*ast.File) { + // start with a clean slate (check.Files may be called multiple times) + check.files = nil + check.unusedDotImports = nil + + check.firstErr = nil + check.methods = nil + check.untyped = nil + check.funcs = nil + check.delayed = nil + + // determine package name and collect valid files + pkg := check.pkg + for _, file := range files { + switch name := file.Name.Name; pkg.name { + case "": + if name != "_" { + pkg.name = name + } else { + check.errorf(file.Name.Pos(), "invalid package name _") + } + fallthrough + + case name: + check.files = append(check.files, file) + + default: + check.errorf(file.Package, "package %s; expected %s", name, pkg.name) + // ignore this file + } + } +} + +// A bailout panic is used for early termination. +type bailout struct{} + +func (check *Checker) handleBailout(err *error) { + switch p := recover().(type) { + case nil, bailout: + // normal return or early exit + *err = check.firstErr + default: + // re-panic + panic(p) + } +} + +// Files checks the provided files as part of the checker's package. +func (check *Checker) Files(files []*ast.File) (err error) { + defer check.handleBailout(&err) + + check.initFiles(files) + + check.collectObjects() + + check.packageObjects(check.resolveOrder()) + + check.functionBodies() + + check.initOrder() + + if !check.conf.DisableUnusedImportCheck { + check.unusedImports() + } + + // perform delayed checks + for _, f := range check.delayed { + f() + } + + check.recordUntyped() + + check.pkg.complete = true + return +} + +func (check *Checker) recordUntyped() { + if !debug && check.Types == nil { + return // nothing to do + } + + for x, info := range check.untyped { + if debug && isTyped(info.typ) { + check.dump("%s: %s (type %s) is typed", x.Pos(), x, info.typ) + unreachable() + } + check.recordTypeAndValue(x, info.mode, info.typ, info.val) + } +} + +func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val constant.Value) { + assert(x != nil) + assert(typ != nil) + if mode == invalid { + return // omit + } + assert(typ != nil) + if mode == constant_ { + assert(val != nil) + assert(typ == Typ[Invalid] || isConstType(typ)) + } + if m := check.Types; m != nil { + m[x] = TypeAndValue{mode, typ, val} + } +} + +func (check *Checker) recordBuiltinType(f ast.Expr, sig *Signature) { + // f must be a (possibly parenthesized) identifier denoting a built-in + // (built-ins in package unsafe always produce a constant result and + // we don't record their signatures, so we don't see qualified idents + // here): record the signature for f and possible children. + for { + check.recordTypeAndValue(f, builtin, sig, nil) + switch p := f.(type) { + case *ast.Ident: + return // we're done + case *ast.ParenExpr: + f = p.X + default: + unreachable() + } + } +} + +func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) { + assert(x != nil) + if a[0] == nil || a[1] == nil { + return + } + assert(isTyped(a[0]) && isTyped(a[1]) && isBoolean(a[1])) + if m := check.Types; m != nil { + for { + tv := m[x] + assert(tv.Type != nil) // should have been recorded already + pos := x.Pos() + tv.Type = NewTuple( + NewVar(pos, check.pkg, "", a[0]), + NewVar(pos, check.pkg, "", a[1]), + ) + m[x] = tv + // if x is a parenthesized expression (p.X), update p.X + p, _ := x.(*ast.ParenExpr) + if p == nil { + break + } + x = p.X + } + } +} + +func (check *Checker) recordDef(id *ast.Ident, obj Object) { + assert(id != 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 + } +} + +func (check *Checker) recordImplicit(node ast.Node, obj Object) { + assert(node != nil) + assert(obj != nil) + if m := check.Implicits; m != nil { + m[node] = obj + } +} + +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.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} + } +} + +func (check *Checker) recordScope(node ast.Node, scope *Scope) { + assert(node != nil) + assert(scope != nil) + if m := check.Scopes; m != nil { + m[node] = scope + } +} diff --git a/libgo/go/go/types/check_test.go b/libgo/go/go/types/check_test.go new file mode 100644 index 00000000000..5e34c65b636 --- /dev/null +++ b/libgo/go/go/types/check_test.go @@ -0,0 +1,298 @@ +// 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 +// } + +// TODO(gri) Also collect strict mode errors of the form /* STRICT ... */ +// and test against strict mode. + +package types_test + +import ( + "flag" + "go/ast" + "go/importer" + "go/parser" + "go/scanner" + "go/token" + "internal/testenv" + "io/ioutil" + "regexp" + "strings" + "testing" + + . "go/types" +) + +var ( + listErrors = flag.Bool("list", false, "list errors") + testFiles = flag.String("files", "", "space-separated list of test files") +) + +// 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. + +// Each tests entry is list of files belonging to the same package. +var tests = [][]string{ + {"testdata/errors.src"}, + {"testdata/importdecl0a.src", "testdata/importdecl0b.src"}, + {"testdata/importdecl1a.src", "testdata/importdecl1b.src"}, + {"testdata/cycles.src"}, + {"testdata/cycles1.src"}, + {"testdata/cycles2.src"}, + {"testdata/cycles3.src"}, + {"testdata/cycles4.src"}, + {"testdata/init0.src"}, + {"testdata/init1.src"}, + {"testdata/init2.src"}, + {"testdata/decls0.src"}, + {"testdata/decls1.src"}, + {"testdata/decls2a.src", "testdata/decls2b.src"}, + {"testdata/decls3.src"}, + {"testdata/const0.src"}, + {"testdata/const1.src"}, + {"testdata/constdecl.src"}, + {"testdata/vardecl.src"}, + {"testdata/expr0.src"}, + {"testdata/expr1.src"}, + {"testdata/expr2.src"}, + {"testdata/expr3.src"}, + {"testdata/methodsets.src"}, + {"testdata/shifts.src"}, + {"testdata/builtins.src"}, + {"testdata/conversions.src"}, + {"testdata/stmt0.src"}, + {"testdata/stmt1.src"}, + {"testdata/gotos.src"}, + {"testdata/labels.src"}, + {"testdata/issues.src"}, + {"testdata/blank.src"}, +} + +var fset = token.NewFileSet() + +// Positioned errors are of the form filename:line:column: message . +var posMsgRx = regexp.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`) + +// splitError splits an error's error message into a position string +// and the actual error message. If there's no position information, +// pos is the empty string, and msg is the entire error message. +// +func splitError(err error) (pos, msg string) { + msg = err.Error() + if m := posMsgRx.FindStringSubmatch(msg); len(m) == 3 { + pos = m[1] + msg = m[2] + } + return +} + +func parseFiles(t *testing.T, filenames []string) ([]*ast.File, []error) { + var files []*ast.File + var errlist []error + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if file == nil { + t.Fatalf("%s: %s", filename, err) + } + files = append(files, file) + if err != nil { + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + errlist = append(errlist, err) + } + } else { + errlist = append(errlist, err) + } + } + } + return files, errlist +} + +// ERROR comments must start with text `ERROR "rx"` or `ERROR rx` where +// rx is a regular expression that matches the expected error message. +// Space around "rx" or rx is ignored. Use the form `ERROR HERE "rx"` +// for error messages that are located immediately after rather than +// at a token's position. +// +var errRx = regexp.MustCompile(`^ *ERROR *(HERE)? *"?([^"]*)"?`) + +// errMap collects the regular expressions of ERROR comments found +// in files and returns them as a map of error positions to error messages. +// +func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string { + // map of position strings to lists of error message patterns + errmap := make(map[string][]string) + + for _, file := range files { + filename := fset.Position(file.Package).Filename + src, err := ioutil.ReadFile(filename) + if err != nil { + t.Fatalf("%s: could not read %s", testname, filename) + } + + var s scanner.Scanner + s.Init(fset.AddFile(filename, -1, len(src)), src, nil, scanner.ScanComments) + var prev token.Pos // position of last non-comment, non-semicolon token + var here token.Pos // position immediately after the token at position prev + + scanFile: + for { + pos, tok, lit := s.Scan() + switch tok { + case token.EOF: + break scanFile + case token.COMMENT: + if lit[1] == '*' { + lit = lit[:len(lit)-2] // strip trailing */ + } + if s := errRx.FindStringSubmatch(lit[2:]); len(s) == 3 { + pos := prev + if s[1] == "HERE" { + pos = here + } + p := fset.Position(pos).String() + errmap[p] = append(errmap[p], strings.TrimSpace(s[2])) + } + case token.SEMICOLON: + // ignore automatically inserted semicolon + if lit == "\n" { + continue scanFile + } + fallthrough + default: + prev = pos + var l int // token length + if tok.IsLiteral() { + l = len(lit) + } else { + l = len(tok.String()) + } + here = prev + token.Pos(l) + } + } + } + + return errmap +} + +func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { + for _, err := range errlist { + pos, gotMsg := splitError(err) + list := errmap[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, wantRx := range list { + rx, err := regexp.Compile(wantRx) + if err != nil { + t.Errorf("%s: %v", pos, err) + continue + } + if rx.MatchString(gotMsg) { + index = i + break + } + } + if index >= 0 { + // eliminate from list + if n := len(list) - 1; n > 0 { + // not the last entry - swap in last element and shorten list by 1 + list[index] = list[n] + errmap[pos] = list[:n] + } else { + // last entry - remove list from map + delete(errmap, pos) + } + } else { + t.Errorf("%s: no error expected: %q", pos, gotMsg) + } + } +} + +func checkFiles(t *testing.T, testfiles []string) { + // parse files and collect parser errors + files, errlist := parseFiles(t, testfiles) + + pkgName := "<no package>" + if len(files) > 0 { + pkgName = files[0].Name.Name + } + + if *listErrors && len(errlist) > 0 { + t.Errorf("--- %s:", pkgName) + for _, err := range errlist { + t.Error(err) + } + } + + // typecheck and collect typechecker errors + var conf Config + conf.Importer = importer.Default() + conf.Error = func(err error) { + if *listErrors { + t.Error(err) + return + } + // Ignore secondary error messages starting with "\t"; + // they are clarifying messages for a primary error. + if !strings.Contains(err.Error(), ": \t") { + errlist = append(errlist, err) + } + } + conf.Check(pkgName, fset, files, nil) + + if *listErrors { + return + } + + // match and eliminate errors; + // we are expecting the following errors + errmap := errMap(t, pkgName, files) + eliminate(t, errmap, errlist) + + // there should be no expected errors left + if len(errmap) > 0 { + t.Errorf("--- %s: %d source positions with expected (but not reported) errors:", pkgName, len(errmap)) + for pos, list := range errmap { + for _, rx := range list { + t.Errorf("%s: %q", pos, rx) + } + } + } +} + +func TestCheck(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // Declare builtins for testing. + DefPredeclaredTestFuncs() + + // If explicit test files are specified, only check those. + if files := *testFiles; files != "" { + checkFiles(t, strings.Split(files, " ")) + return + } + + // Otherwise, run all the tests. + for _, files := range tests { + checkFiles(t, files) + } +} diff --git a/libgo/go/go/types/conversions.go b/libgo/go/go/types/conversions.go new file mode 100644 index 00000000000..74826ce9345 --- /dev/null +++ b/libgo/go/go/types/conversions.go @@ -0,0 +1,146 @@ +// 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 implements typechecking of conversions. + +package types + +import "go/constant" + +// Conversion type-checks the conversion T(x). +// The result is in x. +func (check *Checker) conversion(x *operand, T Type) { + constArg := x.mode == constant_ + + var ok bool + switch { + case constArg && isConstType(T): + // constant conversion + switch t := T.Underlying().(*Basic); { + case representableConst(x.val, check.conf, t.kind, &x.val): + ok = true + case isInteger(x.typ) && isString(t): + codepoint := int64(-1) + if i, ok := constant.Int64Val(x.val); ok { + codepoint = i + } + // If codepoint < 0 the absolute value is too large (or unknown) for + // conversion. This is the same as converting any other out-of-range + // value - let string(codepoint) do the work. + x.val = constant.MakeString(string(codepoint)) + ok = true + } + case x.convertibleTo(check.conf, T): + // non-constant conversion + x.mode = value + ok = true + } + + if !ok { + check.errorf(x.pos(), "cannot convert %s to %s", x, T) + x.mode = invalid + return + } + + // The conversion argument types are final. For untyped values the + // conversion provides the type, per the spec: "A constant may be + // given a type explicitly by a constant declaration or conversion,...". + final := x.typ + if isUntyped(x.typ) { + final = T + // - For conversions to interfaces, use the argument's default type. + // - For conversions of untyped constants to non-constant types, also + // use the default type (e.g., []byte("foo") should report string + // not []byte as type for the constant "foo"). + // - Keep untyped nil for untyped nil arguments. + if IsInterface(T) || constArg && !isConstType(T) { + final = defaultType(x.typ) + } + check.updateExprType(x.expr, final, true) + } + + x.typ = T +} + +func (x *operand) convertibleTo(conf *Config, T Type) bool { + // "x is assignable to T" + if x.assignableTo(conf, T) { + return true + } + + // "x's type and T have identical underlying types" + V := x.typ + Vu := V.Underlying() + Tu := T.Underlying() + if Identical(Vu, Tu) { + return true + } + + // "x's type and T are unnamed pointer types and their pointer base types have identical underlying types" + if V, ok := V.(*Pointer); ok { + if T, ok := T.(*Pointer); ok { + if Identical(V.base.Underlying(), T.base.Underlying()) { + return true + } + } + } + + // "x's type and T are both integer or floating point types" + if (isInteger(V) || isFloat(V)) && (isInteger(T) || isFloat(T)) { + return true + } + + // "x's type and T are both complex types" + if isComplex(V) && isComplex(T) { + return true + } + + // "x is an integer or a slice of bytes or runes and T is a string type" + if (isInteger(V) || isBytesOrRunes(Vu)) && isString(T) { + return true + } + + // "x is a string and T is a slice of bytes or runes" + if isString(V) && isBytesOrRunes(Tu) { + return true + } + + // package unsafe: + // "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer" + if (isPointer(Vu) || isUintptr(Vu)) && isUnsafePointer(T) { + return true + } + // "and vice versa" + if isUnsafePointer(V) && (isPointer(Tu) || isUintptr(Tu)) { + return true + } + + return false +} + +func isUintptr(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.kind == Uintptr +} + +func isUnsafePointer(typ Type) bool { + // TODO(gri): Is this (typ.Underlying() instead of just typ) correct? + // The spec does not say so, but gc claims it is. See also + // issue 6326. + t, ok := typ.Underlying().(*Basic) + return ok && t.kind == UnsafePointer +} + +func isPointer(typ Type) bool { + _, ok := typ.Underlying().(*Pointer) + return ok +} + +func isBytesOrRunes(typ Type) bool { + if s, ok := typ.(*Slice); ok { + t, ok := s.elem.Underlying().(*Basic) + return ok && (t.kind == Byte || t.kind == Rune) + } + return false +} diff --git a/libgo/go/go/types/decl.go b/libgo/go/go/types/decl.go new file mode 100644 index 00000000000..8e9e5f36de4 --- /dev/null +++ b/libgo/go/go/types/decl.go @@ -0,0 +1,430 @@ +// Copyright 2014 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 ( + "go/ast" + "go/constant" + "go/token" +) + +func (check *Checker) reportAltDecl(obj Object) { + if pos := obj.Pos(); pos.IsValid() { + // We use "other" rather than "previous" here because + // the first declaration seen may not be textually + // earlier in the source. + check.errorf(pos, "\tother declaration of %s", obj.Name()) // secondary error, \t indented + } +} + +func (check *Checker) declare(scope *Scope, id *ast.Ident, obj Object, pos token.Pos) { + // spec: "The blank identifier, represented by the underscore + // character _, may be used in a declaration like any other + // identifier but the declaration does not introduce a new + // binding." + if obj.Name() != "_" { + if alt := scope.Insert(obj); alt != nil { + check.errorf(obj.Pos(), "%s redeclared in this block", obj.Name()) + check.reportAltDecl(alt) + return + } + obj.setScopePos(pos) + } + if id != nil { + check.recordDef(id, obj) + } +} + +// objDecl type-checks the declaration of obj in its respective (file) context. +// See check.typ for the details on def and path. +func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) { + if obj.Type() != nil { + return // already checked - nothing to do + } + + if trace { + check.trace(obj.Pos(), "-- declaring %s", obj.Name()) + check.indent++ + defer func() { + check.indent-- + check.trace(obj.Pos(), "=> %s", obj) + }() + } + + d := check.objMap[obj] + if d == nil { + check.dump("%s: %s should have been declared", obj.Pos(), obj.Name()) + unreachable() + } + + // save/restore current context and setup object context + defer func(ctxt context) { + check.context = ctxt + }(check.context) + check.context = context{ + scope: d.file, + } + + // Const and var declarations must not have initialization + // cycles. We track them by remembering the current declaration + // in check.decl. Initialization expressions depending on other + // consts, vars, or functions, add dependencies to the current + // check.decl. + switch obj := obj.(type) { + case *Const: + check.decl = d // new package-level const decl + check.constDecl(obj, d.typ, d.init) + case *Var: + check.decl = d // new package-level var decl + check.varDecl(obj, d.lhs, d.typ, d.init) + case *TypeName: + // invalid recursive types are detected via path + check.typeDecl(obj, d.typ, def, path) + case *Func: + // functions may be recursive - no need to track dependencies + check.funcDecl(obj, d) + default: + unreachable() + } +} + +func (check *Checker) constDecl(obj *Const, typ, init ast.Expr) { + assert(obj.typ == nil) + + if obj.visited { + obj.typ = Typ[Invalid] + return + } + obj.visited = true + + // use the correct value of iota + assert(check.iota == nil) + check.iota = obj.val + defer func() { check.iota = nil }() + + // provide valid constant value under all circumstances + obj.val = constant.MakeUnknown() + + // determine type, if any + if typ != nil { + t := check.typ(typ) + if !isConstType(t) { + check.errorf(typ.Pos(), "invalid constant type %s", t) + obj.typ = Typ[Invalid] + return + } + obj.typ = t + } + + // check initialization + var x operand + if init != nil { + check.expr(&x, init) + } + check.initConst(obj, &x) +} + +func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init ast.Expr) { + assert(obj.typ == nil) + + if obj.visited { + obj.typ = Typ[Invalid] + return + } + obj.visited = true + + // var declarations cannot use iota + assert(check.iota == nil) + + // determine type, if any + if typ != nil { + obj.typ = check.typ(typ) + } + + // check initialization + if init == nil { + if typ == nil { + // error reported before by arityMatch + obj.typ = Typ[Invalid] + } + return + } + + if lhs == nil || len(lhs) == 1 { + assert(lhs == nil || lhs[0] == obj) + var x operand + check.expr(&x, init) + check.initVar(obj, &x, false) + return + } + + if debug { + // obj must be one of lhs + found := false + for _, lhs := range lhs { + if obj == lhs { + found = true + break + } + } + if !found { + panic("inconsistent lhs") + } + } + check.initVars(lhs, []ast.Expr{init}, token.NoPos) +} + +// underlying returns the underlying type of typ; possibly by following +// forward chains of named types. Such chains only exist while named types +// are incomplete. +func underlying(typ Type) Type { + for { + n, _ := typ.(*Named) + if n == nil { + break + } + typ = n.underlying + } + return typ +} + +func (n *Named) setUnderlying(typ Type) { + if n != nil { + n.underlying = typ + } +} + +func (check *Checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, path []*TypeName) { + assert(obj.typ == nil) + + // type declarations cannot use iota + assert(check.iota == nil) + + named := &Named{obj: obj} + def.setUnderlying(named) + obj.typ = named // make sure recursive type declarations terminate + + // determine underlying type of named + check.typExpr(typ, named, append(path, obj)) + + // The underlying type of named may be itself a named type that is + // incomplete: + // + // type ( + // A B + // B *C + // C A + // ) + // + // The type of C is the (named) type of A which is incomplete, + // and which has as its underlying type the named type B. + // Determine the (final, unnamed) underlying type by resolving + // any forward chain (they always end in an unnamed type). + named.underlying = underlying(named.underlying) + + // check and add associated methods + // TODO(gri) It's easy to create pathological cases where the + // current approach is incorrect: In general we need to know + // and add all methods _before_ type-checking the type. + // See https://play.golang.org/p/WMpE0q2wK8 + check.addMethodDecls(obj) +} + +func (check *Checker) addMethodDecls(obj *TypeName) { + // get associated methods + methods := check.methods[obj.name] + if len(methods) == 0 { + return // no methods + } + delete(check.methods, obj.name) + + // use an objset to check for name conflicts + var mset objset + + // spec: "If the base type is a struct type, the non-blank method + // and field names must be distinct." + base := obj.typ.(*Named) + if t, _ := base.underlying.(*Struct); t != nil { + for _, fld := range t.fields { + if fld.name != "_" { + assert(mset.insert(fld) == nil) + } + } + } + + // Checker.Files may be called multiple times; additional package files + // may add methods to already type-checked types. Add pre-existing methods + // so that we can detect redeclarations. + for _, m := range base.methods { + assert(m.name != "_") + assert(mset.insert(m) == nil) + } + + // type-check methods + for _, m := range methods { + // spec: "For a base type, the non-blank names of methods bound + // to it must be unique." + if m.name != "_" { + if alt := mset.insert(m); alt != nil { + switch alt.(type) { + case *Var: + check.errorf(m.pos, "field and method with the same name %s", m.name) + case *Func: + check.errorf(m.pos, "method %s already declared for %s", m.name, base) + default: + unreachable() + } + check.reportAltDecl(alt) + continue + } + } + check.objDecl(m, nil, nil) + // methods with blank _ names cannot be found - don't keep them + if m.name != "_" { + base.methods = append(base.methods, m) + } + } +} + +func (check *Checker) funcDecl(obj *Func, decl *declInfo) { + assert(obj.typ == nil) + + // func declarations cannot use iota + assert(check.iota == nil) + + sig := new(Signature) + obj.typ = sig // guard against cycles + fdecl := decl.fdecl + check.funcType(sig, fdecl.Recv, fdecl.Type) + if sig.recv == nil && obj.name == "init" && (sig.params.Len() > 0 || sig.results.Len() > 0) { + check.errorf(fdecl.Pos(), "func init must have no arguments and no return values") + // ok to continue + } + + // function body must be type-checked after global declarations + // (functions implemented elsewhere have no body) + if !check.conf.IgnoreFuncBodies && fdecl.Body != nil { + check.later(obj.name, decl, sig, fdecl.Body) + } +} + +func (check *Checker) declStmt(decl ast.Decl) { + pkg := check.pkg + + switch d := decl.(type) { + case *ast.BadDecl: + // ignore + + case *ast.GenDecl: + var last *ast.ValueSpec // last ValueSpec with type or init exprs seen + for iota, spec := range d.Specs { + switch s := spec.(type) { + case *ast.ValueSpec: + switch d.Tok { + case token.CONST: + // determine which init exprs to use + switch { + case s.Type != nil || len(s.Values) > 0: + last = s + case last == nil: + last = new(ast.ValueSpec) // make sure last exists + } + + // declare all constants + lhs := make([]*Const, len(s.Names)) + for i, name := range s.Names { + obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(iota))) + lhs[i] = obj + + var init ast.Expr + if i < len(last.Values) { + init = last.Values[i] + } + + check.constDecl(obj, last.Type, init) + } + + check.arityMatch(s, last) + + // spec: "The scope of a constant or variable identifier declared + // inside a function begins at the end of the ConstSpec or VarSpec + // (ShortVarDecl for short variable declarations) and ends at the + // end of the innermost containing block." + scopePos := s.End() + for i, name := range s.Names { + check.declare(check.scope, name, lhs[i], scopePos) + } + + case token.VAR: + lhs0 := make([]*Var, len(s.Names)) + for i, name := range s.Names { + lhs0[i] = NewVar(name.Pos(), pkg, name.Name, nil) + } + + // initialize all variables + for i, obj := range lhs0 { + var lhs []*Var + var init ast.Expr + switch len(s.Values) { + case len(s.Names): + // lhs and rhs match + init = s.Values[i] + case 1: + // rhs is expected to be a multi-valued expression + lhs = lhs0 + init = s.Values[0] + default: + if i < len(s.Values) { + init = s.Values[i] + } + } + check.varDecl(obj, lhs, s.Type, init) + if len(s.Values) == 1 { + // If we have a single lhs variable we are done either way. + // If we have a single rhs expression, it must be a multi- + // valued expression, in which case handling the first lhs + // variable will cause all lhs variables to have a type + // assigned, and we are done as well. + if debug { + for _, obj := range lhs0 { + assert(obj.typ != nil) + } + } + break + } + } + + check.arityMatch(s, nil) + + // declare all variables + // (only at this point are the variable scopes (parents) set) + scopePos := s.End() // see constant declarations + for i, name := range s.Names { + // see constant declarations + check.declare(check.scope, name, lhs0[i], scopePos) + } + + default: + check.invalidAST(s.Pos(), "invalid token %s", d.Tok) + } + + case *ast.TypeSpec: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) + // spec: "The scope of a type identifier declared inside a function + // begins at the identifier in the TypeSpec and ends at the end of + // the innermost containing block." + scopePos := s.Name.Pos() + check.declare(check.scope, s.Name, obj, scopePos) + check.typeDecl(obj, s.Type, nil, nil) + + default: + check.invalidAST(s.Pos(), "const, type, or var declaration expected") + } + } + + default: + check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) + } +} diff --git a/libgo/go/go/types/errors.go b/libgo/go/go/types/errors.go new file mode 100644 index 00000000000..0c0049b1f3e --- /dev/null +++ b/libgo/go/go/types/errors.go @@ -0,0 +1,103 @@ +// 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 implements various error reporters. + +package types + +import ( + "fmt" + "go/ast" + "go/token" + "strings" +) + +func assert(p bool) { + if !p { + panic("assertion failed") + } +} + +func unreachable() { + panic("unreachable") +} + +func (check *Checker) qualifier(pkg *Package) string { + if pkg != check.pkg { + return pkg.path + } + return "" +} + +func (check *Checker) sprintf(format string, args ...interface{}) string { + for i, arg := range args { + switch a := arg.(type) { + case nil: + arg = "<nil>" + case operand: + panic("internal error: should always pass *operand") + case *operand: + arg = operandString(a, check.qualifier) + case token.Pos: + arg = check.fset.Position(a).String() + case ast.Expr: + arg = ExprString(a) + case Object: + arg = ObjectString(a, check.qualifier) + case Type: + arg = TypeString(a, check.qualifier) + } + args[i] = arg + } + return fmt.Sprintf(format, args...) +} + +func (check *Checker) trace(pos token.Pos, format string, args ...interface{}) { + fmt.Printf("%s:\t%s%s\n", + check.fset.Position(pos), + strings.Repeat(". ", check.indent), + check.sprintf(format, args...), + ) +} + +// dump is only needed for debugging +func (check *Checker) dump(format string, args ...interface{}) { + fmt.Println(check.sprintf(format, args...)) +} + +func (check *Checker) err(pos token.Pos, msg string, soft bool) { + err := Error{check.fset, pos, msg, soft} + if check.firstErr == nil { + check.firstErr = err + } + f := check.conf.Error + if f == nil { + panic(bailout{}) // report only first error + } + f(err) +} + +func (check *Checker) error(pos token.Pos, msg string) { + check.err(pos, msg, false) +} + +func (check *Checker) errorf(pos token.Pos, format string, args ...interface{}) { + check.err(pos, check.sprintf(format, args...), false) +} + +func (check *Checker) softErrorf(pos token.Pos, format string, args ...interface{}) { + check.err(pos, check.sprintf(format, args...), true) +} + +func (check *Checker) invalidAST(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid AST: "+format, args...) +} + +func (check *Checker) invalidArg(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid argument: "+format, args...) +} + +func (check *Checker) invalidOp(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid operation: "+format, args...) +} diff --git a/libgo/go/go/types/eval.go b/libgo/go/go/types/eval.go new file mode 100644 index 00000000000..7b42ff1a9d0 --- /dev/null +++ b/libgo/go/go/types/eval.go @@ -0,0 +1,83 @@ +// Copyright 2013 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/parser" + "go/token" +) + +// Eval returns the type and, if constant, the value for the +// expression expr, evaluated at position pos of package pkg, +// which must have been derived from type-checking an AST with +// complete position information relative to the provided file +// set. +// +// If the expression contains function literals, their bodies +// are ignored (i.e., the bodies are not type-checked). +// +// If pkg == nil, the Universe scope is used and the provided +// position pos is ignored. If pkg != nil, and pos is invalid, +// the package scope is used. Otherwise, pos must belong to the +// package. +// +// An error is returned if pos is not within the package or +// if the node cannot be evaluated. +// +// Note: Eval should not be used instead of running Check to compute +// types and values, but in addition to Check. Eval will re-evaluate +// its argument each time, and it also does not know about the context +// in which an expression is used (e.g., an assignment). Thus, top- +// level untyped constants will return an untyped type rather then the +// respective context-specific type. +// +func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (tv TypeAndValue, err error) { + // determine scope + var scope *Scope + if pkg == nil { + scope = Universe + pos = token.NoPos + } else if !pos.IsValid() { + scope = pkg.scope + } else { + // The package scope extent (position information) may be + // incorrect (files spread accross a wide range of fset + // positions) - ignore it and just consider its children + // (file scopes). + for _, fscope := range pkg.scope.children { + if scope = fscope.Innermost(pos); scope != nil { + break + } + } + if scope == nil || debug { + s := scope + for s != nil && s != pkg.scope { + s = s.parent + } + // s == nil || s == pkg.scope + if s == nil { + return TypeAndValue{}, fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name) + } + } + } + + // parse expressions + node, err := parser.ParseExprFrom(fset, "eval", expr, 0) + if err != nil { + return TypeAndValue{}, err + } + + // initialize checker + check := NewChecker(nil, fset, pkg, nil) + check.scope = scope + check.pos = pos + defer check.handleBailout(&err) + + // evaluate node + var x operand + check.rawExpr(&x, node, nil) + return TypeAndValue{x.mode, x.typ, x.val}, err +} diff --git a/libgo/go/go/types/eval_test.go b/libgo/go/go/types/eval_test.go new file mode 100644 index 00000000000..7e0be43e723 --- /dev/null +++ b/libgo/go/go/types/eval_test.go @@ -0,0 +1,188 @@ +// Copyright 2013 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 for Eval. + +package types_test + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "strings" + "testing" + + . "go/types" +) + +func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, expr string, typ Type, typStr, valStr string) { + gotTv, err := Eval(fset, pkg, pos, expr) + if err != nil { + t.Errorf("Eval(%q) failed: %s", expr, err) + return + } + if gotTv.Type == nil { + t.Errorf("Eval(%q) got nil type but no error", expr) + return + } + + // compare types + if typ != nil { + // we have a type, check identity + if !Identical(gotTv.Type, typ) { + t.Errorf("Eval(%q) got type %s, want %s", expr, gotTv.Type, typ) + return + } + } else { + // we have a string, compare type string + gotStr := gotTv.Type.String() + if gotStr != typStr { + t.Errorf("Eval(%q) got type %s, want %s", expr, gotStr, typStr) + return + } + } + + // compare values + gotStr := "" + if gotTv.Value != nil { + gotStr = gotTv.Value.String() + } + if gotStr != valStr { + t.Errorf("Eval(%q) got value %s, want %s", expr, gotStr, valStr) + } +} + +func TestEvalBasic(t *testing.T) { + fset := token.NewFileSet() + for _, typ := range Typ[Bool : String+1] { + testEval(t, fset, nil, token.NoPos, typ.Name(), typ, "", "") + } +} + +func TestEvalComposite(t *testing.T) { + fset := token.NewFileSet() + for _, test := range independentTestTypes { + testEval(t, fset, nil, token.NoPos, test.src, nil, test.str, "") + } +} + +func TestEvalArith(t *testing.T) { + var tests = []string{ + `true`, + `false == false`, + `12345678 + 87654321 == 99999999`, + `10 * 20 == 200`, + `(1<<1000)*2 >> 100 == 2<<900`, + `"foo" + "bar" == "foobar"`, + `"abc" <= "bcd"`, + `len([10]struct{}{}) == 2*5`, + } + fset := token.NewFileSet() + for _, test := range tests { + testEval(t, fset, nil, token.NoPos, test, Typ[UntypedBool], "", "true") + } +} + +func TestEvalPos(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // The contents of /*-style comments are of the form + // expr => value, type + // where value may be the empty string. + // Each expr is evaluated at the position of the comment + // and the result is compared with the expected value + // and type. + var sources = []string{ + ` + package p + import "fmt" + import m "math" + const c = 3.0 + type T []int + func f(a int, s string) float64 { + fmt.Println("calling f") + _ = m.Pi // use package math + const d int = c + 1 + var x int + x = a + len(s) + return float64(x) + /* true => true, untyped bool */ + /* fmt.Println => , func(a ...interface{}) (n int, err error) */ + /* c => 3, untyped float */ + /* T => , p.T */ + /* a => , int */ + /* s => , string */ + /* d => 4, int */ + /* x => , int */ + /* d/c => 1, int */ + /* c/2 => 3/2, untyped float */ + /* m.Pi < m.E => false, untyped bool */ + } + `, + ` + package p + /* c => 3, untyped float */ + type T1 /* T1 => , p.T1 */ struct {} + var v1 /* v1 => , int */ = 42 + func /* f1 => , func(v1 float64) */ f1(v1 float64) { + /* f1 => , func(v1 float64) */ + /* v1 => , float64 */ + var c /* c => 3, untyped float */ = "foo" /* c => , string */ + { + var c struct { + c /* c => , string */ int + } + /* c => , struct{c int} */ + _ = c + } + _ = func(a, b, c int) /* c => , string */ { + /* c => , int */ + } + _ = c + type FT /* FT => , p.FT */ interface{} + } + `, + ` + package p + /* T => , p.T */ + `, + } + + fset := token.NewFileSet() + var files []*ast.File + for i, src := range sources { + file, err := parser.ParseFile(fset, "p", src, parser.ParseComments) + if err != nil { + t.Fatalf("could not parse file %d: %s", i, err) + } + files = append(files, file) + } + + conf := Config{Importer: importer.Default()} + pkg, err := conf.Check("p", fset, files, nil) + if err != nil { + t.Fatal(err) + } + + for _, file := range files { + for _, group := range file.Comments { + for _, comment := range group.List { + s := comment.Text + if len(s) >= 4 && s[:2] == "/*" && s[len(s)-2:] == "*/" { + str, typ := split(s[2:len(s)-2], ", ") + str, val := split(str, "=>") + testEval(t, fset, pkg, comment.Pos(), str, nil, typ, val) + } + } + } + } +} + +// split splits string s at the first occurrence of s. +func split(s, sep string) (string, string) { + i := strings.Index(s, sep) + return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):]) +} diff --git a/libgo/go/go/types/example_test.go b/libgo/go/go/types/example_test.go new file mode 100644 index 00000000000..9daad8765a8 --- /dev/null +++ b/libgo/go/go/types/example_test.go @@ -0,0 +1,312 @@ +// Copyright 2015 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. + +// Only run where builders (build.golang.org) have +// access to compiled packages for import. +// +// +build ignore,!arm,!arm64,!nacl + +package types_test + +// This file shows examples of basic usage of the go/types API. +// +// To locate a Go package, use (*go/build.Context).Import. +// To load, parse, and type-check a complete Go program +// from source, use golang.org/x/tools/go/loader. + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/importer" + "go/parser" + "go/token" + "go/types" + "log" + "regexp" + "sort" + "strings" +) + +// ExampleScope prints the tree of Scopes of a package created from a +// set of parsed files. +func ExampleScope() { + // Parse the source files for a package. + fset := token.NewFileSet() + var files []*ast.File + for _, file := range []struct{ name, input string }{ + {"main.go", ` +package main +import "fmt" +func main() { + freezing := FToC(-18) + fmt.Println(freezing, Boiling) } +`}, + {"celsius.go", ` +package main +import "fmt" +type Celsius float64 +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } +func FToC(f float64) Celsius { return Celsius(f - 32 / 9 * 5) } +const Boiling Celsius = 100 +`}, + } { + f, err := parser.ParseFile(fset, file.name, file.input, 0) + if err != nil { + log.Fatal(err) + } + files = append(files, f) + } + + // Type-check a package consisting of these files. + // Type information for the imported "fmt" package + // comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a. + conf := types.Config{Importer: importer.Default()} + pkg, err := conf.Check("temperature", fset, files, nil) + if err != nil { + log.Fatal(err) + } + + // Print the tree of scopes. + // For determinism, we redact addresses. + var buf bytes.Buffer + pkg.Scope().WriteTo(&buf, 0, true) + rx := regexp.MustCompile(` 0x[a-fA-F0-9]*`) + fmt.Println(rx.ReplaceAllString(buf.String(), "")) + + // Output: + // package "temperature" scope { + // . const temperature.Boiling temperature.Celsius + // . type temperature.Celsius float64 + // . func temperature.FToC(f float64) temperature.Celsius + // . func temperature.main() + // + // . main.go scope { + // . . package fmt + // + // . . function scope { + // . . . var freezing temperature.Celsius + // . . }. } + // . celsius.go scope { + // . . package fmt + // + // . . function scope { + // . . . var c temperature.Celsius + // . . } + // . . function scope { + // . . . var f float64 + // . . }. }} +} + +// ExampleMethodSet prints the method sets of various types. +func ExampleMethodSet() { + // Parse a single source file. + const input = ` +package temperature +import "fmt" +type Celsius float64 +func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) } +func (c *Celsius) SetF(f float64) { *c = Celsius(f - 32 / 9 * 5) } +` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "celsius.go", input, 0) + if err != nil { + log.Fatal(err) + } + + // Type-check a package consisting of this file. + // Type information for the imported packages + // comes from $GOROOT/pkg/$GOOS_$GOOARCH/fmt.a. + conf := types.Config{Importer: importer.Default()} + pkg, err := conf.Check("temperature", fset, []*ast.File{f}, nil) + if err != nil { + log.Fatal(err) + } + + // Print the method sets of Celsius and *Celsius. + celsius := pkg.Scope().Lookup("Celsius").Type() + for _, t := range []types.Type{celsius, types.NewPointer(celsius)} { + fmt.Printf("Method set of %s:\n", t) + mset := types.NewMethodSet(t) + for i := 0; i < mset.Len(); i++ { + fmt.Println(mset.At(i)) + } + fmt.Println() + } + + // Output: + // Method set of temperature.Celsius: + // method (temperature.Celsius) String() string + // + // Method set of *temperature.Celsius: + // method (*temperature.Celsius) SetF(f float64) + // method (*temperature.Celsius) String() string +} + +// ExampleInfo prints various facts recorded by the type checker in a +// types.Info struct: definitions of and references to each named object, +// and the type, value, and mode of every expression in the package. +func ExampleInfo() { + // Parse a single source file. + const input = ` +package fib + +type S string + +var a, b, c = len(b), S(c), "hello" + +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) - fib(x-2) +}` + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "fib.go", input, 0) + if err != nil { + log.Fatal(err) + } + + // Type-check the package. + // We create an empty map for each kind of input + // we're interested in, and Check populates them. + info := types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + } + var conf types.Config + pkg, err := conf.Check("fib", fset, []*ast.File{f}, &info) + if err != nil { + log.Fatal(err) + } + + // Print package-level variables in initialization order. + fmt.Printf("InitOrder: %v\n\n", info.InitOrder) + + // For each named object, print the line and + // column of its definition and each of its uses. + fmt.Println("Defs and Uses of each named object:") + usesByObj := make(map[types.Object][]string) + for id, obj := range info.Uses { + posn := fset.Position(id.Pos()) + lineCol := fmt.Sprintf("%d:%d", posn.Line, posn.Column) + usesByObj[obj] = append(usesByObj[obj], lineCol) + } + var items []string + for obj, uses := range usesByObj { + sort.Strings(uses) + item := fmt.Sprintf("%s:\n defined at %s\n used at %s", + types.ObjectString(obj, types.RelativeTo(pkg)), + fset.Position(obj.Pos()), + strings.Join(uses, ", ")) + items = append(items, item) + } + sort.Strings(items) // sort by line:col, in effect + fmt.Println(strings.Join(items, "\n")) + fmt.Println() + + fmt.Println("Types and Values of each expression:") + items = nil + for expr, tv := range info.Types { + var buf bytes.Buffer + posn := fset.Position(expr.Pos()) + tvstr := tv.Type.String() + if tv.Value != nil { + tvstr += " = " + tv.Value.String() + } + // line:col | expr | mode : type = value + fmt.Fprintf(&buf, "%2d:%2d | %-19s | %-7s : %s", + posn.Line, posn.Column, exprString(fset, expr), + mode(tv), tvstr) + items = append(items, buf.String()) + } + sort.Strings(items) + fmt.Println(strings.Join(items, "\n")) + + // Output: + // InitOrder: [c = "hello" b = S(c) a = len(b)] + // + // Defs and Uses of each named object: + // builtin len: + // defined at - + // used at 6:15 + // func fib(x int) int: + // defined at fib.go:8:6 + // used at 12:20, 12:9 + // type S string: + // defined at fib.go:4:6 + // used at 6:23 + // type int int: + // defined at - + // used at 8:12, 8:17 + // type string string: + // defined at - + // used at 4:8 + // var b S: + // defined at fib.go:6:8 + // used at 6:19 + // var c string: + // defined at fib.go:6:11 + // used at 6:25 + // var x int: + // defined at fib.go:8:10 + // used at 10:10, 12:13, 12:24, 9:5 + // + // Types and Values of each expression: + // 4: 8 | string | type : string + // 6:15 | len | builtin : func(string) int + // 6:15 | len(b) | value : int + // 6:19 | b | var : fib.S + // 6:23 | S | type : fib.S + // 6:23 | S(c) | value : fib.S + // 6:25 | c | var : string + // 6:29 | "hello" | value : string = "hello" + // 8:12 | int | type : int + // 8:17 | int | type : int + // 9: 5 | x | var : int + // 9: 5 | x < 2 | value : untyped bool + // 9: 9 | 2 | value : int = 2 + // 10:10 | x | var : int + // 12: 9 | fib | value : func(x int) int + // 12: 9 | fib(x - 1) | value : int + // 12: 9 | fib(x-1) - fib(x-2) | value : int + // 12:13 | x | var : int + // 12:13 | x - 1 | value : int + // 12:15 | 1 | value : int = 1 + // 12:20 | fib | value : func(x int) int + // 12:20 | fib(x - 2) | value : int + // 12:24 | x | var : int + // 12:24 | x - 2 | value : int + // 12:26 | 2 | value : int = 2 +} + +func mode(tv types.TypeAndValue) string { + switch { + case tv.IsVoid(): + return "void" + case tv.IsType(): + return "type" + case tv.IsBuiltin(): + return "builtin" + case tv.IsNil(): + return "nil" + case tv.Assignable(): + if tv.Addressable() { + return "var" + } + return "mapindex" + case tv.IsValue(): + return "value" + default: + return "unknown" + } +} + +func exprString(fset *token.FileSet, expr ast.Expr) string { + var buf bytes.Buffer + format.Node(&buf, fset, expr) + return buf.String() +} diff --git a/libgo/go/go/types/expr.go b/libgo/go/go/types/expr.go new file mode 100644 index 00000000000..7d00dd5fa5d --- /dev/null +++ b/libgo/go/go/types/expr.go @@ -0,0 +1,1496 @@ +// 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 implements typechecking of expressions. + +package types + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "math" +) + +/* +Basic algorithm: + +Expressions are checked recursively, top down. Expression checker functions +are generally of the form: + + func f(x *operand, e *ast.Expr, ...) + +where e is the expression to be checked, and x is the result of the check. +The check performed by f may fail in which case x.mode == invalid, and +related error messages will have been issued by f. + +If a hint argument is present, it is the composite literal element type +of an outer composite literal; it is used to type-check composite literal +elements that have no explicit type specification in the source +(e.g.: []T{{...}, {...}}, the hint is the type T in this case). + +All expressions are checked via rawExpr, which dispatches according +to expression kind. Upon returning, rawExpr is recording the types and +constant values for all expressions that have an untyped type (those types +may change on the way up in the expression tree). Usually these are constants, +but the results of comparisons or non-constant shifts of untyped constants +may also be untyped, but not constant. + +Untyped expressions may eventually become fully typed (i.e., not untyped), +typically when the value is assigned to a variable, or is used otherwise. +The updateExprType method is used to record this final type and update +the recorded types: the type-checked expression tree is again traversed down, +and the new type is propagated as needed. Untyped constant expression values +that become fully typed must now be representable by the full type (constant +sub-expression trees are left alone except for their roots). This mechanism +ensures that a client sees the actual (run-time) type an untyped value would +have. It also permits type-checking of lhs shift operands "as if the shift +were not present": when updateExprType visits an untyped lhs shift operand +and assigns it it's final type, that type must be an integer type, and a +constant lhs must be representable as an integer. + +When an expression gets its final type, either on the way out from rawExpr, +on the way down in updateExprType, or at the end of the type checker run, +the type (and constant value, if any) is recorded via Info.Types, if present. +*/ + +type opPredicates map[token.Token]func(Type) bool + +var unaryOpPredicates = opPredicates{ + token.ADD: isNumeric, + token.SUB: isNumeric, + token.XOR: isInteger, + token.NOT: isBoolean, +} + +func (check *Checker) op(m opPredicates, x *operand, op token.Token) bool { + if pred := m[op]; pred != nil { + if !pred(x.typ) { + check.invalidOp(x.pos(), "operator %s not defined for %s", op, x) + return false + } + } else { + check.invalidAST(x.pos(), "unknown operator %s", op) + return false + } + return true +} + +// The unary expression e may be nil. It's passed in for better error messages only. +func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) { + switch op { + case token.AND: + // spec: "As an exception to the addressability + // requirement x may also be a composite literal." + if _, ok := unparen(x.expr).(*ast.CompositeLit); !ok && x.mode != variable { + check.invalidOp(x.pos(), "cannot take address of %s", x) + x.mode = invalid + return + } + x.mode = value + x.typ = &Pointer{base: x.typ} + return + + case token.ARROW: + typ, ok := x.typ.Underlying().(*Chan) + if !ok { + check.invalidOp(x.pos(), "cannot receive from non-channel %s", x) + x.mode = invalid + return + } + if typ.dir == SendOnly { + check.invalidOp(x.pos(), "cannot receive from send-only channel %s", x) + x.mode = invalid + return + } + x.mode = commaok + x.typ = typ.elem + check.hasCallOrRecv = true + return + } + + if !check.op(unaryOpPredicates, x, op) { + x.mode = invalid + return + } + + if x.mode == constant_ { + typ := x.typ.Underlying().(*Basic) + var prec uint + if isUnsigned(typ) { + prec = uint(check.conf.sizeof(typ) * 8) + } + x.val = constant.UnaryOp(op, x.val, prec) + // Typed constants must be representable in + // their type after each constant operation. + if isTyped(typ) { + if e != nil { + x.expr = e // for better error message + } + check.representable(x, typ) + } + return + } + + x.mode = value + // x.typ remains unchanged +} + +func isShift(op token.Token) bool { + return op == token.SHL || op == token.SHR +} + +func isComparison(op token.Token) bool { + // Note: tokens are not ordered well to make this much easier + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return true + } + return false +} + +func fitsFloat32(x constant.Value) bool { + f32, _ := constant.Float32Val(x) + f := float64(f32) + return !math.IsInf(f, 0) +} + +func roundFloat32(x constant.Value) constant.Value { + f32, _ := constant.Float32Val(x) + f := float64(f32) + if !math.IsInf(f, 0) { + return constant.MakeFloat64(f) + } + return nil +} + +func fitsFloat64(x constant.Value) bool { + f, _ := constant.Float64Val(x) + return !math.IsInf(f, 0) +} + +func roundFloat64(x constant.Value) constant.Value { + f, _ := constant.Float64Val(x) + if !math.IsInf(f, 0) { + return constant.MakeFloat64(f) + } + return nil +} + +// representableConst reports whether x can be represented as +// value of the given basic type kind and for the configuration +// provided (only needed for int/uint sizes). +// +// If rounded != nil, *rounded is set to the rounded value of x for +// representable floating-point values; it is left alone otherwise. +// It is ok to provide the addressof the first argument for rounded. +func representableConst(x constant.Value, conf *Config, as BasicKind, rounded *constant.Value) bool { + switch x.Kind() { + case constant.Unknown: + return true + + case constant.Bool: + return as == Bool || as == UntypedBool + + case constant.Int: + if x, ok := constant.Int64Val(x); ok { + switch as { + case Int: + var s = uint(conf.sizeof(Typ[as])) * 8 + return int64(-1)<<(s-1) <= x && x <= int64(1)<<(s-1)-1 + case Int8: + const s = 8 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int16: + const s = 16 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int32: + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int64: + return true + case Uint, Uintptr: + if s := uint(conf.sizeof(Typ[as])) * 8; s < 64 { + return 0 <= x && x <= int64(1)<<s-1 + } + return 0 <= x + case Uint8: + const s = 8 + return 0 <= x && x <= 1<<s-1 + case Uint16: + const s = 16 + return 0 <= x && x <= 1<<s-1 + case Uint32: + const s = 32 + return 0 <= x && x <= 1<<s-1 + case Uint64: + return 0 <= x + case Float32, Float64, Complex64, Complex128, + UntypedInt, UntypedFloat, UntypedComplex: + return true + } + } + + n := constant.BitLen(x) + switch as { + case Uint, Uintptr: + var s = uint(conf.sizeof(Typ[as])) * 8 + return constant.Sign(x) >= 0 && n <= int(s) + case Uint64: + return constant.Sign(x) >= 0 && n <= 64 + case Float32, Complex64: + if rounded == nil { + return fitsFloat32(x) + } + r := roundFloat32(x) + if r != nil { + *rounded = r + return true + } + case Float64, Complex128: + if rounded == nil { + return fitsFloat64(x) + } + r := roundFloat64(x) + if r != nil { + *rounded = r + return true + } + case UntypedInt, UntypedFloat, UntypedComplex: + return true + } + + case constant.Float: + switch as { + case Float32, Complex64: + if rounded == nil { + return fitsFloat32(x) + } + r := roundFloat32(x) + if r != nil { + *rounded = r + return true + } + case Float64, Complex128: + if rounded == nil { + return fitsFloat64(x) + } + r := roundFloat64(x) + if r != nil { + *rounded = r + return true + } + case UntypedFloat, UntypedComplex: + return true + } + + case constant.Complex: + switch as { + case Complex64: + if rounded == nil { + return fitsFloat32(constant.Real(x)) && fitsFloat32(constant.Imag(x)) + } + re := roundFloat32(constant.Real(x)) + im := roundFloat32(constant.Imag(x)) + if re != nil && im != nil { + *rounded = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + return true + } + case Complex128: + if rounded == nil { + return fitsFloat64(constant.Real(x)) && fitsFloat64(constant.Imag(x)) + } + re := roundFloat64(constant.Real(x)) + im := roundFloat64(constant.Imag(x)) + if re != nil && im != nil { + *rounded = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + return true + } + case UntypedComplex: + return true + } + + case constant.String: + return as == String || as == UntypedString + + default: + unreachable() + } + + return false +} + +// representable checks that a constant operand is representable in the given basic type. +func (check *Checker) representable(x *operand, typ *Basic) { + assert(x.mode == constant_) + if !representableConst(x.val, check.conf, typ.kind, &x.val) { + var msg string + if isNumeric(x.typ) && isNumeric(typ) { + // numeric conversion : error msg + // + // integer -> integer : overflows + // integer -> float : overflows (actually not possible) + // float -> integer : truncated + // float -> float : overflows + // + if !isInteger(x.typ) && isInteger(typ) { + msg = "%s truncated to %s" + } else { + msg = "%s overflows %s" + } + } else { + msg = "cannot convert %s to %s" + } + check.errorf(x.pos(), msg, x, typ) + x.mode = invalid + } +} + +// updateExprType updates the type of x to typ and invokes itself +// recursively for the operands of x, depending on expression kind. +// If typ is still an untyped and not the final type, updateExprType +// only updates the recorded untyped type for x and possibly its +// operands. Otherwise (i.e., typ is not an untyped type anymore, +// or it is the final type for x), the type and value are recorded. +// Also, if x is a constant, it must be representable as a value of typ, +// and if x is the (formerly untyped) lhs operand of a non-constant +// shift, it must be an integer value. +// +func (check *Checker) updateExprType(x ast.Expr, typ Type, final bool) { + old, found := check.untyped[x] + if !found { + return // nothing to do + } + + // update operands of x if necessary + switch x := x.(type) { + case *ast.BadExpr, + *ast.FuncLit, + *ast.CompositeLit, + *ast.IndexExpr, + *ast.SliceExpr, + *ast.TypeAssertExpr, + *ast.StarExpr, + *ast.KeyValueExpr, + *ast.ArrayType, + *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.ChanType: + // These expression are never untyped - nothing to do. + // The respective sub-expressions got their final types + // upon assignment or use. + if debug { + check.dump("%s: found old type(%s): %s (new: %s)", x.Pos(), x, old.typ, typ) + unreachable() + } + return + + case *ast.CallExpr: + // Resulting in an untyped constant (e.g., built-in complex). + // The respective calls take care of calling updateExprType + // for the arguments if necessary. + + case *ast.Ident, *ast.BasicLit, *ast.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. + + case *ast.ParenExpr: + check.updateExprType(x.X, typ, final) + + case *ast.UnaryExpr: + // If x is a constant, the operands were constants. + // They don't need to be updated since they never + // get "materialized" into a typed value; and they + // will be processed at the end of the type check. + if old.val != nil { + break + } + check.updateExprType(x.X, typ, final) + + case *ast.BinaryExpr: + if old.val != nil { + break // see comment for unary expressions + } + if isComparison(x.Op) { + // The result type is independent of operand types + // and the operand types must have final types. + } else if isShift(x.Op) { + // The result type depends only on lhs operand. + // The rhs type was updated when checking the shift. + check.updateExprType(x.X, typ, final) + } else { + // The operand types match the result type. + check.updateExprType(x.X, typ, final) + check.updateExprType(x.Y, typ, final) + } + + default: + unreachable() + } + + // If the new type is not final and still untyped, just + // update the recorded type. + if !final && isUntyped(typ) { + old.typ = typ.Underlying().(*Basic) + check.untyped[x] = old + return + } + + // Otherwise we have the final (typed or untyped type). + // Remove it from the map of yet untyped expressions. + delete(check.untyped, x) + + // If x is the lhs of a shift, its final type must be integer. + // We already know from the shift check that it is representable + // as an integer if it is a constant. + if old.isLhs && !isInteger(typ) { + check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) + return + } + + // Everything's fine, record final type and value for x. + check.recordTypeAndValue(x, old.mode, typ, old.val) +} + +// updateExprVal updates the value of x to val. +func (check *Checker) updateExprVal(x ast.Expr, val constant.Value) { + if info, ok := check.untyped[x]; ok { + info.val = val + check.untyped[x] = info + } +} + +// convertUntyped attempts to set the type of an untyped value to the target type. +func (check *Checker) convertUntyped(x *operand, target Type) { + if x.mode == invalid || isTyped(x.typ) || target == Typ[Invalid] { + return + } + + // TODO(gri) Sloppy code - clean up. This function is central + // to assignment and expression checking. + + if isUntyped(target) { + // both x and target are untyped + xkind := x.typ.(*Basic).kind + tkind := target.(*Basic).kind + if isNumeric(x.typ) && isNumeric(target) { + if xkind < tkind { + x.typ = target + check.updateExprType(x.expr, target, false) + } + } else if xkind != tkind { + goto Error + } + return + } + + // typed target + switch t := target.Underlying().(type) { + case *Basic: + if x.mode == constant_ { + check.representable(x, t) + if x.mode == invalid { + return + } + // expression value may have been rounded - update if needed + // TODO(gri) A floating-point value may silently underflow to + // zero. If it was negative, the sign is lost. See issue 6898. + check.updateExprVal(x.expr, x.val) + } else { + // Non-constant untyped values may appear as the + // result of comparisons (untyped bool), intermediate + // (delayed-checked) rhs operands of shifts, and as + // the value nil. + switch x.typ.(*Basic).kind { + case UntypedBool: + if !isBoolean(target) { + goto Error + } + case UntypedInt, UntypedRune, UntypedFloat, UntypedComplex: + if !isNumeric(target) { + goto Error + } + case UntypedString: + // Non-constant untyped string values are not + // permitted by the spec and should not occur. + unreachable() + case UntypedNil: + // Unsafe.Pointer is a basic type that includes nil. + if !hasNil(target) { + goto Error + } + default: + goto Error + } + } + case *Interface: + if !x.isNil() && !t.Empty() /* empty interfaces are ok */ { + goto Error + } + // Update operand types to the default type rather then + // the target (interface) type: values must have concrete + // dynamic types. If the value is nil, keep it untyped + // (this is important for tools such as go vet which need + // the dynamic type for argument checking of say, print + // functions) + if x.isNil() { + target = Typ[UntypedNil] + } else { + // cannot assign untyped values to non-empty interfaces + if !t.Empty() { + goto Error + } + target = defaultType(x.typ) + } + case *Pointer, *Signature, *Slice, *Map, *Chan: + if !x.isNil() { + goto Error + } + // keep nil untyped - see comment for interfaces, above + target = Typ[UntypedNil] + default: + goto Error + } + + x.typ = target + check.updateExprType(x.expr, target, true) // UntypedNils are final + return + +Error: + check.errorf(x.pos(), "cannot convert %s to %s", x, target) + x.mode = invalid +} + +func (check *Checker) comparison(x, y *operand, op token.Token) { + // spec: "In any comparison, the first operand must be assignable + // to the type of the second operand, or vice versa." + err := "" + if x.assignableTo(check.conf, y.typ) || y.assignableTo(check.conf, x.typ) { + defined := false + switch op { + case token.EQL, token.NEQ: + // spec: "The equality operators == and != apply to operands that are comparable." + defined = Comparable(x.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) + case token.LSS, token.LEQ, token.GTR, token.GEQ: + // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." + defined = isOrdered(x.typ) + default: + unreachable() + } + if !defined { + typ := x.typ + if x.isNil() { + typ = y.typ + } + err = check.sprintf("operator %s not defined for %s", op, typ) + } + } else { + err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) + } + + if err != "" { + check.errorf(x.pos(), "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) + x.mode = invalid + return + } + + if x.mode == constant_ && y.mode == constant_ { + x.val = constant.MakeBool(constant.Compare(x.val, op, y.val)) + // The operands are never materialized; no need to update + // their types. + } else { + x.mode = value + // The operands have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + check.updateExprType(x.expr, defaultType(x.typ), true) + check.updateExprType(y.expr, defaultType(y.typ), true) + } + + // spec: "Comparison operators compare two operands and yield + // an untyped boolean value." + x.typ = Typ[UntypedBool] +} + +func (check *Checker) shift(x, y *operand, op token.Token) { + untypedx := isUntyped(x.typ) + + // The lhs must be of integer type or be representable + // as an integer; otherwise the shift has no chance. + if !x.isInteger() { + check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + // spec: "The right operand in a shift expression must have unsigned + // integer type or be an untyped constant that can be converted to + // unsigned integer type." + switch { + case isInteger(y.typ) && isUnsigned(y.typ): + // nothing to do + case isUntyped(y.typ): + check.convertUntyped(y, Typ[UntypedInt]) + if y.mode == invalid { + x.mode = invalid + return + } + default: + check.invalidOp(y.pos(), "shift count %s must be unsigned integer", y) + x.mode = invalid + return + } + + if x.mode == constant_ { + if y.mode == constant_ { + // rhs must be an integer value + if !y.isInteger() { + check.invalidOp(y.pos(), "shift count %s must be unsigned integer", y) + x.mode = invalid + return + } + // rhs must be within reasonable bounds + const stupidShift = 1023 - 1 + 52 // so we can express smallestFloat64 + s, ok := constant.Uint64Val(y.val) + if !ok || s > stupidShift { + check.invalidOp(y.pos(), "stupid shift count %s", y) + x.mode = invalid + return + } + // The lhs is representable as an integer but may not be an integer + // (e.g., 2.0, an untyped float) - this can only happen for untyped + // non-integer numeric constants. Correct the type so that the shift + // result is of integer type. + if !isInteger(x.typ) { + x.typ = Typ[UntypedInt] + } + x.val = constant.Shift(x.val, op, uint(s)) + return + } + + // non-constant shift with constant lhs + if untypedx { + // spec: "If the left operand of a non-constant shift + // expression is an untyped constant, the type of the + // constant is what it would be if the shift expression + // were replaced by its left operand alone.". + // + // Delay operand checking until we know the final type: + // The lhs expression must be in the untyped map, mark + // the entry as lhs shift operand. + info, found := check.untyped[x.expr] + assert(found) + info.isLhs = true + check.untyped[x.expr] = info + // keep x's type + x.mode = value + return + } + } + + // constant rhs must be >= 0 + if y.mode == constant_ && constant.Sign(y.val) < 0 { + check.invalidOp(y.pos(), "shift count %s must not be negative", y) + } + + // non-constant shift - lhs must be an integer + if !isInteger(x.typ) { + check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + x.mode = value +} + +var binaryOpPredicates = opPredicates{ + token.ADD: func(typ Type) bool { return isNumeric(typ) || isString(typ) }, + token.SUB: isNumeric, + token.MUL: isNumeric, + token.QUO: isNumeric, + token.REM: isInteger, + + token.AND: isInteger, + token.OR: isInteger, + token.XOR: isInteger, + token.AND_NOT: isInteger, + + token.LAND: isBoolean, + token.LOR: isBoolean, +} + +// The binary expression e may be nil. It's passed in for better error messages only. +func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token) { + var y operand + + check.expr(x, lhs) + check.expr(&y, rhs) + + if x.mode == invalid { + return + } + if y.mode == invalid { + x.mode = invalid + x.expr = y.expr + return + } + + if isShift(op) { + check.shift(x, &y, op) + return + } + + check.convertUntyped(x, y.typ) + if x.mode == invalid { + return + } + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + x.mode = invalid + return + } + + if isComparison(op) { + check.comparison(x, &y, op) + return + } + + if !Identical(x.typ, y.typ) { + // only report an error if we have valid types + // (otherwise we had an error reported elsewhere already) + if x.typ != Typ[Invalid] && y.typ != Typ[Invalid] { + check.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + } + x.mode = invalid + return + } + + if !check.op(binaryOpPredicates, x, op) { + x.mode = invalid + return + } + + if (op == token.QUO || op == token.REM) && (x.mode == constant_ || isInteger(x.typ)) && y.mode == constant_ && constant.Sign(y.val) == 0 { + check.invalidOp(y.pos(), "division by zero") + x.mode = invalid + return + } + + if x.mode == constant_ && y.mode == constant_ { + typ := x.typ.Underlying().(*Basic) + // force integer division of integer operands + if op == token.QUO && isInteger(typ) { + op = token.QUO_ASSIGN + } + x.val = constant.BinaryOp(x.val, op, y.val) + // Typed constants must be representable in + // their type after each constant operation. + if isTyped(typ) { + if e != nil { + x.expr = e // for better error message + } + check.representable(x, typ) + } + return + } + + x.mode = value + // x.typ is unchanged +} + +// index checks an index expression for validity. +// If max >= 0, it is the upper bound for index. +// If index is valid and the result i >= 0, then i is the constant value of index. +func (check *Checker) index(index ast.Expr, max int64) (i int64, valid bool) { + var x operand + check.expr(&x, index) + if x.mode == invalid { + return + } + + // an untyped constant must be representable as Int + check.convertUntyped(&x, Typ[Int]) + if x.mode == invalid { + return + } + + // the index must be of integer type + if !isInteger(x.typ) { + check.invalidArg(x.pos(), "index %s must be integer", &x) + return + } + + // a constant index i must be in bounds + if x.mode == constant_ { + if constant.Sign(x.val) < 0 { + check.invalidArg(x.pos(), "index %s must not be negative", &x) + return + } + i, valid = constant.Int64Val(x.val) + if !valid || max >= 0 && i >= max { + check.errorf(x.pos(), "index %s is out of bounds", &x) + return i, false + } + // 0 <= i [ && i < max ] + return i, true + } + + return -1, true +} + +// indexElts checks the elements (elts) of an array or slice composite literal +// against the literal's element type (typ), and the element indices against +// the literal length if known (length >= 0). It returns the length of the +// literal (maximum index value + 1). +// +func (check *Checker) indexedElts(elts []ast.Expr, typ Type, length int64) int64 { + visited := make(map[int64]bool, len(elts)) + var index, max int64 + for _, e := range elts { + // determine and check index + validIndex := false + eval := e + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + if i, ok := check.index(kv.Key, length); ok { + if i >= 0 { + index = i + validIndex = true + } else { + check.errorf(e.Pos(), "index %s must be integer constant", kv.Key) + } + } + eval = kv.Value + } else if length >= 0 && index >= length { + check.errorf(e.Pos(), "index %d is out of bounds (>= %d)", index, length) + } else { + validIndex = true + } + + // if we have a valid index, check for duplicate entries + if validIndex { + if visited[index] { + check.errorf(e.Pos(), "duplicate index %d in array or slice literal", index) + } + visited[index] = true + } + index++ + if index > max { + max = index + } + + // check element against composite literal element type + var x operand + check.exprWithHint(&x, eval, typ) + if !check.assignment(&x, typ) && x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in array or slice literal", &x, typ) + } + } + return max +} + +// exprKind describes the kind of an expression; the kind +// determines if an expression is valid in 'statement context'. +type exprKind int + +const ( + conversion exprKind = iota + expression + statement +) + +// rawExpr typechecks expression e and initializes x with the expression +// value or type. If an error occurred, x.mode is set to invalid. +// If hint != nil, it is the type of a composite literal element. +// +func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind { + if trace { + check.trace(e.Pos(), "%s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", x) + }() + } + + kind := check.exprInternal(x, e, hint) + + // convert x into a user-friendly set of values + // TODO(gri) this code can be simplified + var typ Type + var val constant.Value + switch x.mode { + case invalid: + typ = Typ[Invalid] + case novalue: + typ = (*Tuple)(nil) + case constant_: + typ = x.typ + val = x.val + default: + typ = x.typ + } + assert(x.expr != nil && typ != nil) + + if isUntyped(typ) { + // delay type and value recording until we know the type + // or until the end of type checking + check.rememberUntyped(x.expr, false, x.mode, typ.(*Basic), val) + } else { + check.recordTypeAndValue(e, x.mode, typ, val) + } + + return kind +} + +// exprInternal contains the core of type checking of expressions. +// Must only be called by rawExpr. +// +func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { + // make sure x has a valid state in case of bailout + // (was issue 5770) + x.mode = invalid + x.typ = Typ[Invalid] + + switch e := e.(type) { + case *ast.BadExpr: + goto Error // error was reported before + + case *ast.Ident: + check.ident(x, e, nil, nil) + + case *ast.Ellipsis: + // ellipses are handled explicitly where they are legal + // (array composite literals and parameter lists) + check.error(e.Pos(), "invalid use of '...'") + goto Error + + case *ast.BasicLit: + x.setConst(e.Kind, e.Value) + if x.mode == invalid { + check.invalidAST(e.Pos(), "invalid literal %v", e.Value) + goto Error + } + + case *ast.FuncLit: + if sig, ok := check.typ(e.Type).(*Signature); ok { + // Anonymous functions are considered part of the + // init expression/func declaration which contains + // them: use existing package-level declaration info. + check.funcBody(check.decl, "", sig, e.Body) + x.mode = value + x.typ = sig + } else { + check.invalidAST(e.Pos(), "invalid function literal %s", e) + goto Error + } + + case *ast.CompositeLit: + typ := hint + openArray := false + if e.Type != nil { + // [...]T array types may only appear with composite literals. + // Check for them here so we don't have to handle ... in general. + typ = nil + if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil { + if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil { + // We have an "open" [...]T array type. + // Create a new ArrayType with unknown length (-1) + // and finish setting it up after analyzing the literal. + typ = &Array{len: -1, elem: check.typ(atyp.Elt)} + openArray = true + } + } + if typ == nil { + typ = check.typ(e.Type) + } + } + if typ == nil { + // TODO(gri) provide better error messages depending on context + check.error(e.Pos(), "missing type in composite literal") + goto Error + } + + switch typ, _ := deref(typ); utyp := typ.Underlying().(type) { + case *Struct: + if len(e.Elts) == 0 { + break + } + fields := utyp.fields + if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok { + // all elements must have keys + visited := make([]bool, len(fields)) + for _, e := range e.Elts { + kv, _ := e.(*ast.KeyValueExpr) + if kv == nil { + check.error(e.Pos(), "mixture of field:value and value elements in struct literal") + continue + } + key, _ := kv.Key.(*ast.Ident) + if key == nil { + check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key) + continue + } + i := fieldIndex(utyp.fields, check.pkg, key.Name) + if i < 0 { + check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name) + continue + } + fld := fields[i] + check.recordUse(key, fld) + // 0 <= i < len(fields) + if visited[i] { + check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) + continue + } + visited[i] = true + check.expr(x, kv.Value) + etyp := fld.typ + if !check.assignment(x, etyp) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + } + continue + } + } + } else { + // no element must have a key + for i, e := range e.Elts { + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + check.error(kv.Pos(), "mixture of field:value and value elements in struct literal") + continue + } + check.expr(x, e) + if i >= len(fields) { + check.error(x.pos(), "too many values in struct literal") + break // cannot continue + } + // i < len(fields) + fld := fields[i] + if !fld.Exported() && fld.pkg != check.pkg { + check.errorf(x.pos(), "implicit assignment to unexported field %s in %s literal", fld.name, typ) + continue + } + etyp := fld.typ + if !check.assignment(x, etyp) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + } + continue + } + } + if len(e.Elts) < len(fields) { + check.error(e.Rbrace, "too few values in struct literal") + // ok to continue + } + } + + case *Array: + n := check.indexedElts(e.Elts, utyp.elem, utyp.len) + // if we have an "open" [...]T array, set the length now that we know it + if openArray { + utyp.len = n + } + + case *Slice: + check.indexedElts(e.Elts, utyp.elem, -1) + + case *Map: + visited := make(map[interface{}][]Type, len(e.Elts)) + for _, e := range e.Elts { + kv, _ := e.(*ast.KeyValueExpr) + if kv == nil { + check.error(e.Pos(), "missing key in map literal") + continue + } + check.exprWithHint(x, kv.Key, utyp.key) + if !check.assignment(x, utyp.key) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.key) + } + continue + } + if x.mode == constant_ { + duplicate := false + // if the key is of interface type, the type is also significant when checking for duplicates + if _, ok := utyp.key.Underlying().(*Interface); ok { + for _, vtyp := range visited[x.val] { + if Identical(vtyp, x.typ) { + duplicate = true + break + } + } + visited[x.val] = append(visited[x.val], x.typ) + } else { + _, duplicate = visited[x.val] + visited[x.val] = nil + } + if duplicate { + check.errorf(x.pos(), "duplicate key %s in map literal", x.val) + continue + } + } + check.exprWithHint(x, kv.Value, utyp.elem) + if !check.assignment(x, utyp.elem) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.elem) + } + continue + } + } + + default: + // if utyp is invalid, an error was reported before + if utyp != Typ[Invalid] { + check.errorf(e.Pos(), "invalid composite literal type %s", typ) + goto Error + } + } + + x.mode = value + x.typ = typ + + case *ast.ParenExpr: + kind := check.rawExpr(x, e.X, nil) + x.expr = e + return kind + + case *ast.SelectorExpr: + check.selector(x, e) + + case *ast.IndexExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := x.typ.Underlying().(type) { + case *Basic: + if isString(typ) { + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // an indexed string always yields a byte value + // (not a constant) even if the string and the + // index are constant + x.mode = value + x.typ = universeByte // use 'byte' name + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + x.mode = value + } + x.typ = typ.elem + + case *Pointer: + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + valid = true + length = typ.len + x.mode = variable + x.typ = typ.elem + } + + case *Slice: + valid = true + x.mode = variable + x.typ = typ.elem + + case *Map: + var key operand + check.expr(&key, e.Index) + if !check.assignment(&key, typ.key) { + if key.mode != invalid { + check.invalidOp(key.pos(), "cannot use %s as map index of type %s", &key, typ.key) + } + goto Error + } + x.mode = mapindex + x.typ = typ.elem + x.expr = e + return expression + } + + if !valid { + check.invalidOp(x.pos(), "cannot index %s", x) + goto Error + } + + if e.Index == nil { + check.invalidAST(e.Pos(), "missing index for %s", x) + goto Error + } + + check.index(e.Index, length) + // ok to continue + + case *ast.SliceExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := x.typ.Underlying().(type) { + case *Basic: + if isString(typ) { + if slice3(e) { + check.invalidOp(x.pos(), "3-index slice of string") + goto Error + } + valid = true + if x.mode == constant_ { + length = int64(len(constant.StringVal(x.val))) + } + // spec: "For untyped string operands the result + // is a non-constant value of type string." + if typ.kind == UntypedString { + x.typ = Typ[String] + } + } + + case *Array: + valid = true + length = typ.len + if x.mode != variable { + check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x) + goto Error + } + x.typ = &Slice{elem: typ.elem} + + case *Pointer: + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + valid = true + length = typ.len + x.typ = &Slice{elem: typ.elem} + } + + case *Slice: + valid = true + // x.typ doesn't change + } + + if !valid { + check.invalidOp(x.pos(), "cannot slice %s", x) + goto Error + } + + x.mode = value + + // spec: "Only the first index may be omitted; it defaults to 0." + if slice3(e) && (e.High == nil || sliceMax(e) == nil) { + check.error(e.Rbrack, "2nd and 3rd index required in 3-index slice") + goto Error + } + + // check indices + var ind [3]int64 + for i, expr := range []ast.Expr{e.Low, e.High, sliceMax(e)} { + x := int64(-1) + switch { + case expr != nil: + // The "capacity" is only known statically for strings, arrays, + // and pointers to arrays, and it is the same as the length for + // those types. + max := int64(-1) + if length >= 0 { + max = length + 1 + } + if t, ok := check.index(expr, max); ok && t >= 0 { + x = t + } + case i == 0: + // default is 0 for the first index + x = 0 + case length >= 0: + // default is length (== capacity) otherwise + x = length + } + ind[i] = x + } + + // constant indices must be in range + // (check.index already checks that existing indices >= 0) + L: + for i, x := range ind[:len(ind)-1] { + if x > 0 { + for _, y := range ind[i+1:] { + if y >= 0 && x > y { + check.errorf(e.Rbrack, "invalid slice indices: %d > %d", x, y) + break L // only report one error, ok to continue + } + } + } + } + + case *ast.TypeAssertExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { + check.invalidOp(x.pos(), "%s is not an interface", x) + goto Error + } + // x.(type) expressions are handled explicitly in type switches + if e.Type == nil { + check.invalidAST(e.Pos(), "use of .(type) outside type switch") + goto Error + } + T := check.typ(e.Type) + if T == Typ[Invalid] { + goto Error + } + check.typeAssertion(x.pos(), x, xtyp, T) + x.mode = commaok + x.typ = T + + case *ast.CallExpr: + return check.call(x, e) + + case *ast.StarExpr: + check.exprOrType(x, e.X) + switch x.mode { + case invalid: + goto Error + case typexpr: + x.typ = &Pointer{base: x.typ} + default: + if typ, ok := x.typ.Underlying().(*Pointer); ok { + x.mode = variable + x.typ = typ.base + } else { + check.invalidOp(x.pos(), "cannot indirect %s", x) + goto Error + } + } + + case *ast.UnaryExpr: + check.expr(x, e.X) + if x.mode == invalid { + goto Error + } + check.unary(x, e, e.Op) + if x.mode == invalid { + goto Error + } + if e.Op == token.ARROW { + x.expr = e + return statement // receive operations may appear in statement context + } + + case *ast.BinaryExpr: + check.binary(x, e, e.X, e.Y, e.Op) + if x.mode == invalid { + goto Error + } + + case *ast.KeyValueExpr: + // key:value expressions are handled in composite literals + check.invalidAST(e.Pos(), "no key:value expected") + goto Error + + case *ast.ArrayType, *ast.StructType, *ast.FuncType, + *ast.InterfaceType, *ast.MapType, *ast.ChanType: + x.mode = typexpr + x.typ = check.typ(e) + // Note: rawExpr (caller of exprInternal) will call check.recordTypeAndValue + // even though check.typ has already called it. This is fine as both + // times the same expression and type are recorded. It is also not a + // performance issue because we only reach here for composite literal + // types, which are comparatively rare. + + default: + panic(fmt.Sprintf("%s: unknown expression type %T", check.fset.Position(e.Pos()), e)) + } + + // everything went well + x.expr = e + return expression + +Error: + x.mode = invalid + x.expr = e + return statement // avoid follow-up errors +} + +// typeAssertion checks that x.(T) is legal; xtyp must be the type of x. +func (check *Checker) typeAssertion(pos token.Pos, x *operand, xtyp *Interface, T Type) { + method, wrongType := assertableTo(xtyp, T) + if method == nil { + return + } + + var msg string + if wrongType { + msg = "wrong type for method" + } else { + msg = "missing method" + } + check.errorf(pos, "%s cannot have dynamic type %s (%s %s)", x, T, msg, method.name) +} + +// expr typechecks expression e and initializes x with the expression value. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) expr(x *operand, e ast.Expr) { + check.rawExpr(x, e, nil) + var msg string + switch x.mode { + default: + return + case novalue: + msg = "used as value" + case builtin: + msg = "must be called" + case typexpr: + msg = "is not an expression" + } + check.errorf(x.pos(), "%s %s", x, msg) + x.mode = invalid +} + +// exprWithHint typechecks expression e and initializes x with the expression value. +// If an error occurred, x.mode is set to invalid. +// If hint != nil, it is the type of a composite literal element. +// +func (check *Checker) exprWithHint(x *operand, e ast.Expr, hint Type) { + assert(hint != nil) + check.rawExpr(x, e, hint) + var msg string + switch x.mode { + default: + return + case novalue: + msg = "used as value" + case builtin: + msg = "must be called" + case typexpr: + msg = "is not an expression" + } + check.errorf(x.pos(), "%s %s", x, msg) + x.mode = invalid +} + +// exprOrType typechecks expression or type e and initializes x with the expression value or type. +// If an error occurred, x.mode is set to invalid. +// +func (check *Checker) exprOrType(x *operand, e ast.Expr) { + check.rawExpr(x, e, nil) + if x.mode == novalue { + check.errorf(x.pos(), "%s used as value or type", x) + x.mode = invalid + } +} diff --git a/libgo/go/go/types/exprstring.go b/libgo/go/go/types/exprstring.go new file mode 100644 index 00000000000..370bdf35324 --- /dev/null +++ b/libgo/go/go/types/exprstring.go @@ -0,0 +1,220 @@ +// Copyright 2013 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 printing of expressions. + +package types + +import ( + "bytes" + "go/ast" +) + +// ExprString returns the (possibly simplified) string representation for x. +func ExprString(x ast.Expr) string { + var buf bytes.Buffer + WriteExpr(&buf, x) + return buf.String() +} + +// WriteExpr writes the (possibly simplified) string representation for x to buf. +func WriteExpr(buf *bytes.Buffer, x ast.Expr) { + // The AST preserves source-level parentheses so there is + // no need to introduce them here to correct for different + // operator precedences. (This assumes that the AST was + // generated by a Go parser.) + + switch x := x.(type) { + default: + buf.WriteString("(bad expr)") // nil, ast.BadExpr, ast.KeyValueExpr + + case *ast.Ident: + buf.WriteString(x.Name) + + case *ast.Ellipsis: + buf.WriteString("...") + if x.Elt != nil { + WriteExpr(buf, x.Elt) + } + + case *ast.BasicLit: + buf.WriteString(x.Value) + + case *ast.FuncLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // simplified + + case *ast.CompositeLit: + buf.WriteByte('(') + WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // simplified + + case *ast.ParenExpr: + buf.WriteByte('(') + WriteExpr(buf, x.X) + buf.WriteByte(')') + + case *ast.SelectorExpr: + WriteExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Name) + + case *ast.IndexExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + WriteExpr(buf, x.Index) + buf.WriteByte(']') + + case *ast.SliceExpr: + WriteExpr(buf, x.X) + buf.WriteByte('[') + if x.Low != nil { + WriteExpr(buf, x.Low) + } + buf.WriteByte(':') + if x.High != nil { + WriteExpr(buf, x.High) + } + if x.Slice3 { + buf.WriteByte(':') + if x.Max != nil { + WriteExpr(buf, x.Max) + } + } + buf.WriteByte(']') + + case *ast.TypeAssertExpr: + WriteExpr(buf, x.X) + buf.WriteString(".(") + WriteExpr(buf, x.Type) + buf.WriteByte(')') + + case *ast.CallExpr: + WriteExpr(buf, x.Fun) + buf.WriteByte('(') + for i, arg := range x.Args { + if i > 0 { + buf.WriteString(", ") + } + WriteExpr(buf, arg) + } + if x.Ellipsis.IsValid() { + buf.WriteString("...") + } + buf.WriteByte(')') + + case *ast.StarExpr: + buf.WriteByte('*') + WriteExpr(buf, x.X) + + case *ast.UnaryExpr: + buf.WriteString(x.Op.String()) + WriteExpr(buf, x.X) + + case *ast.BinaryExpr: + WriteExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + WriteExpr(buf, x.Y) + + case *ast.ArrayType: + buf.WriteByte('[') + if x.Len != nil { + WriteExpr(buf, x.Len) + } + buf.WriteByte(']') + WriteExpr(buf, x.Elt) + + case *ast.StructType: + buf.WriteString("struct{") + writeFieldList(buf, x.Fields, "; ", false) + buf.WriteByte('}') + + case *ast.FuncType: + buf.WriteString("func") + writeSigExpr(buf, x) + + case *ast.InterfaceType: + buf.WriteString("interface{") + writeFieldList(buf, x.Methods, "; ", true) + buf.WriteByte('}') + + case *ast.MapType: + buf.WriteString("map[") + WriteExpr(buf, x.Key) + buf.WriteByte(']') + WriteExpr(buf, x.Value) + + case *ast.ChanType: + var s string + switch x.Dir { + case ast.SEND: + s = "chan<- " + case ast.RECV: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + WriteExpr(buf, x.Value) + } +} + +func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { + buf.WriteByte('(') + writeFieldList(buf, sig.Params, ", ", false) + buf.WriteByte(')') + + res := sig.Results + n := res.NumFields() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && len(res.List[0].Names) == 0 { + // single unnamed result + WriteExpr(buf, res.List[0].Type) + return + } + + // multiple or named result(s) + buf.WriteByte('(') + writeFieldList(buf, res, ", ", false) + buf.WriteByte(')') +} + +func writeFieldList(buf *bytes.Buffer, fields *ast.FieldList, sep string, iface bool) { + for i, f := range fields.List { + if i > 0 { + buf.WriteString(sep) + } + + // field list names + for i, name := range f.Names { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(name.Name) + } + + // types of interface methods consist of signatures only + if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { + writeSigExpr(buf, sig) + continue + } + + // named fields are separated with a blank from the field type + if len(f.Names) > 0 { + buf.WriteByte(' ') + } + + WriteExpr(buf, f.Type) + + // ignore tag + } +} diff --git a/libgo/go/go/types/exprstring_test.go b/libgo/go/go/types/exprstring_test.go new file mode 100644 index 00000000000..51102881c9f --- /dev/null +++ b/libgo/go/go/types/exprstring_test.go @@ -0,0 +1,94 @@ +// Copyright 2013 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 ( + "go/parser" + "testing" + + . "go/types" +) + +var testExprs = []testEntry{ + // basic type literals + dup("x"), + dup("true"), + dup("42"), + dup("3.1415"), + dup("2.71828i"), + dup(`'a'`), + dup(`"foo"`), + dup("`bar`"), + + // func and composite literals + {"func(){}", "(func() literal)"}, + {"func(x int) complex128 {}", "(func(x int) complex128 literal)"}, + {"[]int{1, 2, 3}", "([]int literal)"}, + + // non-type expressions + dup("(x)"), + dup("x.f"), + dup("a[i]"), + + dup("s[:]"), + dup("s[i:]"), + dup("s[:j]"), + dup("s[i:j]"), + dup("s[:j:k]"), + dup("s[i:j:k]"), + + dup("x.(T)"), + + dup("x.([10]int)"), + dup("x.([...]int)"), + + dup("x.(struct{})"), + dup("x.(struct{x int; y, z float32; E})"), + + dup("x.(func())"), + dup("x.(func(x int))"), + dup("x.(func() int)"), + dup("x.(func(x, y int, z float32) (r int))"), + dup("x.(func(a, b, c int))"), + dup("x.(func(x ...T))"), + + dup("x.(interface{})"), + dup("x.(interface{m(); n(x int); E})"), + dup("x.(interface{m(); n(x int) T; E; F})"), + + dup("x.(map[K]V)"), + + dup("x.(chan E)"), + dup("x.(<-chan E)"), + dup("x.(chan<- chan int)"), + dup("x.(chan<- <-chan int)"), + dup("x.(<-chan chan int)"), + dup("x.(chan (<-chan int))"), + + dup("f()"), + dup("f(x)"), + dup("int(x)"), + dup("f(x, x + y)"), + dup("f(s...)"), + dup("f(a, s...)"), + + dup("*x"), + dup("&x"), + dup("x + y"), + dup("x + y << (2 * s)"), +} + +func TestExprString(t *testing.T) { + for _, test := range testExprs { + x, err := parser.ParseExpr(test.src) + if err != nil { + t.Errorf("%s: %s", test.src, err) + continue + } + if got := ExprString(x); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} diff --git a/libgo/go/go/types/go11.go b/libgo/go/go/types/go11.go new file mode 100644 index 00000000000..cf41cabeeac --- /dev/null +++ b/libgo/go/go/types/go11.go @@ -0,0 +1,17 @@ +// Copyright 2013 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. + +// +build !go1.2 + +package types + +import "go/ast" + +func slice3(x *ast.SliceExpr) bool { + return false +} + +func sliceMax(x *ast.SliceExpr) ast.Expr { + return nil +} diff --git a/libgo/go/go/types/go12.go b/libgo/go/go/types/go12.go new file mode 100644 index 00000000000..20174421540 --- /dev/null +++ b/libgo/go/go/types/go12.go @@ -0,0 +1,17 @@ +// Copyright 2013 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. + +// +build go1.2 + +package types + +import "go/ast" + +func slice3(x *ast.SliceExpr) bool { + return x.Slice3 +} + +func sliceMax(x *ast.SliceExpr) ast.Expr { + return x.Max +} diff --git a/libgo/go/go/types/hilbert_test.go b/libgo/go/go/types/hilbert_test.go new file mode 100644 index 00000000000..cfd51b1d649 --- /dev/null +++ b/libgo/go/go/types/hilbert_test.go @@ -0,0 +1,234 @@ +// Copyright 2013 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 ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "io/ioutil" + "testing" + + . "go/types" +) + +var ( + H = flag.Int("H", 5, "Hilbert matrix size") + out = flag.String("out", "", "write generated program to out") +) + +func TestHilbert(t *testing.T) { + // generate source + src := program(*H, *out) + if *out != "" { + ioutil.WriteFile(*out, src, 0666) + return + } + + // parse source + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hilbert.go", src, 0) + if err != nil { + t.Fatal(err) + } + + // type-check file + DefPredeclaredTestFuncs() // define assert built-in + conf := Config{Importer: importer.Default()} + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) + if err != nil { + t.Fatal(err) + } +} + +func program(n int, out string) []byte { + var g gen + + g.p(`// WARNING: GENERATED FILE - DO NOT MODIFY MANUALLY! +// (To generate, in go/types directory: go test -run=Hilbert -H=%d -out=%q) + +// This program tests arbitrary precision constant arithmetic +// by generating the constant elements of a Hilbert matrix H, +// its inverse I, and the product P = H*I. The product should +// be the identity matrix. +package main + +func main() { + if !ok { + printProduct() + return + } + println("PASS") +} + +`, n, out) + g.hilbert(n) + g.inverse(n) + g.product(n) + g.verify(n) + g.printProduct(n) + g.binomials(2*n - 1) + g.factorials(2*n - 1) + + return g.Bytes() +} + +type gen struct { + bytes.Buffer +} + +func (g *gen) p(format string, args ...interface{}) { + fmt.Fprintf(&g.Buffer, format, args...) +} + +func (g *gen) hilbert(n int) { + g.p(`// Hilbert matrix, n = %d +const ( +`, n) + for i := 0; i < n; i++ { + g.p("\t") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("h%d_%d", i, j) + } + if i == 0 { + g.p(" = ") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("1.0/(iota + %d)", j+1) + } + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) inverse(n int) { + g.p(`// Inverse Hilbert matrix +const ( +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + s := "+" + if (i+j)&1 != 0 { + s = "-" + } + g.p("\ti%d_%d = %s%d * b%d_%d * b%d_%d * b%d_%d * b%d_%d\n", + i, j, s, i+j+1, n+i, n-j-1, n+j, n-i-1, i+j, i, i+j, i) + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) product(n int) { + g.p(`// Product matrix +const ( +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + g.p("\tp%d_%d = ", i, j) + for k := 0; k < n; k++ { + if k > 0 { + g.p(" + ") + } + g.p("h%d_%d*i%d_%d", i, k, k, j) + } + g.p("\n") + } + g.p("\n") + } + g.p(")\n\n") +} + +func (g *gen) verify(n int) { + g.p(`// Verify that product is the identity matrix +const ok = +`) + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + if j == 0 { + g.p("\t") + } else { + g.p(" && ") + } + v := 0 + if i == j { + v = 1 + } + g.p("p%d_%d == %d", i, j, v) + } + g.p(" &&\n") + } + g.p("\ttrue\n\n") + + // verify ok at type-check time + if *out == "" { + g.p("const _ = assert(ok)\n\n") + } +} + +func (g *gen) printProduct(n int) { + g.p("func printProduct() {\n") + for i := 0; i < n; i++ { + g.p("\tprintln(") + for j := 0; j < n; j++ { + if j > 0 { + g.p(", ") + } + g.p("p%d_%d", i, j) + } + g.p(")\n") + } + g.p("}\n\n") +} + +func (g *gen) mulRange(a, b int) { + if a > b { + g.p("1") + return + } + for i := a; i <= b; i++ { + if i > a { + g.p("*") + } + g.p("%d", i) + } +} + +func (g *gen) binomials(n int) { + g.p(`// Binomials +const ( +`) + for j := 0; j <= n; j++ { + if j > 0 { + g.p("\n") + } + for k := 0; k <= j; k++ { + g.p("\tb%d_%d = f%d / (f%d*f%d)\n", j, k, j, k, j-k) + } + } + g.p(")\n\n") +} + +func (g *gen) factorials(n int) { + g.p(`// Factorials +const ( + f0 = 1 + f1 = 1 +`) + for i := 2; i <= n; i++ { + g.p("\tf%d = f%d * %d\n", i, i-1, i) + } + g.p(")\n\n") +} diff --git a/libgo/go/go/types/initorder.go b/libgo/go/go/types/initorder.go new file mode 100644 index 00000000000..0fd567b2691 --- /dev/null +++ b/libgo/go/go/types/initorder.go @@ -0,0 +1,222 @@ +// Copyright 2014 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 ( + "container/heap" + "fmt" +) + +// initOrder computes the Info.InitOrder for package variables. +func (check *Checker) initOrder() { + // An InitOrder may already have been computed if a package is + // built from several calls to (*Checker).Files. Clear it. + check.Info.InitOrder = check.Info.InitOrder[:0] + + // compute the object dependency graph and + // initialize a priority queue with the list + // of graph nodes + pq := nodeQueue(dependencyGraph(check.objMap)) + heap.Init(&pq) + + const debug = false + if debug { + fmt.Printf("package %s: object dependency graph\n", check.pkg.Name()) + for _, n := range pq { + for _, o := range n.out { + fmt.Printf("\t%s -> %s\n", n.obj.Name(), o.obj.Name()) + } + } + fmt.Println() + fmt.Printf("package %s: initialization order\n", check.pkg.Name()) + } + + // determine initialization order by removing the highest priority node + // (the one with the fewest dependencies) and its edges from the graph, + // repeatedly, until there are no nodes left. + // In a valid Go program, those nodes always have zero dependencies (after + // removing all incoming dependencies), otherwise there are initialization + // cycles. + mark := 0 + emitted := make(map[*declInfo]bool) + for len(pq) > 0 { + // get the next node + n := heap.Pop(&pq).(*objNode) + + // if n still depends on other nodes, we have a cycle + if n.in > 0 { + mark++ // mark nodes using a different value each time + cycle := findPath(n, n, mark) + if i := valIndex(cycle); i >= 0 { + check.reportCycle(cycle, i) + } + // ok to continue, but the variable initialization order + // will be incorrect at this point since it assumes no + // cycle errors + } + + // reduce dependency count of all dependent nodes + // and update priority queue + for _, out := range n.out { + out.in-- + heap.Fix(&pq, out.index) + } + + // record the init order for variables with initializers only + v, _ := n.obj.(*Var) + info := check.objMap[v] + if v == nil || !info.hasInitializer() { + continue + } + + // n:1 variable declarations such as: a, b = f() + // introduce a node for each lhs variable (here: a, b); + // but they all have the same initializer - emit only + // one, for the first variable seen + if emitted[info] { + continue // initializer already emitted, if any + } + emitted[info] = true + + infoLhs := info.lhs // possibly nil (see declInfo.lhs field comment) + if infoLhs == nil { + infoLhs = []*Var{v} + } + init := &Initializer{infoLhs, info.init} + check.Info.InitOrder = append(check.Info.InitOrder, init) + + if debug { + fmt.Printf("\t%s\n", init) + } + } + + if debug { + fmt.Println() + } +} + +// findPath returns the (reversed) list of nodes z, ... c, b, a, +// such that there is a path (list of edges) from a to z. +// If there is no such path, the result is nil. +// Nodes marked with the value mark are considered "visited"; +// unvisited nodes are marked during the graph search. +func findPath(a, z *objNode, mark int) []*objNode { + if a.mark == mark { + return nil // node already seen + } + a.mark = mark + + for _, n := range a.out { + if n == z { + return []*objNode{z} + } + if P := findPath(n, z, mark); P != nil { + return append(P, n) + } + } + + return nil +} + +// valIndex returns the index of the first constant or variable in a, +// if any; or a value < 0. +func valIndex(a []*objNode) int { + for i, n := range a { + switch n.obj.(type) { + case *Const, *Var: + return i + } + } + return -1 +} + +// reportCycle reports an error for the cycle starting at i. +func (check *Checker) reportCycle(cycle []*objNode, i int) { + obj := cycle[i].obj + check.errorf(obj.Pos(), "initialization cycle for %s", obj.Name()) + // print cycle + for _ = range cycle { + check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented + i++ + if i >= len(cycle) { + i = 0 + } + obj = cycle[i].obj + } + check.errorf(obj.Pos(), "\t%s", obj.Name()) +} + +// An objNode represents a node in the object dependency graph. +// Each node b in a.out represents an edge a->b indicating that +// b depends on a. +// Nodes may be marked for cycle detection. A node n is marked +// if n.mark corresponds to the current mark value. +type objNode struct { + obj Object // object represented by this node + in int // number of nodes this node depends on + out []*objNode // list of nodes that depend on this node + index int // node index in list of nodes + mark int // for cycle detection +} + +// dependencyGraph computes the transposed object dependency graph +// from the given objMap. The transposed graph is returned as a list +// of nodes; an edge d->n indicates that node n depends on node d. +func dependencyGraph(objMap map[Object]*declInfo) []*objNode { + // M maps each object to its corresponding node + M := make(map[Object]*objNode, len(objMap)) + for obj := range objMap { + M[obj] = &objNode{obj: obj} + } + + // G is the graph of nodes n + G := make([]*objNode, len(M)) + i := 0 + for obj, n := range M { + deps := objMap[obj].deps + n.in = len(deps) + for d := range deps { + d := M[d] // node n depends on node d + d.out = append(d.out, n) // add edge d->n + } + + G[i] = n + n.index = i + i++ + } + + return G +} + +// nodeQueue implements the container/heap interface; +// a nodeQueue may be used as a priority queue. +type nodeQueue []*objNode + +func (a nodeQueue) Len() int { return len(a) } + +func (a nodeQueue) Swap(i, j int) { + x, y := a[i], a[j] + a[i], a[j] = y, x + x.index, y.index = j, i +} + +func (a nodeQueue) Less(i, j int) bool { + x, y := a[i], a[j] + // nodes are prioritized by number of incoming dependencies (1st key) + // and source order (2nd key) + return x.in < y.in || x.in == y.in && x.obj.order() < y.obj.order() +} + +func (a *nodeQueue) Push(x interface{}) { + panic("unreachable") +} + +func (a *nodeQueue) Pop() interface{} { + n := len(*a) + x := (*a)[n-1] + x.index = -1 // for safety + *a = (*a)[:n-1] + return x +} diff --git a/libgo/go/go/types/issues_test.go b/libgo/go/go/types/issues_test.go new file mode 100644 index 00000000000..672c78dfc20 --- /dev/null +++ b/libgo/go/go/types/issues_test.go @@ -0,0 +1,206 @@ +// Copyright 2013 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 tests for various issues. + +package types_test + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "sort" + "strings" + "testing" + + . "go/types" +) + +func TestIssue5770(t *testing.T) { + src := `package p; type S struct{T}` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + conf := Config{Importer: importer.Default()} + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, nil) // do not crash + want := "undeclared name: T" + if err == nil || !strings.Contains(err.Error(), want) { + t.Errorf("got: %v; want: %s", err, want) + } +} + +func TestIssue5849(t *testing.T) { + src := ` +package p +var ( + s uint + _ = uint8(8) + _ = uint16(16) << s + _ = uint32(32 << s) + _ = uint64(64 << s + s) + _ = (interface{})("foo") + _ = (interface{})(nil) +)` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var conf Config + types := make(map[ast.Expr]TypeAndValue) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + for x, tv := range types { + var want Type + switch x := x.(type) { + case *ast.BasicLit: + switch x.Value { + case `8`: + want = Typ[Uint8] + case `16`: + want = Typ[Uint16] + case `32`: + want = Typ[Uint32] + case `64`: + want = Typ[Uint] // because of "+ s", s is of type uint + case `"foo"`: + want = Typ[String] + } + case *ast.Ident: + if x.Name == "nil" { + want = Typ[UntypedNil] + } + } + if want != nil && !Identical(tv.Type, want) { + t.Errorf("got %s; want %s", tv.Type, want) + } + } +} + +func TestIssue6413(t *testing.T) { + src := ` +package p +func f() int { + defer f() + go f() + return 0 +} +` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var conf Config + types := make(map[ast.Expr]TypeAndValue) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) + if err != nil { + t.Fatal(err) + } + + want := Typ[Int] + n := 0 + for x, tv := range types { + if _, ok := x.(*ast.CallExpr); ok { + if tv.Type != want { + t.Errorf("%s: got %s; want %s", fset.Position(x.Pos()), tv.Type, want) + } + n++ + } + } + + if n != 2 { + t.Errorf("got %d CallExprs; want 2", n) + } +} + +func TestIssue7245(t *testing.T) { + src := ` +package p +func (T) m() (res bool) { return } +type T struct{} // receiver type after method declaration +` + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + var conf Config + 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 := 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) + } +} + +// This tests that uses of existing vars on the LHS of an assignment +// are Uses, not Defs; and also that the (illegal) use of a non-var on +// the LHS of an assignment is a Use nonetheless. +func TestIssue7827(t *testing.T) { + const src = ` +package p +func _() { + const w = 1 // defs w + x, y := 2, 3 // defs x, y + w, x, z := 4, 5, 6 // uses w, x, defs z; error: cannot assign to w + _, _, _ = x, y, z // uses x, y, z +} +` + const want = `L3 defs func p._() +L4 defs const w untyped int +L5 defs var x int +L5 defs var y int +L6 defs var z int +L6 uses const w untyped int +L6 uses var x int +L7 uses var x int +L7 uses var y int +L7 uses var z int` + + f, err := parser.ParseFile(fset, "", src, 0) + if err != nil { + t.Fatal(err) + } + + // don't abort at the first error + conf := Config{Error: func(err error) { t.Log(err) }} + defs := make(map[*ast.Ident]Object) + uses := make(map[*ast.Ident]Object) + _, err = conf.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Defs: defs, Uses: uses}) + if s := fmt.Sprint(err); !strings.HasSuffix(s, "cannot assign to w") { + t.Errorf("Check: unexpected error: %s", s) + } + + var facts []string + for id, obj := range defs { + if obj != nil { + fact := fmt.Sprintf("L%d defs %s", fset.Position(id.Pos()).Line, obj) + facts = append(facts, fact) + } + } + for id, obj := range uses { + fact := fmt.Sprintf("L%d uses %s", fset.Position(id.Pos()).Line, obj) + facts = append(facts, fact) + } + sort.Strings(facts) + + got := strings.Join(facts, "\n") + if got != want { + t.Errorf("Unexpected defs/uses\ngot:\n%s\nwant:\n%s", got, want) + } +} diff --git a/libgo/go/go/types/labels.go b/libgo/go/go/types/labels.go new file mode 100644 index 00000000000..7364d4dbe68 --- /dev/null +++ b/libgo/go/go/types/labels.go @@ -0,0 +1,268 @@ +// Copyright 2013 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 ( + "go/ast" + "go/token" +) + +// labels checks correct label use in body. +func (check *Checker) labels(body *ast.BlockStmt) { + // set of all labels in this body + all := NewScope(nil, body.Pos(), body.End(), "label") + + fwdJumps := check.blockBranches(all, nil, nil, body.List) + + // If there are any forward jumps left, no label was found for + // the corresponding goto statements. Either those labels were + // never defined, or they are inside blocks and not reachable + // for the respective gotos. + for _, jmp := range fwdJumps { + var msg string + name := jmp.Label.Name + if alt := all.Lookup(name); alt != nil { + msg = "goto %s jumps into block" + alt.(*Label).used = true // avoid another error + } else { + msg = "label %s not declared" + } + check.errorf(jmp.Label.Pos(), msg, name) + } + + // spec: "It is illegal to define a label that is never used." + for _, obj := range all.elems { + if lbl := obj.(*Label); !lbl.used { + check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name) + } + } +} + +// A block tracks label declarations in a block and its enclosing blocks. +type block struct { + parent *block // enclosing block + lstmt *ast.LabeledStmt // labeled statement to which this block belongs, or nil + labels map[string]*ast.LabeledStmt // allocated lazily +} + +// insert records a new label declaration for the current block. +// The label must not have been declared before in any block. +func (b *block) insert(s *ast.LabeledStmt) { + name := s.Label.Name + if debug { + assert(b.gotoTarget(name) == nil) + } + labels := b.labels + if labels == nil { + labels = make(map[string]*ast.LabeledStmt) + b.labels = labels + } + labels[name] = s +} + +// gotoTarget returns the labeled statement in the current +// or an enclosing block with the given label name, or nil. +func (b *block) gotoTarget(name string) *ast.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.labels[name]; t != nil { + return t + } + } + return nil +} + +// enclosingTarget returns the innermost enclosing labeled +// statement with the given label name, or nil. +func (b *block) enclosingTarget(name string) *ast.LabeledStmt { + for s := b; s != nil; s = s.parent { + if t := s.lstmt; t != nil && t.Label.Name == name { + return t + } + } + return nil +} + +// blockBranches processes a block's statement list and returns the set of outgoing forward jumps. +// all is the scope of all declared labels, parent the set of labels declared in the immediately +// enclosing block, and lstmt is the labeled statement this block is associated with (or nil). +func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *ast.LabeledStmt, list []ast.Stmt) []*ast.BranchStmt { + b := &block{parent: parent, lstmt: lstmt} + + var ( + varDeclPos token.Pos + fwdJumps, badJumps []*ast.BranchStmt + ) + + // All forward jumps jumping over a variable declaration are possibly + // invalid (they may still jump out of the block and be ok). + // recordVarDecl records them for the given position. + recordVarDecl := func(pos token.Pos) { + varDeclPos = pos + badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps + } + + jumpsOverVarDecl := func(jmp *ast.BranchStmt) bool { + if varDeclPos.IsValid() { + for _, bad := range badJumps { + if jmp == bad { + return true + } + } + } + return false + } + + blockBranches := func(lstmt *ast.LabeledStmt, list []ast.Stmt) { + // Unresolved forward jumps inside the nested block + // become forward jumps in the current block. + fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, list)...) + } + + var stmtBranches func(ast.Stmt) + stmtBranches = func(s ast.Stmt) { + switch s := s.(type) { + case *ast.DeclStmt: + if d, _ := s.Decl.(*ast.GenDecl); d != nil && d.Tok == token.VAR { + recordVarDecl(d.Pos()) + } + + case *ast.LabeledStmt: + // declare non-blank label + if name := s.Label.Name; name != "_" { + lbl := NewLabel(s.Label.Pos(), check.pkg, name) + if alt := all.Insert(lbl); alt != nil { + check.softErrorf(lbl.pos, "label %s already declared", name) + check.reportAltDecl(alt) + // ok to continue + } else { + b.insert(s) + check.recordDef(s.Label, lbl) + } + // resolve matching forward jumps and remove them from fwdJumps + i := 0 + for _, jmp := range fwdJumps { + if jmp.Label.Name == name { + // match + lbl.used = true + check.recordUse(jmp.Label, lbl) + if jumpsOverVarDecl(jmp) { + check.softErrorf( + jmp.Label.Pos(), + "goto %s jumps over variable declaration at line %d", + name, + check.fset.Position(varDeclPos).Line, + ) + // ok to continue + } + } else { + // no match - record new forward jump + fwdJumps[i] = jmp + i++ + } + } + fwdJumps = fwdJumps[:i] + lstmt = s + } + stmtBranches(s.Stmt) + + case *ast.BranchStmt: + if s.Label == nil { + return // checked in 1st pass (check.stmt) + } + + // determine and validate target + name := s.Label.Name + switch s.Tok { + case token.BREAK: + // spec: "If there is a label, it must be that of an enclosing + // "for", "switch", or "select" statement, and that is the one + // whose execution terminates." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label.Pos(), "invalid break label %s", name) + return + } + + case token.CONTINUE: + // spec: "If there is a label, it must be that of an enclosing + // "for" statement, and that is the one whose execution advances." + valid := false + if t := b.enclosingTarget(name); t != nil { + switch t.Stmt.(type) { + case *ast.ForStmt, *ast.RangeStmt: + valid = true + } + } + if !valid { + check.errorf(s.Label.Pos(), "invalid continue label %s", name) + return + } + + case token.GOTO: + if b.gotoTarget(name) == nil { + // label may be declared later - add branch to forward jumps + fwdJumps = append(fwdJumps, s) + return + } + + default: + check.invalidAST(s.Pos(), "branch statement: %s %s", s.Tok, name) + return + } + + // record label use + obj := all.Lookup(name) + obj.(*Label).used = true + check.recordUse(s.Label, obj) + + case *ast.AssignStmt: + if s.Tok == token.DEFINE { + recordVarDecl(s.Pos()) + } + + case *ast.BlockStmt: + blockBranches(lstmt, s.List) + + case *ast.IfStmt: + stmtBranches(s.Body) + if s.Else != nil { + stmtBranches(s.Else) + } + + case *ast.CaseClause: + blockBranches(nil, s.Body) + + case *ast.SwitchStmt: + stmtBranches(s.Body) + + case *ast.TypeSwitchStmt: + stmtBranches(s.Body) + + case *ast.CommClause: + blockBranches(nil, s.Body) + + case *ast.SelectStmt: + stmtBranches(s.Body) + + case *ast.ForStmt: + stmtBranches(s.Body) + + case *ast.RangeStmt: + stmtBranches(s.Body) + } + } + + for _, s := range list { + stmtBranches(s) + } + + return fwdJumps +} diff --git a/libgo/go/go/types/lookup.go b/libgo/go/go/types/lookup.go new file mode 100644 index 00000000000..3caca5519b6 --- /dev/null +++ b/libgo/go/go/types/lookup.go @@ -0,0 +1,341 @@ +// Copyright 2013 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 various field and method lookup functions. + +package types + +// LookupFieldOrMethod looks up a field or method with given package and name +// in T and returns the corresponding *Var or *Func, an index sequence, and a +// bool indicating if there were any pointer indirections on the path to the +// field or method. If addressable is set, T is the type of an addressable +// variable (only matters for method lookups). +// +// The last index entry is the field or method index in the (possibly embedded) +// type where the entry was found, either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of all methods (method set) of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the anonymous struct fields +// traversed to get to the found entry, starting at depth 0. +// +// If no entry is found, a nil object is returned. In this case, the returned +// index and indirect values have the following meaning: +// +// - If index != nil, the index sequence points to an ambiguous entry +// (the same name appeared more than once at the same embedding level). +// +// - If indirect is set, a method with a pointer receiver type was found +// but there was no pointer on the path from the actual receiver type to +// the method's formal receiver base type, nor was the receiver addressable. +// +func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + // Methods cannot be associated to a named pointer type + // (spec: "The type denoted by T is called the receiver base type; + // it must not be a pointer or interface type and it must be declared + // in the same package as the method."). + // Thus, if we have a named pointer type, proceed with the underlying + // pointer type but discard the result if it is a method since we would + // not have found it for T (see also issue 8590). + if t, _ := T.(*Named); t != nil { + if p, _ := t.underlying.(*Pointer); p != nil { + obj, index, indirect = lookupFieldOrMethod(p, false, pkg, name) + if _, ok := obj.(*Func); ok { + return nil, nil, false + } + return + } + } + + return lookupFieldOrMethod(T, addressable, pkg, name) +} + +// TODO(gri) The named type consolidation and seen maps below must be +// indexed by unique keys for a given type. Verify that named +// types always have only one representation (even when imported +// indirectly via different packages.) + +func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { + // WARNING: The code in this function is extremely subtle - do not modify casually! + // This function and NewMethodSet should be kept in sync. + + if name == "_" { + return // blank fields/methods are never found + } + + typ, isPtr := deref(T) + named, _ := typ.(*Named) + + // *typ where typ is an interface has no methods. + if isPtr { + utyp := typ + if named != nil { + utyp = named.underlying + } + if _, ok := utyp.(*Interface); ok { + return + } + } + + // Start with typ as single entry at shallowest depth. + // If typ is not a named type, insert a nil type instead. + current := []embeddedType{{named, nil, isPtr, false}} + + // named types that we have seen already, allocated lazily + var seen map[*Named]bool + + // search current depth + for len(current) > 0 { + var next []embeddedType // embedded types found at current depth + + // look for (pkg, name) in all types at current depth + for _, e := range current { + // The very first time only, e.typ may be nil. + // In this case, we don't have a named type and + // we simply continue with the underlying type. + if e.typ != nil { + if seen[e.typ] { + // We have seen this type before, at a more shallow depth + // (note that multiples of this type at the current depth + // were consolidated before). The type at that depth shadows + // this same type at the current depth, so we can ignore + // this one. + continue + } + if seen == nil { + seen = make(map[*Named]bool) + } + seen[e.typ] = true + + // look for a matching attached method + if i, m := lookupMethod(e.typ.methods, pkg, name); m != nil { + // potential match + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + continue // we can't have a matching field or interface method + } + + // continue with underlying type + typ = e.typ.underlying + } + + switch t := typ.(type) { + case *Struct: + // look for a matching field and collect embedded types + for i, f := range t.fields { + if f.sameId(pkg, name) { + assert(f.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = f + indirect = e.indirect + continue // we can't have a matching interface method + } + // Collect embedded struct fields for searching the next + // lower depth, but only if we have not seen a match yet + // (if we have a match it is either the desired field or + // we have a name collision on the same depth; in either + // case we don't need to look further). + // Embedded fields are always of the form T or *T where + // T is a named type. If e.typ appeared multiple times at + // this depth, f.typ appears multiple times at the next + // depth. + if obj == nil && f.anonymous { + // Ignore embedded basic types - only user-defined + // named types can have methods or struct fields. + typ, isPtr := deref(f.typ) + if t, _ := typ.(*Named); t != nil { + next = append(next, embeddedType{t, concat(e.index, i), e.indirect || isPtr, e.multiples}) + } + } + } + + case *Interface: + // look for a matching method + // TODO(gri) t.allMethods is sorted - use binary search + if i, m := lookupMethod(t.allMethods, pkg, name); m != nil { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + obj = m + indirect = e.indirect + } + } + } + + if obj != nil { + // found a potential match + // spec: "A method call x.m() is valid if the method set of (the type of) x + // contains m and the argument list can be assigned to the parameter + // list of m. If x is addressable and &x's method set contains m, x.m() + // is shorthand for (&x).m()". + if f, _ := obj.(*Func); f != nil && ptrRecv(f) && !indirect && !addressable { + return nil, nil, true // pointer/addressable receiver required + } + return + } + + current = consolidateMultiples(next) + } + + return nil, nil, false // not found +} + +// embeddedType represents an embedded named type +type embeddedType struct { + typ *Named // nil means use the outer typ variable instead + index []int // embedded field indices, starting with index at depth 0 + indirect bool // if set, there was a pointer indirection on the path to this field + multiples bool // if set, typ appears multiple times at this depth +} + +// consolidateMultiples collects multiple list entries with the same type +// into a single entry marked as containing multiples. The result is the +// consolidated list. +func consolidateMultiples(list []embeddedType) []embeddedType { + if len(list) <= 1 { + return list // at most one entry - nothing to do + } + + n := 0 // number of entries w/ unique type + prev := make(map[*Named]int) // index at which type was previously seen + for _, e := range list { + if i, found := prev[e.typ]; found { + list[i].multiples = true + // ignore this entry + } else { + prev[e.typ] = n + list[n] = e + n++ + } + } + return list[:n] +} + +// MissingMethod returns (nil, false) if V implements T, otherwise it +// returns a missing method required by T and whether it is missing or +// just has the wrong type. +// +// For non-interface types V, or if static is set, V implements T if all +// methods of T are present in V. Otherwise (V is an interface and static +// is not set), MissingMethod only checks that methods of T which are also +// present in V have matching types (e.g., for a type assertion x.(T) where +// x is of interface type V). +// +func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType bool) { + // fast path for common case + if T.Empty() { + return + } + + // TODO(gri) Consider using method sets here. Might be more efficient. + + if ityp, _ := V.Underlying().(*Interface); ityp != nil { + // TODO(gri) allMethods is sorted - can do this more efficiently + for _, m := range T.allMethods { + _, obj := lookupMethod(ityp.allMethods, m.pkg, m.name) + switch { + case obj == nil: + if static { + return m, false + } + case !Identical(obj.Type(), m.typ): + return m, true + } + } + return + } + + // A concrete type implements T if it implements all methods of T. + for _, m := range T.allMethods { + obj, _, _ := lookupFieldOrMethod(V, false, m.pkg, m.name) + + f, _ := obj.(*Func) + if f == nil { + return m, false + } + + if !Identical(f.typ, m.typ) { + return m, true + } + } + + return +} + +// assertableTo reports whether a value of type V can be asserted to have type T. +// It returns (nil, false) as affirmative answer. Otherwise it returns a missing +// method required by V and whether it is missing or just has the wrong type. +func assertableTo(V *Interface, T Type) (method *Func, wrongType bool) { + // no static check is required if T is an interface + // spec: "If T is an interface type, x.(T) asserts that the + // dynamic type of x implements the interface T." + if _, ok := T.Underlying().(*Interface); ok && !strict { + return + } + return MissingMethod(T, V, false) +} + +// deref dereferences typ if it is a *Pointer and returns its base and true. +// Otherwise it returns (typ, false). +func deref(typ Type) (Type, bool) { + if p, _ := typ.(*Pointer); p != nil { + return p.base, true + } + return typ, false +} + +// derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a +// (named or unnamed) struct and returns its base. Otherwise it returns typ. +func derefStructPtr(typ Type) Type { + if p, _ := typ.Underlying().(*Pointer); p != nil { + if _, ok := p.base.Underlying().(*Struct); ok { + return p.base + } + } + return typ +} + +// concat returns the result of concatenating list and i. +// The result does not share its underlying array with list. +func concat(list []int, i int) []int { + var t []int + t = append(t, list...) + return append(t, i) +} + +// fieldIndex returns the index for the field with matching package and name, or a value < 0. +func fieldIndex(fields []*Var, pkg *Package, name string) int { + if name != "_" { + for i, f := range fields { + if f.sameId(pkg, name) { + return i + } + } + } + return -1 +} + +// lookupMethod returns the index of and method with matching package and name, or (-1, nil). +func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) { + if name != "_" { + for i, m := range methods { + if m.sameId(pkg, name) { + return i, m + } + } + } + return -1, nil +} diff --git a/libgo/go/go/types/methodset.go b/libgo/go/go/types/methodset.go new file mode 100644 index 00000000000..b27f2dac34a --- /dev/null +++ b/libgo/go/go/types/methodset.go @@ -0,0 +1,268 @@ +// Copyright 2013 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 method sets. + +package types + +import ( + "bytes" + "fmt" + "sort" +) + +// A MethodSet is an ordered set of concrete or abstract (interface) methods; +// a method is a MethodVal selection, and they are ordered by ascending m.Obj().Id(). +// The zero value for a MethodSet is a ready-to-use empty method set. +type MethodSet struct { + list []*Selection +} + +func (s *MethodSet) String() string { + if s.Len() == 0 { + return "MethodSet {}" + } + + var buf bytes.Buffer + fmt.Fprintln(&buf, "MethodSet {") + for _, f := range s.list { + fmt.Fprintf(&buf, "\t%s\n", f) + } + fmt.Fprintln(&buf, "}") + return buf.String() +} + +// Len returns the number of methods in s. +func (s *MethodSet) Len() int { return len(s.list) } + +// At returns the i'th method in s for 0 <= i < s.Len(). +func (s *MethodSet) At(i int) *Selection { return s.list[i] } + +// Lookup returns the method with matching package and name, or nil if not found. +func (s *MethodSet) Lookup(pkg *Package, name string) *Selection { + if s.Len() == 0 { + return nil + } + + key := Id(pkg, name) + i := sort.Search(len(s.list), func(i int) bool { + m := s.list[i] + return m.obj.Id() >= key + }) + if i < len(s.list) { + m := s.list[i] + if m.obj.Id() == key { + return m + } + } + return nil +} + +// Shared empty method set. +var emptyMethodSet MethodSet + +// NewMethodSet returns the method set for the given type T. +// It always returns a non-nil method set, even if it is empty. +func NewMethodSet(T Type) *MethodSet { + // WARNING: The code in this function is extremely subtle - do not modify casually! + // This function and lookupFieldOrMethod should be kept in sync. + + // method set up to the current depth, allocated lazily + var base methodSet + + typ, isPtr := deref(T) + named, _ := typ.(*Named) + + // *typ where typ is an interface has no methods. + if isPtr { + utyp := typ + if named != nil { + utyp = named.underlying + } + if _, ok := utyp.(*Interface); ok { + return &emptyMethodSet + } + } + + // Start with typ as single entry at shallowest depth. + // If typ is not a named type, insert a nil type instead. + current := []embeddedType{{named, nil, isPtr, false}} + + // named types that we have seen already, allocated lazily + var seen map[*Named]bool + + // collect methods at current depth + for len(current) > 0 { + var next []embeddedType // embedded types found at current depth + + // field and method sets at current depth, allocated lazily + var fset fieldSet + var mset methodSet + + for _, e := range current { + // The very first time only, e.typ may be nil. + // In this case, we don't have a named type and + // we simply continue with the underlying type. + if e.typ != nil { + if seen[e.typ] { + // We have seen this type before, at a more shallow depth + // (note that multiples of this type at the current depth + // were consolidated before). The type at that depth shadows + // this same type at the current depth, so we can ignore + // this one. + continue + } + if seen == nil { + seen = make(map[*Named]bool) + } + seen[e.typ] = true + + mset = mset.add(e.typ.methods, e.index, e.indirect, e.multiples) + + // continue with underlying type + typ = e.typ.underlying + } + + switch t := typ.(type) { + case *Struct: + for i, f := range t.fields { + fset = fset.add(f, e.multiples) + + // Embedded fields are always of the form T or *T where + // T is a named type. If typ appeared multiple times at + // this depth, f.Type appears multiple times at the next + // depth. + if f.anonymous { + // Ignore embedded basic types - only user-defined + // named types can have methods or struct fields. + typ, isPtr := deref(f.typ) + if t, _ := typ.(*Named); t != nil { + next = append(next, embeddedType{t, concat(e.index, i), e.indirect || isPtr, e.multiples}) + } + } + } + + case *Interface: + mset = mset.add(t.allMethods, e.index, true, e.multiples) + } + } + + // Add methods and collisions at this depth to base if no entries with matching + // names exist already. + for k, m := range mset { + if _, found := base[k]; !found { + // Fields collide with methods of the same name at this depth. + if _, found := fset[k]; found { + m = nil // collision + } + if base == nil { + base = make(methodSet) + } + base[k] = m + } + } + + // Multiple fields with matching names collide at this depth and shadow all + // entries further down; add them as collisions to base if no entries with + // matching names exist already. + for k, f := range fset { + if f == nil { + if _, found := base[k]; !found { + if base == nil { + base = make(methodSet) + } + base[k] = nil // collision + } + } + } + + current = consolidateMultiples(next) + } + + if len(base) == 0 { + return &emptyMethodSet + } + + // collect methods + var list []*Selection + for _, m := range base { + if m != nil { + m.recv = T + list = append(list, m) + } + } + sort.Sort(byUniqueName(list)) + return &MethodSet{list} +} + +// A fieldSet is a set of fields and name collisions. +// A collision indicates that multiple fields with the +// same unique id appeared. +type fieldSet map[string]*Var // a nil entry indicates a name collision + +// Add adds field f to the field set s. +// If multiples is set, f appears multiple times +// and is treated as a collision. +func (s fieldSet) add(f *Var, multiples bool) fieldSet { + if s == nil { + s = make(fieldSet) + } + key := f.Id() + // if f is not in the set, add it + if !multiples { + if _, found := s[key]; !found { + s[key] = f + return s + } + } + s[key] = nil // collision + return s +} + +// A methodSet is a set of methods and name collisions. +// A collision indicates that multiple methods with the +// same unique id appeared. +type methodSet map[string]*Selection // a nil entry indicates a name collision + +// Add adds all functions in list to the method set s. +// If multiples is set, every function in list appears multiple times +// and is treated as a collision. +func (s methodSet) add(list []*Func, index []int, indirect bool, multiples bool) methodSet { + if len(list) == 0 { + return s + } + if s == nil { + s = make(methodSet) + } + for i, f := range list { + key := f.Id() + // if f is not in the set, add it + if !multiples { + // TODO(gri) A found method may not be added because it's not in the method set + // (!indirect && ptrRecv(f)). A 2nd method on the same level may be in the method + // set and may not collide with the first one, thus leading to a false positive. + // Is that possible? Investigate. + if _, found := s[key]; !found && (indirect || !ptrRecv(f)) { + s[key] = &Selection{MethodVal, nil, f, concat(index, i), indirect} + continue + } + } + s[key] = nil // collision + } + return s +} + +// ptrRecv reports whether the receiver is of the form *T. +// The receiver must exist. +func ptrRecv(f *Func) bool { + _, isPtr := deref(f.typ.(*Signature).recv.typ) + return isPtr +} + +// byUniqueName function lists can be sorted by their unique names. +type byUniqueName []*Selection + +func (a byUniqueName) Len() int { return len(a) } +func (a byUniqueName) Less(i, j int) bool { return a[i].obj.Id() < a[j].obj.Id() } +func (a byUniqueName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/libgo/go/go/types/object.go b/libgo/go/go/types/object.go new file mode 100644 index 00000000000..b835c6e53e8 --- /dev/null +++ b/libgo/go/go/types/object.go @@ -0,0 +1,360 @@ +// Copyright 2013 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 ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/token" +) + +// TODO(gri) Document factory, accessor methods, and fields. General clean-up. + +// An Object describes a named language entity such as a package, +// constant, type, variable, function (incl. methods), or label. +// All objects implement the Object interface. +// +type Object interface { + Parent() *Scope // scope in which this object is declared + Pos() token.Pos // position of object identifier in declaration + Pkg() *Package // nil for objects in the Universe scope and labels + Name() string // package local object name + Type() Type // object type + Exported() bool // reports whether the name starts with a capital letter + Id() string // object id (see Id below) + + // String returns a human-readable string of the object. + String() string + + // order reflects a package-level object's source order: if object + // a is before object b in the source, then a.order() < b.order(). + // order returns a value > 0 for package-level objects; it returns + // 0 for all other objects (including objects in file scopes). + order() uint32 + + // setOrder sets the order number of the object. It must be > 0. + setOrder(uint32) + + // setParent sets the parent scope of the object. + setParent(*Scope) + + // sameId reports whether obj.Id() and Id(pkg, name) are the same. + sameId(pkg *Package, name string) bool + + // scopePos returns the start position of the scope of this Object + scopePos() token.Pos + + // setScopePos sets the start position of the scope for this Object. + setScopePos(pos token.Pos) +} + +// Id returns name if it is exported, otherwise it +// returns the name qualified with the package path. +func Id(pkg *Package, name string) string { + if ast.IsExported(name) { + return name + } + // unexported names need the package path for differentiation + // (if there's no package, make sure we don't start with '.' + // as that may change the order of methods between a setup + // inside a package and outside a package - which breaks some + // tests) + path := "_" + // TODO(gri): shouldn't !ast.IsExported(name) => pkg != nil be an precondition? + // if pkg == nil { + // panic("nil package in lookup of unexported name") + // } + if pkg != nil { + path = pkg.path + if path == "" { + path = "_" + } + } + return path + "." + name +} + +// An object implements the common parts of an Object. +type object struct { + parent *Scope + pos token.Pos + pkg *Package + name string + typ Type + order_ uint32 + scopePos_ token.Pos +} + +func (obj *object) Parent() *Scope { return obj.parent } +func (obj *object) Pos() token.Pos { return obj.pos } +func (obj *object) Pkg() *Package { return obj.pkg } +func (obj *object) Name() string { return obj.name } +func (obj *object) Type() Type { return obj.typ } +func (obj *object) Exported() bool { return ast.IsExported(obj.name) } +func (obj *object) Id() string { return Id(obj.pkg, obj.name) } +func (obj *object) String() string { panic("abstract") } +func (obj *object) order() uint32 { return obj.order_ } +func (obj *object) scopePos() token.Pos { return obj.scopePos_ } + +func (obj *object) setParent(parent *Scope) { obj.parent = parent } +func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order } +func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos } + +func (obj *object) sameId(pkg *Package, name string) bool { + // spec: + // "Two identifiers are different if they are spelled differently, + // or if they appear in different packages and are not exported. + // Otherwise, they are the same." + if name != obj.name { + return false + } + // obj.Name == name + if obj.Exported() { + return true + } + // not exported, so packages must be the same (pkg == nil for + // fields in Universe scope; this can only happen for types + // introduced via Eval) + if pkg == nil || obj.pkg == nil { + return pkg == obj.pkg + } + // pkg != nil && obj.pkg != nil + return pkg.path == obj.pkg.path +} + +// A PkgName represents an imported Go package. +type PkgName struct { + object + imported *Package + used bool // set if the package was used +} + +func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName { + return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, token.NoPos}, imported, false} +} + +// Imported returns the package that was imported. +// It is distinct from Pkg(), which is the package containing the import statement. +func (obj *PkgName) Imported() *Package { return obj.imported } + +// A Const represents a declared constant. +type Const struct { + object + val constant.Value + visited bool // for initialization cycle detection +} + +func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val constant.Value) *Const { + return &Const{object{nil, pos, pkg, name, typ, 0, token.NoPos}, val, false} +} + +func (obj *Const) Val() constant.Value { return obj.val } + +// A TypeName represents a declared type. +type TypeName struct { + object +} + +func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName { + return &TypeName{object{nil, pos, pkg, name, typ, 0, token.NoPos}} +} + +// A Variable represents a declared variable (including function parameters and results, and struct fields). +type Var struct { + object + anonymous bool // if set, the variable is an anonymous struct field, and name is the type name + visited bool // for initialization cycle detection + isField bool // var is struct field + used bool // set if the variable was used +} + +func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}} +} + +func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}, used: true} // parameters are always 'used' +} + +func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool) *Var { + return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}, anonymous: anonymous, isField: true} +} + +func (obj *Var) Anonymous() bool { return obj.anonymous } + +func (obj *Var) IsField() bool { return obj.isField } + +// A Func represents a declared function, concrete method, or abstract +// (interface) method. Its Type() is always a *Signature. +// An abstract method may belong to many interfaces due to embedding. +type Func struct { + object +} + +func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func { + // don't store a nil signature + var typ Type + if sig != nil { + typ = sig + } + return &Func{object{nil, pos, pkg, name, typ, 0, token.NoPos}} +} + +// FullName returns the package- or receiver-type-qualified name of +// function or method obj. +func (obj *Func) FullName() string { + var buf bytes.Buffer + writeFuncName(&buf, obj, nil) + return buf.String() +} + +func (obj *Func) Scope() *Scope { + return obj.typ.(*Signature).scope +} + +// A Label represents a declared label. +type Label struct { + object + used bool // set if the label was used +} + +func NewLabel(pos token.Pos, pkg *Package, name string) *Label { + return &Label{object{pos: pos, pkg: pkg, name: name, typ: Typ[Invalid]}, false} +} + +// A Builtin represents a built-in function. +// Builtins don't have a valid type. +type Builtin struct { + object + id builtinId +} + +func newBuiltin(id builtinId) *Builtin { + return &Builtin{object{name: predeclaredFuncs[id].name, typ: Typ[Invalid]}, id} +} + +// Nil represents the predeclared value nil. +type Nil struct { + object +} + +func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) { + typ := obj.Type() + switch obj := obj.(type) { + case *PkgName: + fmt.Fprintf(buf, "package %s", obj.Name()) + if path := obj.imported.path; path != "" && path != obj.name { + fmt.Fprintf(buf, " (%q)", path) + } + return + + case *Const: + buf.WriteString("const") + + case *TypeName: + buf.WriteString("type") + typ = typ.Underlying() + + case *Var: + if obj.isField { + buf.WriteString("field") + } else { + buf.WriteString("var") + } + + case *Func: + buf.WriteString("func ") + writeFuncName(buf, obj, qf) + if typ != nil { + WriteSignature(buf, typ.(*Signature), qf) + } + return + + case *Label: + buf.WriteString("label") + typ = nil + + case *Builtin: + buf.WriteString("builtin") + typ = nil + + case *Nil: + buf.WriteString("nil") + return + + default: + panic(fmt.Sprintf("writeObject(%T)", obj)) + } + + buf.WriteByte(' ') + + // For package-level objects, qualify the name. + if obj.Pkg() != nil && obj.Pkg().scope.Lookup(obj.Name()) == obj { + writePackage(buf, obj.Pkg(), qf) + } + buf.WriteString(obj.Name()) + if typ != nil { + buf.WriteByte(' ') + WriteType(buf, typ, qf) + } +} + +func writePackage(buf *bytes.Buffer, pkg *Package, qf Qualifier) { + if pkg == nil { + return + } + var s string + if qf != nil { + s = qf(pkg) + } else { + s = pkg.Path() + } + if s != "" { + buf.WriteString(s) + buf.WriteByte('.') + } +} + +// ObjectString returns the string form of obj. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func ObjectString(obj Object, qf Qualifier) string { + var buf bytes.Buffer + writeObject(&buf, obj, qf) + return buf.String() +} + +func (obj *PkgName) String() string { return ObjectString(obj, nil) } +func (obj *Const) String() string { return ObjectString(obj, nil) } +func (obj *TypeName) String() string { return ObjectString(obj, nil) } +func (obj *Var) String() string { return ObjectString(obj, nil) } +func (obj *Func) String() string { return ObjectString(obj, nil) } +func (obj *Label) String() string { return ObjectString(obj, nil) } +func (obj *Builtin) String() string { return ObjectString(obj, nil) } +func (obj *Nil) String() string { return ObjectString(obj, nil) } + +func writeFuncName(buf *bytes.Buffer, f *Func, qf Qualifier) { + if f.typ != nil { + sig := f.typ.(*Signature) + if recv := sig.Recv(); recv != nil { + buf.WriteByte('(') + if _, ok := recv.Type().(*Interface); ok { + // gcimporter creates abstract methods of + // named interfaces using the interface type + // (not the named type) as the receiver. + // Don't print it in full. + buf.WriteString("interface") + } else { + WriteType(buf, recv.Type(), qf) + } + buf.WriteByte(')') + buf.WriteByte('.') + } else if f.pkg != nil { + writePackage(buf, f.pkg, qf) + } + } + buf.WriteString(f.name) +} diff --git a/libgo/go/go/types/objset.go b/libgo/go/go/types/objset.go new file mode 100644 index 00000000000..55eb74addba --- /dev/null +++ b/libgo/go/go/types/objset.go @@ -0,0 +1,31 @@ +// Copyright 2013 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 objsets. +// +// An objset is similar to a Scope but objset elements +// are identified by their unique id, instead of their +// object name. + +package types + +// An objset is a set of objects identified by their unique id. +// The zero value for objset is a ready-to-use empty objset. +type objset map[string]Object // initialized lazily + +// insert attempts to insert an object obj into objset s. +// If s already contains an alternative object alt with +// the same name, insert leaves s unchanged and returns alt. +// Otherwise it inserts obj and returns nil. +func (s *objset) insert(obj Object) Object { + id := obj.Id() + if alt := (*s)[id]; alt != nil { + return alt + } + if *s == nil { + *s = make(map[string]Object) + } + (*s)[id] = obj + return nil +} diff --git a/libgo/go/go/types/operand.go b/libgo/go/go/types/operand.go new file mode 100644 index 00000000000..d3bab51b04a --- /dev/null +++ b/libgo/go/go/types/operand.go @@ -0,0 +1,287 @@ +// 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 defines operands and associated operations. + +package types + +import ( + "bytes" + "go/ast" + "go/constant" + "go/token" +) + +// An operandMode specifies the (addressing) mode of an operand. +type operandMode byte + +const ( + invalid operandMode = iota // operand is invalid + novalue // operand represents no value (result of a function call w/o result) + builtin // operand is a built-in function + typexpr // operand is a type + constant_ // operand is a constant; the operand's typ is a Basic type + variable // operand is an addressable variable + mapindex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) + value // operand is a computed value + commaok // like value, but operand may be used in a comma,ok expression +) + +var operandModeString = [...]string{ + invalid: "invalid operand", + novalue: "no value", + builtin: "built-in", + typexpr: "type", + constant_: "constant", + variable: "variable", + mapindex: "map index expression", + value: "value", + commaok: "comma, ok expression", +} + +// An operand represents an intermediate value during type checking. +// Operands have an (addressing) mode, the expression evaluating to +// the operand, the operand's type, a value for constants, and an id +// for built-in functions. +// The zero value of operand is a ready to use invalid operand. +// +type operand struct { + mode operandMode + expr ast.Expr + typ Type + val constant.Value + id builtinId +} + +// pos returns the position of the expression corresponding to x. +// If x is invalid the position is token.NoPos. +// +func (x *operand) pos() token.Pos { + // x.expr may not be set if x is invalid + if x.expr == nil { + return token.NoPos + } + return x.expr.Pos() +} + +// Operand string formats +// (not all "untyped" cases can appear due to the type system, +// but they fall out naturally here) +// +// mode format +// +// invalid <expr> ( <mode> ) +// novalue <expr> ( <mode> ) +// builtin <expr> ( <mode> ) +// typexpr <expr> ( <mode> ) +// +// constant <expr> (<untyped kind> <mode> ) +// constant <expr> ( <mode> of type <typ>) +// constant <expr> (<untyped kind> <mode> <val> ) +// constant <expr> ( <mode> <val> of type <typ>) +// +// variable <expr> (<untyped kind> <mode> ) +// variable <expr> ( <mode> of type <typ>) +// +// mapindex <expr> (<untyped kind> <mode> ) +// mapindex <expr> ( <mode> of type <typ>) +// +// value <expr> (<untyped kind> <mode> ) +// value <expr> ( <mode> of type <typ>) +// +// commaok <expr> (<untyped kind> <mode> ) +// commaok <expr> ( <mode> of type <typ>) +// +func operandString(x *operand, qf Qualifier) string { + var buf bytes.Buffer + + var expr string + if x.expr != nil { + expr = ExprString(x.expr) + } else { + switch x.mode { + case builtin: + expr = predeclaredFuncs[x.id].name + case typexpr: + expr = TypeString(x.typ, qf) + case constant_: + expr = x.val.String() + } + } + + // <expr> ( + if expr != "" { + buf.WriteString(expr) + buf.WriteString(" (") + } + + // <untyped kind> + hasType := false + switch x.mode { + case invalid, novalue, builtin, typexpr: + // no type + default: + // has type + if isUntyped(x.typ) { + buf.WriteString(x.typ.(*Basic).name) + buf.WriteByte(' ') + break + } + hasType = true + } + + // <mode> + buf.WriteString(operandModeString[x.mode]) + + // <val> + if x.mode == constant_ { + if s := x.val.String(); s != expr { + buf.WriteByte(' ') + buf.WriteString(s) + } + } + + // <typ> + if hasType { + if x.typ != Typ[Invalid] { + buf.WriteString(" of type ") + WriteType(&buf, x.typ, qf) + } else { + buf.WriteString(" with invalid type") + } + } + + // ) + if expr != "" { + buf.WriteByte(')') + } + + return buf.String() +} + +func (x *operand) String() string { + return operandString(x, nil) +} + +// setConst sets x to the untyped constant for literal lit. +func (x *operand) setConst(tok token.Token, lit string) { + val := constant.MakeFromLiteral(lit, tok, 0) + if val == nil { + // TODO(gri) Should we make it an unknown constant instead? + x.mode = invalid + return + } + + var kind BasicKind + switch tok { + case token.INT: + kind = UntypedInt + case token.FLOAT: + kind = UntypedFloat + case token.IMAG: + kind = UntypedComplex + case token.CHAR: + kind = UntypedRune + case token.STRING: + kind = UntypedString + } + + x.mode = constant_ + x.typ = Typ[kind] + x.val = val +} + +// isNil reports whether x is the nil value. +func (x *operand) isNil() bool { + return x.mode == value && x.typ == Typ[UntypedNil] +} + +// TODO(gri) The functions operand.assignableTo, checker.convertUntyped, +// checker.representable, and checker.assignment are +// overlapping in functionality. Need to simplify and clean up. + +// assignableTo reports whether x is assignable to a variable of type T. +func (x *operand) assignableTo(conf *Config, T Type) bool { + if x.mode == invalid || T == Typ[Invalid] { + return true // avoid spurious errors + } + + V := x.typ + + // x's type is identical to T + if Identical(V, T) { + return true + } + + Vu := V.Underlying() + Tu := T.Underlying() + + // T is an interface type and x implements T + // (Do this check first as it might succeed early.) + if Ti, ok := Tu.(*Interface); ok { + if Implements(x.typ, Ti) { + return true + } + } + + // x's type V and T have identical underlying types + // and at least one of V or T is not a named type + if Identical(Vu, Tu) && (!isNamed(V) || !isNamed(T)) { + return true + } + + // x is a bidirectional channel value, T is a channel + // type, x's type V and T have identical element types, + // and at least one of V or T is not a named type + if Vc, ok := Vu.(*Chan); ok && Vc.dir == SendRecv { + if Tc, ok := Tu.(*Chan); ok && Identical(Vc.elem, Tc.elem) { + return !isNamed(V) || !isNamed(T) + } + } + + // x is the predeclared identifier nil and T is a pointer, + // function, slice, map, channel, or interface type + if x.isNil() { + switch t := Tu.(type) { + case *Basic: + if t.kind == UnsafePointer { + return true + } + case *Pointer, *Signature, *Slice, *Map, *Chan, *Interface: + return true + } + return false + } + + // x is an untyped constant representable by a value of type T + // TODO(gri) This is borrowing from checker.convertUntyped and + // checker.representable. Need to clean up. + if isUntyped(Vu) { + switch t := Tu.(type) { + case *Basic: + if x.mode == constant_ { + return representableConst(x.val, conf, t.kind, nil) + } + // The result of a comparison is an untyped boolean, + // but may not be a constant. + if Vb, _ := Vu.(*Basic); Vb != nil { + return Vb.kind == UntypedBool && isBoolean(Tu) + } + case *Interface: + return x.isNil() || t.Empty() + case *Pointer, *Signature, *Slice, *Map, *Chan: + return x.isNil() + } + } + + return false +} + +// isInteger reports whether x is value of integer type +// or an untyped constant representable as an integer. +func (x *operand) isInteger() bool { + return x.mode == invalid || + isInteger(x.typ) || + isUntyped(x.typ) && x.mode == constant_ && representableConst(x.val, nil, UntypedInt, nil) // no *Config required for UntypedInt +} diff --git a/libgo/go/go/types/ordering.go b/libgo/go/go/types/ordering.go new file mode 100644 index 00000000000..6bb98f2dc10 --- /dev/null +++ b/libgo/go/go/types/ordering.go @@ -0,0 +1,127 @@ +// Copyright 2014 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 resolveOrder. + +package types + +import ( + "go/ast" + "sort" +) + +// resolveOrder computes the order in which package-level objects +// must be type-checked. +// +// Interface types appear first in the list, sorted topologically +// by dependencies on embedded interfaces that are also declared +// in this package, followed by all other objects sorted in source +// order. +// +// TODO(gri) Consider sorting all types by dependencies here, and +// in the process check _and_ report type cycles. This may simplify +// the full type-checking phase. +// +func (check *Checker) resolveOrder() []Object { + var ifaces, others []Object + + // collect interface types with their dependencies, and all other objects + for obj := range check.objMap { + if ityp := check.interfaceFor(obj); ityp != nil { + ifaces = append(ifaces, obj) + // determine dependencies on embedded interfaces + for _, f := range ityp.Methods.List { + if len(f.Names) == 0 { + // Embedded interface: The type must be a (possibly + // qualified) identifier denoting another interface. + // Imported interfaces are already fully resolved, + // so we can ignore qualified identifiers. + if ident, _ := f.Type.(*ast.Ident); ident != nil { + embedded := check.pkg.scope.Lookup(ident.Name) + if check.interfaceFor(embedded) != nil { + check.objMap[obj].addDep(embedded) + } + } + } + } + } else { + others = append(others, obj) + } + } + + // final object order + var order []Object + + // sort interface types topologically by dependencies, + // and in source order if there are no dependencies + sort.Sort(inSourceOrder(ifaces)) + if debug { + for _, obj := range ifaces { + assert(check.objMap[obj].mark == 0) + } + } + for _, obj := range ifaces { + check.appendInPostOrder(&order, obj) + } + + // sort everything else in source order + sort.Sort(inSourceOrder(others)) + + return append(order, others...) +} + +// interfaceFor returns the AST interface denoted by obj, or nil. +func (check *Checker) interfaceFor(obj Object) *ast.InterfaceType { + tname, _ := obj.(*TypeName) + if tname == nil { + return nil // not a type + } + d := check.objMap[obj] + if d == nil { + check.dump("%s: %s should have been declared", obj.Pos(), obj.Name()) + unreachable() + } + if d.typ == nil { + return nil // invalid AST - ignore (will be handled later) + } + ityp, _ := d.typ.(*ast.InterfaceType) + return ityp +} + +func (check *Checker) appendInPostOrder(order *[]Object, obj Object) { + d := check.objMap[obj] + if d.mark != 0 { + // We've already seen this object; either because it's + // already added to order, or because we have a cycle. + // In both cases we stop. Cycle errors are reported + // when type-checking types. + return + } + d.mark = 1 + + for _, obj := range orderedSetObjects(d.deps) { + check.appendInPostOrder(order, obj) + } + + *order = append(*order, obj) +} + +func orderedSetObjects(set map[Object]bool) []Object { + list := make([]Object, len(set)) + i := 0 + for obj := range set { + // we don't care about the map element value + list[i] = obj + i++ + } + sort.Sort(inSourceOrder(list)) + return list +} + +// inSourceOrder implements the sort.Sort interface. +type inSourceOrder []Object + +func (a inSourceOrder) Len() int { return len(a) } +func (a inSourceOrder) Less(i, j int) bool { return a[i].order() < a[j].order() } +func (a inSourceOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/libgo/go/go/types/package.go b/libgo/go/go/types/package.go new file mode 100644 index 00000000000..48fe8398fe6 --- /dev/null +++ b/libgo/go/go/types/package.go @@ -0,0 +1,65 @@ +// Copyright 2013 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/token" +) + +// A Package describes a Go package. +type Package struct { + path string + name string + scope *Scope + complete bool + imports []*Package + fake bool // scope lookup errors are silently dropped if package is fake (internal use only) +} + +// NewPackage returns a new Package for the given package path and name; +// the name must not be the blank identifier. +// The package is not complete and contains no explicit imports. +func NewPackage(path, name string) *Package { + if name == "_" { + panic("invalid package name _") + } + scope := NewScope(Universe, token.NoPos, token.NoPos, fmt.Sprintf("package %q", path)) + return &Package{path: path, name: name, scope: scope} +} + +// Path returns the package path. +func (pkg *Package) Path() string { return pkg.path } + +// Name returns the package name. +func (pkg *Package) Name() string { return pkg.name } + +// Scope returns the (complete or incomplete) package scope +// holding the objects declared at package level (TypeNames, +// Consts, Vars, and Funcs). +func (pkg *Package) Scope() *Scope { return pkg.scope } + +// A package is complete if its scope contains (at least) all +// exported objects; otherwise it is incomplete. +func (pkg *Package) Complete() bool { return pkg.complete } + +// MarkComplete marks a package as complete. +func (pkg *Package) MarkComplete() { pkg.complete = true } + +// Imports returns the list of packages directly imported by +// pkg; the list is in source order. Package unsafe is excluded. +// +// If pkg was loaded from export data, Imports includes packages that +// provide package-level objects referenced by pkg. This may be more or +// less than the set of packages directly imported by pkg's source code. +func (pkg *Package) Imports() []*Package { return pkg.imports } + +// SetImports sets the list of explicitly imported packages to list. +// It is the caller's responsibility to make sure list elements are unique. +func (pkg *Package) SetImports(list []*Package) { pkg.imports = list } + +func (pkg *Package) String() string { + return fmt.Sprintf("package %s (%q)", pkg.name, pkg.path) +} diff --git a/libgo/go/go/types/predicates.go b/libgo/go/go/types/predicates.go new file mode 100644 index 00000000000..993c6d290b1 --- /dev/null +++ b/libgo/go/go/types/predicates.go @@ -0,0 +1,309 @@ +// 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 implements commonly used type predicates. + +package types + +import "sort" + +func isNamed(typ Type) bool { + if _, ok := typ.(*Basic); ok { + return ok + } + _, ok := typ.(*Named) + return ok +} + +func isBoolean(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsBoolean != 0 +} + +func isInteger(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsInteger != 0 +} + +func isUnsigned(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsUnsigned != 0 +} + +func isFloat(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsFloat != 0 +} + +func isComplex(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsComplex != 0 +} + +func isNumeric(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsNumeric != 0 +} + +func isString(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsString != 0 +} + +func isTyped(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return !ok || t.info&IsUntyped == 0 +} + +func isUntyped(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsUntyped != 0 +} + +func isOrdered(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsOrdered != 0 +} + +func isConstType(typ Type) bool { + t, ok := typ.Underlying().(*Basic) + return ok && t.info&IsConstType != 0 +} + +// IsInterface reports whether typ is an interface type. +func IsInterface(typ Type) bool { + _, ok := typ.Underlying().(*Interface) + return ok +} + +// Comparable reports whether values of type T are comparable. +func Comparable(T Type) bool { + switch t := T.Underlying().(type) { + case *Basic: + // assume invalid types to be comparable + // to avoid follow-up errors + return t.kind != UntypedNil + case *Pointer, *Interface, *Chan: + return true + case *Struct: + for _, f := range t.fields { + if !Comparable(f.typ) { + return false + } + } + return true + case *Array: + return Comparable(t.elem) + } + return false +} + +// hasNil reports whether a type includes the nil value. +func hasNil(typ Type) bool { + switch t := typ.Underlying().(type) { + case *Basic: + return t.kind == UnsafePointer + case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan: + return true + } + return false +} + +// Identical reports whether x and y are identical. +func Identical(x, y Type) bool { + return identical(x, y, nil) +} + +// An ifacePair is a node in a stack of interface type pairs compared for identity. +type ifacePair struct { + x, y *Interface + prev *ifacePair +} + +func (p *ifacePair) identical(q *ifacePair) bool { + return p.x == q.x && p.y == q.y || p.x == q.y && p.y == q.x +} + +func identical(x, y Type, p *ifacePair) bool { + if x == y { + return true + } + + switch x := x.(type) { + case *Basic: + // Basic types are singletons except for the rune and byte + // aliases, thus we cannot solely rely on the x == y check + // above. + if y, ok := y.(*Basic); ok { + return x.kind == y.kind + } + + case *Array: + // Two array types are identical if they have identical element types + // and the same array length. + if y, ok := y.(*Array); ok { + return x.len == y.len && identical(x.elem, y.elem, p) + } + + case *Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*Slice); ok { + return identical(x.elem, y.elem, p) + } + + case *Struct: + // Two struct types are identical if they have the same sequence of fields, + // and if corresponding fields have the same names, and identical types, + // and identical tags. Two anonymous fields are considered to have the same + // name. Lower-case field names from different packages are always different. + if y, ok := y.(*Struct); ok { + if x.NumFields() == y.NumFields() { + for i, f := range x.fields { + g := y.fields[i] + if f.anonymous != g.anonymous || + x.Tag(i) != y.Tag(i) || + !f.sameId(g.pkg, g.name) || + !identical(f.typ, g.typ, p) { + return false + } + } + return true + } + } + + case *Pointer: + // Two pointer types are identical if they have identical base types. + if y, ok := y.(*Pointer); ok { + return identical(x.base, y.base, p) + } + + case *Tuple: + // Two tuples types are identical if they have the same number of elements + // and corresponding elements have identical types. + if y, ok := y.(*Tuple); ok { + if x.Len() == y.Len() { + if x != nil { + for i, v := range x.vars { + w := y.vars[i] + if !identical(v.typ, w.typ, p) { + return false + } + } + } + return true + } + } + + case *Signature: + // Two function types are identical if they have the same number of parameters + // and result values, corresponding parameter and result types are identical, + // and either both functions are variadic or neither is. Parameter and result + // names are not required to match. + if y, ok := y.(*Signature); ok { + return x.variadic == y.variadic && + identical(x.params, y.params, p) && + identical(x.results, y.results, p) + } + + case *Interface: + // Two interface types are identical if they have the same set of methods with + // the same names and identical function types. Lower-case method names from + // different packages are always different. The order of the methods is irrelevant. + if y, ok := y.(*Interface); ok { + a := x.allMethods + b := y.allMethods + if len(a) == len(b) { + // Interface types are the only types where cycles can occur + // that are not "terminated" via named types; and such cycles + // can only be created via method parameter types that are + // anonymous interfaces (directly or indirectly) embedding + // the current interface. Example: + // + // type T interface { + // m() interface{T} + // } + // + // If two such (differently named) interfaces are compared, + // endless recursion occurs if the cycle is not detected. + // + // If x and y were compared before, they must be equal + // (if they were not, the recursion would have stopped); + // search the ifacePair stack for the same pair. + // + // This is a quadratic algorithm, but in practice these stacks + // are extremely short (bounded by the nesting depth of interface + // type declarations that recur via parameter types, an extremely + // rare occurrence). An alternative implementation might use a + // "visited" map, but that is probably less efficient overall. + q := &ifacePair{x, y, p} + for p != nil { + if p.identical(q) { + return true // same pair was compared before + } + p = p.prev + } + if debug { + assert(sort.IsSorted(byUniqueMethodName(a))) + assert(sort.IsSorted(byUniqueMethodName(b))) + } + for i, f := range a { + g := b[i] + if f.Id() != g.Id() || !identical(f.typ, g.typ, q) { + return false + } + } + return true + } + } + + case *Map: + // Two map types are identical if they have identical key and value types. + if y, ok := y.(*Map); ok { + return identical(x.key, y.key, p) && identical(x.elem, y.elem, p) + } + + case *Chan: + // Two channel types are identical if they have identical value types + // and the same direction. + if y, ok := y.(*Chan); ok { + return x.dir == y.dir && identical(x.elem, y.elem, p) + } + + case *Named: + // Two named types are identical if their type names originate + // in the same type declaration. + if y, ok := y.(*Named); ok { + return x.obj == y.obj + } + + default: + unreachable() + } + + return false +} + +// defaultType returns the default "typed" type for an "untyped" type; +// it returns the incoming type for all other types. The default type +// for untyped nil is untyped nil. +// +func defaultType(typ Type) Type { + if t, ok := typ.(*Basic); ok { + switch t.kind { + case UntypedBool: + return Typ[Bool] + case UntypedInt: + return Typ[Int] + case UntypedRune: + return universeRune // use 'rune' name + case UntypedFloat: + return Typ[Float64] + case UntypedComplex: + return Typ[Complex128] + case UntypedString: + return Typ[String] + } + } + return typ +} diff --git a/libgo/go/go/types/resolver.go b/libgo/go/go/types/resolver.go new file mode 100644 index 00000000000..c31ef423d9a --- /dev/null +++ b/libgo/go/go/types/resolver.go @@ -0,0 +1,445 @@ +// Copyright 2013 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/constant" + "go/token" + pathLib "path" + "strconv" + "strings" + "unicode" +) + +// A declInfo describes a package-level const, type, var, or func declaration. +type declInfo struct { + file *Scope // scope of file containing this declaration + lhs []*Var // lhs of n:1 variable declarations, or nil + typ ast.Expr // type, or nil + init ast.Expr // init expression, or nil + fdecl *ast.FuncDecl // func declaration, or nil + + deps map[Object]bool // type and init dependencies; lazily allocated + mark int // for dependency analysis +} + +// hasInitializer reports whether the declared object has an initialization +// expression or function body. +func (d *declInfo) hasInitializer() bool { + return d.init != nil || d.fdecl != nil && d.fdecl.Body != nil +} + +// addDep adds obj as a dependency to d. +func (d *declInfo) addDep(obj Object) { + m := d.deps + if m == nil { + m = make(map[Object]bool) + d.deps = m + } + m[obj] = true +} + +// arityMatch checks that the lhs and rhs of a const or var decl +// have the appropriate number of names and init exprs. For const +// decls, init is the value spec providing the init exprs; for +// var decls, init is nil (the init exprs are in s in this case). +func (check *Checker) arityMatch(s, init *ast.ValueSpec) { + l := len(s.Names) + r := len(s.Values) + if init != nil { + r = len(init.Values) + } + + switch { + case init == nil && r == 0: + // var decl w/o init expr + if s.Type == nil { + check.errorf(s.Pos(), "missing type or init expr") + } + case l < r: + if l < len(s.Values) { + // init exprs from s + n := s.Values[l] + check.errorf(n.Pos(), "extra init expr %s", n) + // TODO(gri) avoid declared but not used error here + } else { + // init exprs "inherited" + check.errorf(s.Pos(), "extra init expr at %s", init.Pos()) + // TODO(gri) avoid declared but not used error here + } + case l > r && (init != nil || r != 1): + n := s.Names[r] + check.errorf(n.Pos(), "missing init expr for %s", n) + } +} + +func validatedImportPath(path string) (string, error) { + s, err := strconv.Unquote(path) + if err != nil { + return "", err + } + if s == "" { + return "", fmt.Errorf("empty string") + } + const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" + for _, r := range s { + if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { + return s, fmt.Errorf("invalid character %#U", r) + } + } + return s, nil +} + +// declarePkgObj declares obj in the package scope, records its ident -> obj mapping, +// and updates check.objMap. The object must not be a function or method. +func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) { + assert(ident.Name == obj.Name()) + + // spec: "A package-scope or file-scope identifier with name init + // may only be declared to be a function with this (func()) signature." + if ident.Name == "init" { + check.errorf(ident.Pos(), "cannot declare init - must be func") + return + } + + check.declare(check.pkg.scope, ident, obj, token.NoPos) + check.objMap[obj] = d + obj.setOrder(uint32(len(check.objMap))) +} + +// filename returns a filename suitable for debugging output. +func (check *Checker) filename(fileNo int) string { + file := check.files[fileNo] + if pos := file.Pos(); pos.IsValid() { + return check.fset.File(pos).Name() + } + return fmt.Sprintf("file[%d]", fileNo) +} + +// collectObjects collects all file and package objects and inserts them +// into their respective scopes. It also performs imports and associates +// methods with receiver base type names. +func (check *Checker) collectObjects() { + pkg := check.pkg + + // pkgImports is the set of packages already imported by any package file seen + // so far. Used to avoid duplicate entries in pkg.imports. Allocate and populate + // it (pkg.imports may not be empty if we are checking test files incrementally). + var pkgImports = make(map[*Package]bool) + for _, imp := range pkg.imports { + pkgImports[imp] = true + } + + for fileNo, file := range check.files { + // The package identifier denotes the current package, + // but there is no corresponding package object. + check.recordDef(file.Name, nil) + + // Use the actual source file extent rather than *ast.File extent since the + // latter doesn't include comments which appear at the start or end of the file. + // Be conservative and use the *ast.File extent if we don't have a *token.File. + pos, end := file.Pos(), file.End() + if f := check.fset.File(file.Pos()); f != nil { + pos, end = token.Pos(f.Base()), token.Pos(f.Base()+f.Size()) + } + fileScope := NewScope(check.pkg.scope, pos, end, check.filename(fileNo)) + check.recordScope(file, fileScope) + + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.BadDecl: + // ignore + + case *ast.GenDecl: + var last *ast.ValueSpec // last ValueSpec with type or init exprs seen + for iota, spec := range d.Specs { + switch s := spec.(type) { + case *ast.ImportSpec: + // import package + var imp *Package + path, err := validatedImportPath(s.Path.Value) + if err != nil { + check.errorf(s.Path.Pos(), "invalid import path (%s)", err) + continue + } + if path == "C" && check.conf.FakeImportC { + // TODO(gri) shouldn't create a new one each time + imp = NewPackage("C", "C") + imp.fake = true + } else if path == "unsafe" { + // package "unsafe" is known to the language + imp = Unsafe + } else { + if importer := check.conf.Importer; importer != nil { + imp, err = importer.Import(path) + if imp == nil && err == nil { + err = fmt.Errorf("Config.Importer.Import(%s) returned nil but no error", path) + } + } else { + err = fmt.Errorf("Config.Importer not installed") + } + if err != nil { + check.errorf(s.Path.Pos(), "could not import %s (%s)", path, err) + continue + } + } + + // add package to list of explicit imports + // (this functionality is provided as a convenience + // for clients; it is not needed for type-checking) + if !pkgImports[imp] { + pkgImports[imp] = true + if imp != Unsafe { + pkg.imports = append(pkg.imports, imp) + } + } + + // local name overrides imported package name + name := imp.name + if s.Name != nil { + name = s.Name.Name + if name == "init" { + check.errorf(s.Name.Pos(), "cannot declare init - must be func") + continue + } + } + + obj := NewPkgName(s.Pos(), pkg, name, imp) + if s.Name != nil { + // in a dot-import, the dot represents the package + check.recordDef(s.Name, obj) + } else { + check.recordImplicit(s, obj) + } + + // add import to file scope + if name == "." { + // merge imported scope with file scope + for _, obj := range imp.scope.elems { + // A package scope may contain non-exported objects, + // do not import them! + if obj.Exported() { + // TODO(gri) When we import a package, we create + // a new local package object. We should do the + // same for each dot-imported object. That way + // they can have correct position information. + // (We must not modify their existing position + // information because the same package - found + // via Config.Packages - may be dot-imported in + // another package!) + check.declare(fileScope, nil, obj, token.NoPos) + check.recordImplicit(s, obj) + } + } + // add position to set of dot-import positions for this file + // (this is only needed for "imported but not used" errors) + check.addUnusedDotImport(fileScope, imp, s.Pos()) + } else { + // declare imported package object in file scope + check.declare(fileScope, nil, obj, token.NoPos) + } + + case *ast.ValueSpec: + switch d.Tok { + case token.CONST: + // determine which initialization expressions to use + switch { + case s.Type != nil || len(s.Values) > 0: + last = s + case last == nil: + last = new(ast.ValueSpec) // make sure last exists + } + + // declare all constants + for i, name := range s.Names { + obj := NewConst(name.Pos(), pkg, name.Name, nil, constant.MakeInt64(int64(iota))) + + var init ast.Expr + if i < len(last.Values) { + init = last.Values[i] + } + + d := &declInfo{file: fileScope, typ: last.Type, init: init} + check.declarePkgObj(name, obj, d) + } + + check.arityMatch(s, last) + + case token.VAR: + lhs := make([]*Var, len(s.Names)) + // If there's exactly one rhs initializer, use + // the same declInfo d1 for all lhs variables + // so that each lhs variable depends on the same + // rhs initializer (n:1 var declaration). + var d1 *declInfo + if len(s.Values) == 1 { + // The lhs elements are only set up after the for loop below, + // but that's ok because declareVar only collects the declInfo + // for a later phase. + d1 = &declInfo{file: fileScope, lhs: lhs, typ: s.Type, init: s.Values[0]} + } + + // declare all variables + for i, name := range s.Names { + obj := NewVar(name.Pos(), pkg, name.Name, nil) + lhs[i] = obj + + d := d1 + if d == nil { + // individual assignments + var init ast.Expr + if i < len(s.Values) { + init = s.Values[i] + } + d = &declInfo{file: fileScope, typ: s.Type, init: init} + } + + check.declarePkgObj(name, obj, d) + } + + check.arityMatch(s, nil) + + default: + check.invalidAST(s.Pos(), "invalid token %s", d.Tok) + } + + case *ast.TypeSpec: + obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) + check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, typ: s.Type}) + + default: + check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) + } + } + + case *ast.FuncDecl: + name := d.Name.Name + obj := NewFunc(d.Name.Pos(), pkg, name, nil) + if d.Recv == nil { + // regular function + if name == "init" { + // don't declare init functions in the package scope - they are invisible + obj.parent = pkg.scope + check.recordDef(d.Name, obj) + // init functions must have a body + if d.Body == nil { + check.softErrorf(obj.pos, "missing function body") + } + } else { + check.declare(pkg.scope, d.Name, obj, token.NoPos) + } + } else { + // method + check.recordDef(d.Name, obj) + // Associate method with receiver base type name, if possible. + // Ignore methods that have an invalid receiver, or a blank _ + // receiver name. They will be type-checked later, with regular + // functions. + if list := d.Recv.List; len(list) > 0 { + typ := list[0].Type + if ptr, _ := typ.(*ast.StarExpr); ptr != nil { + typ = ptr.X + } + if base, _ := typ.(*ast.Ident); base != nil && base.Name != "_" { + check.assocMethod(base.Name, obj) + } + } + } + info := &declInfo{file: fileScope, fdecl: d} + check.objMap[obj] = info + obj.setOrder(uint32(len(check.objMap))) + + default: + check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) + } + } + } + + // verify that objects in package and file scopes have different names + for _, scope := range check.pkg.scope.children /* file scopes */ { + for _, obj := range scope.elems { + if alt := pkg.scope.Lookup(obj.Name()); alt != nil { + if pkg, ok := obj.(*PkgName); ok { + check.errorf(alt.Pos(), "%s already declared through import of %s", alt.Name(), pkg.Imported()) + check.reportAltDecl(pkg) + } else { + check.errorf(alt.Pos(), "%s already declared through dot-import of %s", alt.Name(), obj.Pkg()) + // TODO(gri) dot-imported objects don't have a position; reportAltDecl won't print anything + check.reportAltDecl(obj) + } + } + } + } +} + +// packageObjects typechecks all package objects in objList, but not function bodies. +func (check *Checker) packageObjects(objList []Object) { + // add new methods to already type-checked types (from a prior Checker.Files call) + for _, obj := range objList { + if obj, _ := obj.(*TypeName); obj != nil && obj.typ != nil { + check.addMethodDecls(obj) + } + } + + // pre-allocate space for type declaration paths so that the underlying array is reused + typePath := make([]*TypeName, 0, 8) + + for _, obj := range objList { + check.objDecl(obj, nil, typePath) + } + + // At this point we may have a non-empty check.methods map; this means that not all + // entries were deleted at the end of typeDecl because the respective receiver base + // types were not found. In that case, an error was reported when declaring those + // methods. We can now safely discard this map. + check.methods = nil +} + +// functionBodies typechecks all function bodies. +func (check *Checker) functionBodies() { + for _, f := range check.funcs { + check.funcBody(f.decl, f.name, f.sig, f.body) + } +} + +// unusedImports checks for unused imports. +func (check *Checker) unusedImports() { + // if function bodies are not checked, packages' uses are likely missing - don't check + if check.conf.IgnoreFuncBodies { + return + } + + // spec: "It is illegal (...) to directly import a package without referring to + // any of its exported identifiers. To import a package solely for its side-effects + // (initialization), use the blank identifier as explicit package name." + + // check use of regular imported packages + for _, scope := range check.pkg.scope.children /* file scopes */ { + for _, obj := range scope.elems { + if obj, ok := obj.(*PkgName); ok { + // Unused "blank imports" are automatically ignored + // since _ identifiers are not entered into scopes. + if !obj.used { + path := obj.imported.path + base := pathLib.Base(path) + if obj.name == base { + check.softErrorf(obj.pos, "%q imported but not used", path) + } else { + check.softErrorf(obj.pos, "%q imported but not used as %s", path, obj.name) + } + } + } + } + } + + // check use of dot-imported packages + for _, unusedDotImports := range check.unusedDotImports { + for pkg, pos := range unusedDotImports { + check.softErrorf(pos, "%q imported but not used", pkg.path) + } + } +} diff --git a/libgo/go/go/types/resolver_test.go b/libgo/go/go/types/resolver_test.go new file mode 100644 index 00000000000..34deae268e3 --- /dev/null +++ b/libgo/go/go/types/resolver_test.go @@ -0,0 +1,209 @@ +// 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 Importer + imported map[string]bool +} + +func (imp *resolveTestImporter) Import(path string) (*Package, error) { + if imp.importer == nil { + imp.importer = importer.Default() + imp.imported = make(map[string]bool) + } + pkg, err := imp.importer.Import(path) + 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 +} diff --git a/libgo/go/go/types/return.go b/libgo/go/go/types/return.go new file mode 100644 index 00000000000..66289852147 --- /dev/null +++ b/libgo/go/go/types/return.go @@ -0,0 +1,185 @@ +// Copyright 2013 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 isTerminating. + +package types + +import ( + "go/ast" + "go/token" +) + +// isTerminating reports if s is a terminating statement. +// If s is labeled, label is the label name; otherwise s +// is "". +func (check *Checker) isTerminating(s ast.Stmt, label string) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt, + *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, + *ast.RangeStmt: + // no chance + + case *ast.LabeledStmt: + return check.isTerminating(s.Stmt, s.Label.Name) + + case *ast.ExprStmt: + // the predeclared (possibly parenthesized) panic() function is terminating + if call, _ := unparen(s.X).(*ast.CallExpr); call != nil { + if id, _ := call.Fun.(*ast.Ident); id != nil { + if _, obj := check.scope.LookupParent(id.Name, token.NoPos); obj != nil { + if b, _ := obj.(*Builtin); b != nil && b.id == _Panic { + return true + } + } + } + } + + case *ast.ReturnStmt: + return true + + case *ast.BranchStmt: + if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH { + return true + } + + case *ast.BlockStmt: + return check.isTerminatingList(s.List, "") + + case *ast.IfStmt: + if s.Else != nil && + check.isTerminating(s.Body, "") && + check.isTerminating(s.Else, "") { + return true + } + + case *ast.SwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.TypeSwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.SelectStmt: + for _, s := range s.Body.List { + cc := s.(*ast.CommClause) + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + + } + return true + + case *ast.ForStmt: + if s.Cond == nil && !hasBreak(s.Body, label, true) { + return true + } + } + + return false +} + +func (check *Checker) isTerminatingList(list []ast.Stmt, label string) bool { + n := len(list) + return n > 0 && check.isTerminating(list[n-1], label) +} + +func (check *Checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool { + hasDefault := false + for _, s := range body.List { + cc := s.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + } + return hasDefault +} + +// TODO(gri) For nested breakable statements, the current implementation of hasBreak +// will traverse the same subtree repeatedly, once for each label. Replace +// with a single-pass label/break matching phase. + +// hasBreak reports if s is or contains a break statement +// referring to the label-ed statement or implicit-ly the +// closest outer breakable statement. +func hasBreak(s ast.Stmt, label string, implicit bool) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt, + *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, + *ast.DeferStmt, *ast.ReturnStmt: + // no chance + + case *ast.LabeledStmt: + return hasBreak(s.Stmt, label, implicit) + + case *ast.BranchStmt: + if s.Tok == token.BREAK { + if s.Label == nil { + return implicit + } + if s.Label.Name == label { + return true + } + } + + case *ast.BlockStmt: + return hasBreakList(s.List, label, implicit) + + case *ast.IfStmt: + if hasBreak(s.Body, label, implicit) || + s.Else != nil && hasBreak(s.Else, label, implicit) { + return true + } + + case *ast.CaseClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.TypeSwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.CommClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SelectStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.ForStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.RangeStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + } + + return false +} + +func hasBreakList(list []ast.Stmt, label string, implicit bool) bool { + for _, s := range list { + if hasBreak(s, label, implicit) { + return true + } + } + return false +} diff --git a/libgo/go/go/types/scope.go b/libgo/go/go/types/scope.go new file mode 100644 index 00000000000..3502840225f --- /dev/null +++ b/libgo/go/go/types/scope.go @@ -0,0 +1,190 @@ +// Copyright 2013 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 Scopes. + +package types + +import ( + "bytes" + "fmt" + "go/token" + "io" + "sort" + "strings" +) + +// TODO(gri) Provide scopes with a name or other mechanism so that +// objects can use that information for better printing. + +// A Scope maintains a set of objects and links to its containing +// (parent) and contained (children) scopes. Objects may be inserted +// and looked up by name. The zero value for Scope is a ready-to-use +// empty scope. +type Scope struct { + parent *Scope + children []*Scope + elems map[string]Object // lazily allocated + pos, end token.Pos // scope extent; may be invalid + comment string // for debugging only +} + +// NewScope returns a new, empty scope contained in the given parent +// scope, if any. The comment is for debugging only. +func NewScope(parent *Scope, pos, end token.Pos, comment string) *Scope { + s := &Scope{parent, nil, nil, pos, end, comment} + // don't add children to Universe scope! + if parent != nil && parent != Universe { + parent.children = append(parent.children, s) + } + return s +} + +// Parent returns the scope's containing (parent) scope. +func (s *Scope) Parent() *Scope { return s.parent } + +// Len() returns the number of scope elements. +func (s *Scope) Len() int { return len(s.elems) } + +// Names returns the scope's element names in sorted order. +func (s *Scope) Names() []string { + names := make([]string, len(s.elems)) + i := 0 + for name := range s.elems { + names[i] = name + i++ + } + sort.Strings(names) + return names +} + +// NumChildren() returns the number of scopes nested in s. +func (s *Scope) NumChildren() int { return len(s.children) } + +// Child returns the i'th child scope for 0 <= i < NumChildren(). +func (s *Scope) Child(i int) *Scope { return s.children[i] } + +// Lookup returns the object in scope s with the given name if such an +// object exists; otherwise the result is nil. +func (s *Scope) Lookup(name string) Object { + return s.elems[name] +} + +// LookupParent follows the parent chain of scopes starting with s until +// it finds a scope where Lookup(name) returns a non-nil object, and then +// returns that scope and object. If a valid position pos is provided, +// only objects that were declared at or before pos are considered. +// If no such scope and object exists, the result is (nil, nil). +// +// Note that obj.Parent() may be different from the returned scope if the +// object was inserted into the scope and already had a parent at that +// time (see Insert, below). This can only happen for dot-imported objects +// whose scope is the scope of the package that exported them. +func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) { + for ; s != nil; s = s.parent { + if obj := s.elems[name]; obj != nil && (!pos.IsValid() || obj.scopePos() <= pos) { + return s, obj + } + } + return nil, nil +} + +// Insert attempts to insert an object obj into scope s. +// If s already contains an alternative object alt with +// the same name, Insert leaves s unchanged and returns alt. +// Otherwise it inserts obj, sets the object's parent scope +// if not already set, and returns nil. +func (s *Scope) Insert(obj Object) Object { + name := obj.Name() + if alt := s.elems[name]; alt != nil { + return alt + } + if s.elems == nil { + s.elems = make(map[string]Object) + } + s.elems[name] = obj + if obj.Parent() == nil { + obj.setParent(s) + } + return nil +} + +// Pos and End describe the scope's source code extent [pos, end). +// The results are guaranteed to be valid only if the type-checked +// AST has complete position information. The extent is undefined +// for Universe and package scopes. +func (s *Scope) Pos() token.Pos { return s.pos } +func (s *Scope) End() token.Pos { return s.end } + +// Contains returns true if pos is within the scope's extent. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Contains(pos token.Pos) bool { + return s.pos <= pos && pos < s.end +} + +// Innermost returns the innermost (child) scope containing +// pos. If pos is not within any scope, the result is nil. +// The result is also nil for the Universe scope. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Innermost(pos token.Pos) *Scope { + // Package scopes do not have extents since they may be + // discontiguous, so iterate over the package's files. + if s.parent == Universe { + for _, s := range s.children { + if inner := s.Innermost(pos); inner != nil { + return inner + } + } + } + + if s.Contains(pos) { + for _, s := range s.children { + if s.Contains(pos) { + return s.Innermost(pos) + } + } + return s + } + return nil +} + +// WriteTo writes a string representation of the scope to w, +// with the scope elements sorted by name. +// The level of indentation is controlled by n >= 0, with +// n == 0 for no indentation. +// If recurse is set, it also writes nested (children) scopes. +func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) { + const ind = ". " + indn := strings.Repeat(ind, n) + + fmt.Fprintf(w, "%s%s scope %p {", indn, s.comment, s) + if len(s.elems) == 0 { + fmt.Fprintf(w, "}\n") + return + } + + fmt.Fprintln(w) + indn1 := indn + ind + for _, name := range s.Names() { + fmt.Fprintf(w, "%s%s\n", indn1, s.elems[name]) + } + + if recurse { + for _, s := range s.children { + fmt.Fprintln(w) + s.WriteTo(w, n+1, recurse) + } + } + + fmt.Fprintf(w, "%s}", indn) +} + +// String returns a string representation of the scope, for debugging. +func (s *Scope) String() string { + var buf bytes.Buffer + s.WriteTo(&buf, 0, false) + return buf.String() +} diff --git a/libgo/go/go/types/selection.go b/libgo/go/go/types/selection.go new file mode 100644 index 00000000000..124e0d39f02 --- /dev/null +++ b/libgo/go/go/types/selection.go @@ -0,0 +1,143 @@ +// Copyright 2013 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 Selections. + +package types + +import ( + "bytes" + "fmt" +) + +// SelectionKind describes the kind of a selector expression x.f +// (excluding qualified identifiers). +type SelectionKind int + +const ( + FieldVal SelectionKind = iota // x.f is a struct field selector + MethodVal // x.f is a method selector + MethodExpr // x.f is a method expression +) + +// A Selection describes a selector expression x.f. +// For the declarations: +// +// type T struct{ x int; E } +// type E struct{} +// func (e E) m() {} +// var p *T +// +// the following relations exist: +// +// Selector Kind Recv Obj Type Index Indirect +// +// p.x FieldVal T x int {0} true +// p.m MethodVal *T m func (e *T) m() {1, 0} true +// T.m MethodExpr T m func m(_ T) {1, 0} false +// +type Selection struct { + kind SelectionKind + recv Type // type of x + obj Object // object denoted by x.f + index []int // path from x to x.f + indirect bool // set if there was any pointer indirection on the path +} + +// Kind returns the selection kind. +func (s *Selection) Kind() SelectionKind { return s.kind } + +// Recv returns the type of x in x.f. +func (s *Selection) Recv() Type { return s.recv } + +// Obj returns the object denoted by x.f; a *Var for +// a field selection, and a *Func in all other cases. +func (s *Selection) Obj() Object { return s.obj } + +// Type returns the type of x.f, which may be different from the type of f. +// See Selection for more information. +func (s *Selection) Type() Type { + switch s.kind { + case MethodVal: + // The type of x.f is a method with its receiver type set + // to the type of x. + sig := *s.obj.(*Func).typ.(*Signature) + recv := *sig.recv + recv.typ = s.recv + sig.recv = &recv + return &sig + + case MethodExpr: + // The type of x.f is a function (without receiver) + // and an additional first argument with the same type as x. + // TODO(gri) Similar code is already in call.go - factor! + // TODO(gri) Compute this eagerly to avoid allocations. + sig := *s.obj.(*Func).typ.(*Signature) + arg0 := *sig.recv + sig.recv = nil + arg0.typ = s.recv + var params []*Var + if sig.params != nil { + params = sig.params.vars + } + sig.params = NewTuple(append([]*Var{&arg0}, params...)...) + return &sig + } + + // In all other cases, the type of x.f is the type of x. + return s.obj.Type() +} + +// Index describes the path from x to f in x.f. +// The last index entry is the field or method index of the type declaring f; +// either: +// +// 1) the list of declared methods of a named type; or +// 2) the list of methods of an interface type; or +// 3) the list of fields of a struct type. +// +// The earlier index entries are the indices of the embedded fields implicitly +// traversed to get from (the type of) x to f, starting at embedding depth 0. +func (s *Selection) Index() []int { return s.index } + +// Indirect reports whether any pointer indirection was required to get from +// x to f in x.f. +func (s *Selection) Indirect() bool { return s.indirect } + +func (s *Selection) String() string { return SelectionString(s, nil) } + +// SelectionString returns the string form of s. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +// +// Examples: +// "field (T) f int" +// "method (T) f(X) Y" +// "method expr (T) f(X) Y" +// +func SelectionString(s *Selection, qf Qualifier) string { + var k string + switch s.kind { + case FieldVal: + k = "field " + case MethodVal: + k = "method " + case MethodExpr: + k = "method expr " + default: + unreachable() + } + var buf bytes.Buffer + buf.WriteString(k) + buf.WriteByte('(') + WriteType(&buf, s.Recv(), qf) + fmt.Fprintf(&buf, ") %s", s.obj.Name()) + if T := s.Type(); s.kind == FieldVal { + buf.WriteByte(' ') + WriteType(&buf, T, qf) + } else { + WriteSignature(&buf, T.(*Signature), qf) + } + return buf.String() +} diff --git a/libgo/go/go/types/self_test.go b/libgo/go/go/types/self_test.go new file mode 100644 index 00000000000..10ad06fbca1 --- /dev/null +++ b/libgo/go/go/types/self_test.go @@ -0,0 +1,102 @@ +// Copyright 2013 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 ( + "flag" + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "path/filepath" + "testing" + "time" + + . "go/types" +) + +var benchmark = flag.Bool("b", false, "run benchmarks") + +func TestSelf(t *testing.T) { + fset := token.NewFileSet() + files, err := pkgFiles(fset, ".") + if err != nil { + t.Fatal(err) + } + + conf := Config{Importer: importer.Default()} + _, err = conf.Check("go/types", fset, files, nil) + if err != nil { + // Importing go/constant doesn't work in the + // build dashboard environment. Don't report an error + // for now so that the build remains green. + // TODO(gri) fix this + t.Log(err) // replace w/ t.Fatal eventually + return + } +} + +func TestBenchmark(t *testing.T) { + if !*benchmark { + return + } + + // We're not using testing's benchmarking mechanism directly + // because we want custom output. + + for _, p := range []string{"types", "constant", filepath.Join("internal", "gcimporter")} { + path := filepath.Join("..", p) + runbench(t, path, false) + runbench(t, path, true) + fmt.Println() + } +} + +func runbench(t *testing.T, path string, ignoreFuncBodies bool) { + fset := token.NewFileSet() + files, err := pkgFiles(fset, path) + if err != nil { + t.Fatal(err) + } + + b := testing.Benchmark(func(b *testing.B) { + for i := 0; i < b.N; i++ { + conf := Config{IgnoreFuncBodies: ignoreFuncBodies} + conf.Check(path, fset, files, nil) + } + }) + + // determine line count + lines := 0 + fset.Iterate(func(f *token.File) bool { + lines += f.LineCount() + return true + }) + + d := time.Duration(b.NsPerOp()) + fmt.Printf( + "%s: %s for %d lines (%d lines/s), ignoreFuncBodies = %v\n", + filepath.Base(path), d, lines, int64(float64(lines)/d.Seconds()), ignoreFuncBodies, + ) +} + +func pkgFiles(fset *token.FileSet, path string) ([]*ast.File, error) { + filenames, err := pkgFilenames(path) // from stdlib_test.go + if err != nil { + return nil, err + } + + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, 0) + if err != nil { + return nil, err + } + files = append(files, file) + } + + return files, nil +} diff --git a/libgo/go/go/types/sizes.go b/libgo/go/go/types/sizes.go new file mode 100644 index 00000000000..56fb310c294 --- /dev/null +++ b/libgo/go/go/types/sizes.go @@ -0,0 +1,211 @@ +// Copyright 2013 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 Sizes. + +package types + +// Sizes defines the sizing functions for package unsafe. +type Sizes interface { + // Alignof returns the alignment of a variable of type T. + // Alignof must implement the alignment guarantees required by the spec. + Alignof(T Type) int64 + + // Offsetsof returns the offsets of the given struct fields, in bytes. + // Offsetsof must implement the offset guarantees required by the spec. + Offsetsof(fields []*Var) []int64 + + // Sizeof returns the size of a variable of type T. + // Sizeof must implement the size guarantees required by the spec. + Sizeof(T Type) int64 +} + +// StdSizes is a convenience type for creating commonly used Sizes. +// It makes the following simplifying assumptions: +// +// - The size of explicitly sized basic types (int16, etc.) is the +// specified size. +// - The size of strings and interfaces is 2*WordSize. +// - The size of slices is 3*WordSize. +// - The size of an array of n elements corresponds to the size of +// a struct of n consecutive fields of the array's element type. +// - The size of a struct is the offset of the last field plus that +// field's size. As with all element types, if the struct is used +// in an array its size must first be aligned to a multiple of the +// struct's alignment. +// - All other types have size WordSize. +// - Arrays and structs are aligned per spec definition; all other +// types are naturally aligned with a maximum alignment MaxAlign. +// +// *StdSizes implements Sizes. +// +type StdSizes struct { + WordSize int64 // word size in bytes - must be >= 4 (32bits) + MaxAlign int64 // maximum alignment in bytes - must be >= 1 +} + +func (s *StdSizes) Alignof(T Type) int64 { + // For arrays and structs, alignment is defined in terms + // of alignment of the elements and fields, respectively. + switch t := T.Underlying().(type) { + case *Array: + // spec: "For a variable x of array type: unsafe.Alignof(x) + // is the same as unsafe.Alignof(x[0]), but at least 1." + return s.Alignof(t.elem) + case *Struct: + // spec: "For a variable x of struct type: unsafe.Alignof(x) + // is the largest of the values unsafe.Alignof(x.f) for each + // field f of x, but at least 1." + max := int64(1) + for _, f := range t.fields { + if a := s.Alignof(f.typ); a > max { + max = a + } + } + return max + } + a := s.Sizeof(T) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + if a > s.MaxAlign { + return s.MaxAlign + } + return a +} + +func (s *StdSizes) Offsetsof(fields []*Var) []int64 { + offsets := make([]int64, len(fields)) + var o int64 + for i, f := range fields { + a := s.Alignof(f.typ) + o = align(o, a) + offsets[i] = o + o += s.Sizeof(f.typ) + } + return offsets +} + +var basicSizes = [...]byte{ + Bool: 1, + Int8: 1, + Int16: 2, + Int32: 4, + Int64: 8, + Uint8: 1, + Uint16: 2, + Uint32: 4, + Uint64: 8, + Float32: 4, + Float64: 8, + Complex64: 8, + Complex128: 16, +} + +func (s *StdSizes) Sizeof(T Type) int64 { + switch t := T.Underlying().(type) { + case *Basic: + assert(isTyped(T)) + k := t.kind + if int(k) < len(basicSizes) { + if s := basicSizes[k]; s > 0 { + return int64(s) + } + } + if k == String { + return s.WordSize * 2 + } + case *Array: + n := t.len + if n == 0 { + return 0 + } + a := s.Alignof(t.elem) + z := s.Sizeof(t.elem) + return align(z, a)*(n-1) + z + case *Slice: + return s.WordSize * 3 + case *Struct: + n := t.NumFields() + if n == 0 { + return 0 + } + offsets := t.offsets + if t.offsets == nil { + // compute offsets on demand + offsets = s.Offsetsof(t.fields) + t.offsets = offsets + } + return offsets[n-1] + s.Sizeof(t.fields[n-1].typ) + case *Interface: + return s.WordSize * 2 + } + return s.WordSize // catch-all +} + +// stdSizes is used if Config.Sizes == nil. +var stdSizes = StdSizes{8, 8} + +func (conf *Config) alignof(T Type) int64 { + if s := conf.Sizes; s != nil { + if a := s.Alignof(T); a >= 1 { + return a + } + panic("Config.Sizes.Alignof returned an alignment < 1") + } + return stdSizes.Alignof(T) +} + +func (conf *Config) offsetsof(T *Struct) []int64 { + offsets := T.offsets + if offsets == nil && T.NumFields() > 0 { + // compute offsets on demand + if s := conf.Sizes; s != nil { + offsets = s.Offsetsof(T.fields) + // sanity checks + if len(offsets) != T.NumFields() { + panic("Config.Sizes.Offsetsof returned the wrong number of offsets") + } + for _, o := range offsets { + if o < 0 { + panic("Config.Sizes.Offsetsof returned an offset < 0") + } + } + } else { + offsets = stdSizes.Offsetsof(T.fields) + } + T.offsets = offsets + } + return offsets +} + +// offsetof returns the offset of the field specified via +// the index sequence relative to typ. All embedded fields +// must be structs (rather than pointer to structs). +func (conf *Config) offsetof(typ Type, index []int) int64 { + var o int64 + for _, i := range index { + s := typ.Underlying().(*Struct) + o += conf.offsetsof(s)[i] + typ = s.fields[i].typ + } + return o +} + +func (conf *Config) sizeof(T Type) int64 { + if s := conf.Sizes; s != nil { + if z := s.Sizeof(T); z >= 0 { + return z + } + panic("Config.Sizes.Sizeof returned a size < 0") + } + return stdSizes.Sizeof(T) +} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a +} diff --git a/libgo/go/go/types/stdlib_test.go b/libgo/go/go/types/stdlib_test.go new file mode 100644 index 00000000000..c6c946e976a --- /dev/null +++ b/libgo/go/go/types/stdlib_test.go @@ -0,0 +1,279 @@ +// Copyright 2013 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 tests types.Check by using it to +// typecheck the standard library and tests. + +package types_test + +import ( + "fmt" + "go/ast" + "go/build" + "go/importer" + "go/parser" + "go/scanner" + "go/token" + "internal/testenv" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + . "go/types" +) + +var ( + pkgCount int // number of packages processed + start time.Time + + // Use the same importer for all std lib tests to + // avoid repeated importing of the same packages. + stdLibImporter = importer.Default() +) + +func TestStdlib(t *testing.T) { + testenv.MustHaveGoBuild(t) + + start = time.Now() + walkDirs(t, filepath.Join(runtime.GOROOT(), "src")) + if testing.Verbose() { + fmt.Println(pkgCount, "packages typechecked in", time.Since(start)) + } +} + +// firstComment returns the contents of the first comment in +// the given file, assuming there's one within the first KB. +func firstComment(filename string) string { + f, err := os.Open(filename) + if err != nil { + return "" + } + defer f.Close() + + var src [1 << 10]byte // read at most 1KB + n, _ := f.Read(src[:]) + + var s scanner.Scanner + s.Init(fset.AddFile("", fset.Base(), n), src[:n], nil, scanner.ScanComments) + for { + _, tok, lit := s.Scan() + switch tok { + case token.COMMENT: + // remove trailing */ of multi-line comment + if lit[1] == '*' { + lit = lit[:len(lit)-2] + } + return strings.TrimSpace(lit[2:]) + case token.EOF: + return "" + } + } +} + +func testTestDir(t *testing.T, path string, ignore ...string) { + files, err := ioutil.ReadDir(path) + if err != nil { + t.Fatal(err) + } + + excluded := make(map[string]bool) + for _, filename := range ignore { + excluded[filename] = true + } + + fset := token.NewFileSet() + for _, f := range files { + // filter directory contents + if f.IsDir() || !strings.HasSuffix(f.Name(), ".go") || excluded[f.Name()] { + continue + } + + // get per-file instructions + expectErrors := false + filename := filepath.Join(path, f.Name()) + if cmd := firstComment(filename); cmd != "" { + switch cmd { + case "skip", "compiledir": + continue // ignore this file + case "errorcheck": + expectErrors = true + } + } + + // parse and type-check file + file, err := parser.ParseFile(fset, filename, nil, 0) + if err == nil { + conf := Config{Importer: stdLibImporter} + _, err = conf.Check(filename, fset, []*ast.File{file}, nil) + } + + if expectErrors { + if err == nil { + t.Errorf("expected errors but found none in %s", filename) + } + } else { + if err != nil { + t.Error(err) + } + } + } +} + +func TestStdTest(t *testing.T) { + testenv.MustHaveGoBuild(t) + + // test/recover4.go is only built for Linux and Darwin. + // TODO(gri) Remove once tests consider +build tags (issue 10370). + if runtime.GOOS != "linux" && runtime.GOOS != "darwin" { + return + } + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test"), + "cmplxdivide.go", // also needs file cmplxdivide1.go - ignore + "sigchld.go", // don't work on Windows; testTestDir should consult build tags + ) +} + +func TestStdFixed(t *testing.T) { + testenv.MustHaveGoBuild(t) + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "fixedbugs"), + "bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore + "bug459.go", // possibly incorrect test - see issue 6703 (pending spec clarification) + "issue3924.go", // possibly incorrect test - see issue 6671 (pending spec clarification) + "issue6889.go", // gc-specific test + "issue7746.go", // large constants - consumes too much memory + "issue11326.go", // large constants + "issue11326b.go", // large constants + ) +} + +func TestStdKen(t *testing.T) { + testenv.MustHaveGoBuild(t) + + testTestDir(t, filepath.Join(runtime.GOROOT(), "test", "ken")) +} + +// Package paths of excluded packages. +var excluded = map[string]bool{ + "builtin": true, +} + +// typecheck typechecks the given package files. +func typecheck(t *testing.T, path string, filenames []string) { + fset := token.NewFileSet() + + // parse package files + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.AllErrors) + if err != nil { + // the parser error may be a list of individual errors; report them all + if list, ok := err.(scanner.ErrorList); ok { + for _, err := range list { + t.Error(err) + } + return + } + t.Error(err) + return + } + + if testing.Verbose() { + if len(files) == 0 { + fmt.Println("package", file.Name.Name) + } + fmt.Println("\t", filename) + } + + files = append(files, file) + } + + // typecheck package files + conf := Config{ + Error: func(err error) { t.Error(err) }, + Importer: stdLibImporter, + } + info := Info{Uses: make(map[*ast.Ident]Object)} + conf.Check(path, fset, files, &info) + pkgCount++ + + // Perform checks of API invariants. + + // All Objects have a package, except predeclared ones. + errorError := Universe.Lookup("error").Type().Underlying().(*Interface).ExplicitMethod(0) // (error).Error + for id, obj := range info.Uses { + predeclared := obj == Universe.Lookup(obj.Name()) || obj == errorError + if predeclared == (obj.Pkg() != nil) { + posn := fset.Position(id.Pos()) + if predeclared { + t.Errorf("%s: predeclared object with package: %s", posn, obj) + } else { + t.Errorf("%s: user-defined object without package: %s", posn, obj) + } + } + } +} + +// pkgFilenames returns the list of package filenames for the given directory. +func pkgFilenames(dir string) ([]string, error) { + ctxt := build.Default + ctxt.CgoEnabled = false + pkg, err := ctxt.ImportDir(dir, 0) + if err != nil { + if _, nogo := err.(*build.NoGoError); nogo { + return nil, nil // no *.go files, not an error + } + return nil, err + } + if excluded[pkg.ImportPath] { + return nil, nil + } + var filenames []string + for _, name := range pkg.GoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + for _, name := range pkg.TestGoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + return filenames, nil +} + +// Note: Could use filepath.Walk instead of walkDirs but that wouldn't +// necessarily be shorter or clearer after adding the code to +// terminate early for -short tests. + +func walkDirs(t *testing.T, dir string) { + // limit run time for short tests + if testing.Short() && time.Since(start) >= 750*time.Millisecond { + return + } + + fis, err := ioutil.ReadDir(dir) + if err != nil { + t.Error(err) + return + } + + // typecheck package in directory + files, err := pkgFilenames(dir) + if err != nil { + t.Error(err) + return + } + if files != nil { + typecheck(t, dir, files) + } + + // traverse subdirectories, but don't walk into testdata + for _, fi := range fis { + if fi.IsDir() && fi.Name() != "testdata" { + walkDirs(t, filepath.Join(dir, fi.Name())) + } + } +} diff --git a/libgo/go/go/types/stmt.go b/libgo/go/go/types/stmt.go new file mode 100644 index 00000000000..88a1d9b8668 --- /dev/null +++ b/libgo/go/go/types/stmt.go @@ -0,0 +1,744 @@ +// 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 implements typechecking of statements. + +package types + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" +) + +func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt) { + if trace { + if name == "" { + name = "<function literal>" + } + fmt.Printf("--- %s: %s {\n", name, sig) + defer fmt.Println("--- <end>") + } + + // set function scope extent + sig.scope.pos = body.Pos() + sig.scope.end = body.End() + + // save/restore current context and setup function context + // (and use 0 indentation at function start) + defer func(ctxt context, indent int) { + check.context = ctxt + check.indent = indent + }(check.context, check.indent) + check.context = context{ + decl: decl, + scope: sig.scope, + sig: sig, + } + check.indent = 0 + + check.stmtList(0, body.List) + + if check.hasLabel { + check.labels(body) + } + + if sig.results.Len() > 0 && !check.isTerminating(body, "") { + check.error(body.Rbrace, "missing return") + } + + // spec: "Implementation restriction: A compiler may make it illegal to + // declare a variable inside a function body if the variable is never used." + // (One could check each scope after use, but that distributes this check + // over several places because CloseScope is not always called explicitly.) + check.usage(sig.scope) +} + +func (check *Checker) usage(scope *Scope) { + for _, obj := range scope.elems { + if v, _ := obj.(*Var); v != nil && !v.used { + check.softErrorf(v.pos, "%s declared but not used", v.name) + } + } + for _, scope := range scope.children { + check.usage(scope) + } +} + +// stmtContext is a bitset describing which +// control-flow statements are permissible. +type stmtContext uint + +const ( + breakOk stmtContext = 1 << iota + continueOk + fallthroughOk +) + +func (check *Checker) simpleStmt(s ast.Stmt) { + if s != nil { + check.stmt(0, s) + } +} + +func (check *Checker) stmtList(ctxt stmtContext, list []ast.Stmt) { + ok := ctxt&fallthroughOk != 0 + inner := ctxt &^ fallthroughOk + for i, s := range list { + inner := inner + if ok && i+1 == len(list) { + inner |= fallthroughOk + } + check.stmt(inner, s) + } +} + +func (check *Checker) multipleDefaults(list []ast.Stmt) { + var first ast.Stmt + for _, s := range list { + var d ast.Stmt + switch c := s.(type) { + case *ast.CaseClause: + if len(c.List) == 0 { + d = s + } + case *ast.CommClause: + if c.Comm == nil { + d = s + } + default: + check.invalidAST(s.Pos(), "case/communication clause expected") + } + if d != nil { + if first != nil { + check.errorf(d.Pos(), "multiple defaults (first at %s)", first.Pos()) + } else { + first = d + } + } + } +} + +func (check *Checker) openScope(s ast.Stmt, comment string) { + scope := NewScope(check.scope, s.Pos(), s.End(), comment) + check.recordScope(s, scope) + check.scope = scope +} + +func (check *Checker) closeScope() { + check.scope = check.scope.Parent() +} + +func assignOp(op token.Token) token.Token { + // token_test.go verifies the token ordering this function relies on + if token.ADD_ASSIGN <= op && op <= token.AND_NOT_ASSIGN { + return op + (token.ADD - token.ADD_ASSIGN) + } + return token.ILLEGAL +} + +func (check *Checker) suspendedCall(keyword string, call *ast.CallExpr) { + var x operand + var msg string + switch check.rawExpr(&x, call, nil) { + case conversion: + msg = "requires function call, not conversion" + case expression: + msg = "discards result of" + case statement: + return + default: + unreachable() + } + check.errorf(x.pos(), "%s %s %s", keyword, msg, &x) +} + +func (check *Checker) caseValues(x operand /* copy argument (not *operand!) */, values []ast.Expr) { + // No duplicate checking for now. See issue 4524. + for _, e := range values { + var y operand + check.expr(&y, e) + if y.mode == invalid { + return + } + // TODO(gri) The convertUntyped call pair below appears in other places. Factor! + // Order matters: By comparing y against x, error positions are at the case values. + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + return + } + check.convertUntyped(&x, y.typ) + if x.mode == invalid { + return + } + check.comparison(&y, &x, token.EQL) + } +} + +func (check *Checker) caseTypes(x *operand, xtyp *Interface, types []ast.Expr, seen map[Type]token.Pos) (T Type) { +L: + for _, e := range types { + T = check.typOrNil(e) + if T == Typ[Invalid] { + continue + } + // complain about duplicate types + // TODO(gri) use a type hash to avoid quadratic algorithm + for t, pos := range seen { + if T == nil && t == nil || T != nil && t != nil && Identical(T, t) { + // talk about "case" rather than "type" because of nil case + check.error(e.Pos(), "duplicate case in type switch") + check.errorf(pos, "\tprevious case %s", T) // secondary error, \t indented + continue L + } + } + seen[T] = e.Pos() + if T != nil { + check.typeAssertion(e.Pos(), x, xtyp, T) + } + } + return +} + +// stmt typechecks statement s. +func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { + // statements cannot use iota in general + // (constant declarations set it explicitly) + assert(check.iota == nil) + + // statements must end with the same top scope as they started with + if debug { + defer func(scope *Scope) { + // don't check if code is panicking + if p := recover(); p != nil { + panic(p) + } + assert(scope == check.scope) + }(check.scope) + } + + inner := ctxt &^ fallthroughOk + switch s := s.(type) { + case *ast.BadStmt, *ast.EmptyStmt: + // ignore + + case *ast.DeclStmt: + check.declStmt(s.Decl) + + case *ast.LabeledStmt: + check.hasLabel = true + check.stmt(ctxt, s.Stmt) + + case *ast.ExprStmt: + // spec: "With the exception of specific built-in functions, + // function and method calls and receive operations can appear + // in statement context. Such statements may be parenthesized." + var x operand + kind := check.rawExpr(&x, s.X, nil) + var msg string + switch x.mode { + default: + if kind == statement { + return + } + msg = "is not used" + case builtin: + msg = "must be called" + case typexpr: + msg = "is not an expression" + } + check.errorf(x.pos(), "%s %s", &x, msg) + + case *ast.SendStmt: + var ch, x operand + check.expr(&ch, s.Chan) + check.expr(&x, s.Value) + if ch.mode == invalid || x.mode == invalid { + return + } + if tch, ok := ch.typ.Underlying().(*Chan); !ok || tch.dir == RecvOnly || !check.assignment(&x, tch.elem) { + if x.mode != invalid { + check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) + } + } + + case *ast.IncDecStmt: + var op token.Token + switch s.Tok { + case token.INC: + op = token.ADD + case token.DEC: + op = token.SUB + default: + check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) + return + } + var x operand + Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position + check.binary(&x, nil, s.X, Y, op) + if x.mode == invalid { + return + } + check.assignVar(s.X, &x) + + case *ast.AssignStmt: + switch s.Tok { + case token.ASSIGN, token.DEFINE: + if len(s.Lhs) == 0 { + check.invalidAST(s.Pos(), "missing lhs in assignment") + return + } + if s.Tok == token.DEFINE { + check.shortVarDecl(s.TokPos, s.Lhs, s.Rhs) + } else { + // regular assignment + check.assignVars(s.Lhs, s.Rhs) + } + + default: + // assignment operations + if len(s.Lhs) != 1 || len(s.Rhs) != 1 { + check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) + return + } + op := assignOp(s.Tok) + if op == token.ILLEGAL { + check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) + return + } + var x operand + check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op) + if x.mode == invalid { + return + } + check.assignVar(s.Lhs[0], &x) + } + + case *ast.GoStmt: + check.suspendedCall("go", s.Call) + + case *ast.DeferStmt: + check.suspendedCall("defer", s.Call) + + case *ast.ReturnStmt: + res := check.sig.results + if res.Len() > 0 { + // function returns results + // (if one, say the first, result parameter is named, all of them are named) + if len(s.Results) == 0 && res.vars[0].name != "" { + // spec: "Implementation restriction: A compiler may disallow an empty expression + // list in a "return" statement if a different entity (constant, type, or variable) + // with the same name as a result parameter is in scope at the place of the return." + for _, obj := range res.vars { + if _, alt := check.scope.LookupParent(obj.name, check.pos); alt != nil && alt != obj { + check.errorf(s.Pos(), "result parameter %s not in scope at return", obj.name) + check.errorf(alt.Pos(), "\tinner declaration of %s", obj) + // ok to continue + } + } + } else { + // return has results or result parameters are unnamed + check.initVars(res.vars, s.Results, s.Return) + } + } else if len(s.Results) > 0 { + check.error(s.Results[0].Pos(), "no result values expected") + check.use(s.Results...) + } + + case *ast.BranchStmt: + if s.Label != nil { + check.hasLabel = true + return // checked in 2nd pass (check.labels) + } + switch s.Tok { + case token.BREAK: + if ctxt&breakOk == 0 { + check.error(s.Pos(), "break not in for, switch, or select statement") + } + case token.CONTINUE: + if ctxt&continueOk == 0 { + check.error(s.Pos(), "continue not in for statement") + } + case token.FALLTHROUGH: + if ctxt&fallthroughOk == 0 { + check.error(s.Pos(), "fallthrough statement out of place") + } + default: + check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) + } + + case *ast.BlockStmt: + check.openScope(s, "block") + defer check.closeScope() + + check.stmtList(inner, s.List) + + case *ast.IfStmt: + check.openScope(s, "if") + defer check.closeScope() + + check.simpleStmt(s.Init) + var x operand + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond.Pos(), "non-boolean condition in if statement") + } + check.stmt(inner, s.Body) + if s.Else != nil { + check.stmt(inner, s.Else) + } + + case *ast.SwitchStmt: + inner |= breakOk + check.openScope(s, "switch") + defer check.closeScope() + + check.simpleStmt(s.Init) + var x operand + 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 = constant.MakeBool(true) + x.expr = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} + } + + check.multipleDefaults(s.Body.List) + + for i, c := range s.Body.List { + clause, _ := c.(*ast.CaseClause) + if clause == nil { + check.invalidAST(c.Pos(), "incorrect expression switch case") + continue + } + if x.mode != invalid { + check.caseValues(x, clause.List) + } + check.openScope(clause, "case") + inner := inner + if i+1 < len(s.Body.List) { + inner |= fallthroughOk + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + case *ast.TypeSwitchStmt: + inner |= breakOk + check.openScope(s, "type switch") + defer check.closeScope() + + check.simpleStmt(s.Init) + + // A type switch guard must be of the form: + // + // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . + // + // The parser is checking syntactic correctness; + // remaining syntactic errors are considered AST errors here. + // TODO(gri) better factoring of error handling (invalid ASTs) + // + var lhs *ast.Ident // lhs identifier or nil + var rhs ast.Expr + switch guard := s.Assign.(type) { + case *ast.ExprStmt: + rhs = guard.X + case *ast.AssignStmt: + if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + + lhs, _ = guard.Lhs[0].(*ast.Ident) + if lhs == nil { + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + + if lhs.Name == "_" { + // _ := x.(type) is an invalid short variable declaration + check.softErrorf(lhs.Pos(), "no new variable on left side of :=") + lhs = nil // avoid declared but not used error below + } else { + check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause + } + + rhs = guard.Rhs[0] + + default: + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + + // rhs must be of the form: expr.(type) and expr must be an interface + expr, _ := rhs.(*ast.TypeAssertExpr) + if expr == nil || expr.Type != nil { + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + var x operand + check.expr(&x, expr.X) + if x.mode == invalid { + return + } + xtyp, _ := x.typ.Underlying().(*Interface) + if xtyp == nil { + check.errorf(x.pos(), "%s is not an interface", &x) + return + } + + check.multipleDefaults(s.Body.List) + + var lhsVars []*Var // list of implicitly declared lhs variables + seen := make(map[Type]token.Pos) // map of seen types to positions + for _, s := range s.Body.List { + clause, _ := s.(*ast.CaseClause) + if clause == nil { + check.invalidAST(s.Pos(), "incorrect type switch case") + continue + } + // Check each type in this type switch case. + T := check.caseTypes(&x, xtyp, clause.List, seen) + check.openScope(clause, "case") + // If lhs exists, declare a corresponding variable in the case-local scope. + if lhs != nil { + // spec: "The TypeSwitchGuard may include a short variable declaration. + // When that form is used, the variable is declared at the beginning of + // the implicit block in each clause. In clauses with a case listing + // exactly one type, the variable has that type; otherwise, the variable + // has the type of the expression in the TypeSwitchGuard." + if len(clause.List) != 1 || T == nil { + T = x.typ + } + obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) + scopePos := clause.End() + if len(clause.Body) > 0 { + scopePos = clause.Body[0].Pos() + } + check.declare(check.scope, nil, obj, scopePos) + check.recordImplicit(clause, obj) + // For the "declared but not used" error, all lhs variables act as + // one; i.e., if any one of them is 'used', all of them are 'used'. + // Collect them for later analysis. + lhsVars = append(lhsVars, obj) + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + // If lhs exists, we must have at least one lhs variable that was used. + if lhs != nil { + var used bool + for _, v := range lhsVars { + if v.used { + used = true + } + v.used = true // avoid usage error when checking entire function + } + if !used { + check.softErrorf(lhs.Pos(), "%s declared but not used", lhs.Name) + } + } + + case *ast.SelectStmt: + inner |= breakOk + + check.multipleDefaults(s.Body.List) + + for _, s := range s.Body.List { + clause, _ := s.(*ast.CommClause) + if clause == nil { + continue // error reported before + } + + // clause.Comm must be a SendStmt, RecvStmt, or default case + valid := false + var rhs ast.Expr // rhs of RecvStmt, or nil + switch s := clause.Comm.(type) { + case nil, *ast.SendStmt: + valid = true + case *ast.AssignStmt: + if len(s.Rhs) == 1 { + rhs = s.Rhs[0] + } + case *ast.ExprStmt: + rhs = s.X + } + + // if present, rhs must be a receive operation + if rhs != nil { + if x, _ := unparen(rhs).(*ast.UnaryExpr); x != nil && x.Op == token.ARROW { + valid = true + } + } + + if !valid { + check.error(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)") + continue + } + + check.openScope(s, "case") + if clause.Comm != nil { + check.stmt(inner, clause.Comm) + } + check.stmtList(inner, clause.Body) + check.closeScope() + } + + case *ast.ForStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + check.simpleStmt(s.Init) + if s.Cond != nil { + var x operand + check.expr(&x, s.Cond) + if x.mode != invalid && !isBoolean(x.typ) { + check.error(s.Cond.Pos(), "non-boolean condition in for statement") + } + } + check.simpleStmt(s.Post) + // spec: "The init statement may be a short variable + // declaration, but the post statement must not." + if s, _ := s.Post.(*ast.AssignStmt); s != nil && s.Tok == token.DEFINE { + check.softErrorf(s.Pos(), "cannot declare in post statement") + check.use(s.Lhs...) // avoid follow-up errors + } + check.stmt(inner, s.Body) + + case *ast.RangeStmt: + inner |= breakOk | continueOk + check.openScope(s, "for") + defer check.closeScope() + + // check expression to iterate over + var x operand + check.expr(&x, s.X) + + // determine key/value types + var key, val Type + if x.mode != invalid { + switch typ := x.typ.Underlying().(type) { + case *Basic: + if isString(typ) { + key = Typ[Int] + val = universeRune // use 'rune' name + } + case *Array: + key = Typ[Int] + val = typ.elem + case *Slice: + key = Typ[Int] + val = typ.elem + case *Pointer: + if typ, _ := typ.base.Underlying().(*Array); typ != nil { + key = Typ[Int] + val = typ.elem + } + case *Map: + key = typ.key + val = typ.elem + case *Chan: + key = typ.elem + val = Typ[Invalid] + if typ.dir == SendOnly { + check.errorf(x.pos(), "cannot range over send-only channel %s", &x) + // ok to continue + } + if s.Value != nil { + check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) + // ok to continue + } + } + } + + if key == nil { + check.errorf(x.pos(), "cannot range over %s", &x) + // ok to continue + } + + // check assignment to/declaration of iteration variables + // (irregular assignment, cannot easily map to existing assignment checks) + + // lhs expressions and initialization value (rhs) types + lhs := [2]ast.Expr{s.Key, s.Value} + rhs := [2]Type{key, val} // key, val may be nil + + if s.Tok == token.DEFINE { + // short variable declaration; variable scope starts after the range clause + // (the for loop opens a new scope, so variables on the lhs never redeclare + // previously declared variables) + var vars []*Var + for i, lhs := range lhs { + if lhs == nil { + continue + } + + // determine lhs variable + var obj *Var + if ident, _ := lhs.(*ast.Ident); ident != nil { + // declare new variable + name := ident.Name + obj = NewVar(ident.Pos(), check.pkg, name, nil) + check.recordDef(ident, obj) + // _ variables don't count as new variables + if name != "_" { + vars = append(vars, obj) + } + } else { + check.errorf(lhs.Pos(), "cannot declare %s", lhs) + obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable + } + + // initialize lhs variable + if typ := rhs[i]; typ != nil { + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = typ + check.initVar(obj, &x, false) + } else { + obj.typ = Typ[Invalid] + obj.used = true // don't complain about unused variable + } + } + + // declare variables + if len(vars) > 0 { + for _, obj := range vars { + // spec: "The scope of a constant or variable identifier declared inside + // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl + // for short variable declarations) and ends at the end of the innermost + // containing block." + scopePos := s.End() + check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) + } + } else { + check.error(s.TokPos, "no new variables on left side of :=") + } + } else { + // ordinary assignment + for i, lhs := range lhs { + if lhs == nil { + continue + } + if typ := rhs[i]; typ != nil { + x.mode = value + x.expr = lhs // we don't have a better rhs expression to use here + x.typ = typ + check.assignVar(lhs, &x) + } + } + } + + check.stmt(inner, s.Body) + + default: + check.error(s.Pos(), "invalid statement") + } +} diff --git a/libgo/go/go/types/token_test.go b/libgo/go/go/types/token_test.go new file mode 100644 index 00000000000..705bb295a16 --- /dev/null +++ b/libgo/go/go/types/token_test.go @@ -0,0 +1,47 @@ +// Copyright 2013 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 checks invariants of token.Token ordering that we rely on +// since package go/token doesn't provide any guarantees at the moment. + +package types + +import ( + "go/token" + "testing" +) + +var assignOps = map[token.Token]token.Token{ + token.ADD_ASSIGN: token.ADD, + token.SUB_ASSIGN: token.SUB, + token.MUL_ASSIGN: token.MUL, + token.QUO_ASSIGN: token.QUO, + token.REM_ASSIGN: token.REM, + token.AND_ASSIGN: token.AND, + token.OR_ASSIGN: token.OR, + token.XOR_ASSIGN: token.XOR, + token.SHL_ASSIGN: token.SHL, + token.SHR_ASSIGN: token.SHR, + token.AND_NOT_ASSIGN: token.AND_NOT, +} + +func TestZeroTok(t *testing.T) { + // zero value for token.Token must be token.ILLEGAL + var zero token.Token + if token.ILLEGAL != zero { + t.Errorf("%s == %d; want 0", token.ILLEGAL, zero) + } +} + +func TestAssignOp(t *testing.T) { + // there are fewer than 256 tokens + for i := 0; i < 256; i++ { + tok := token.Token(i) + got := assignOp(tok) + want := assignOps[tok] + if got != want { + t.Errorf("for assignOp(%s): got %s; want %s", tok, got, want) + } + } +} diff --git a/libgo/go/go/types/type.go b/libgo/go/go/types/type.go new file mode 100644 index 00000000000..1df8b45b285 --- /dev/null +++ b/libgo/go/go/types/type.go @@ -0,0 +1,454 @@ +// 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 "sort" + +// TODO(gri) Revisit factory functions - make sure they have all relevant parameters. + +// A Type represents a type of Go. +// All types implement the Type interface. +type Type interface { + // Underlying returns the underlying type of a type. + Underlying() Type + + // String returns a string representation of a type. + String() string +} + +// BasicKind describes the kind of basic type. +type BasicKind int + +const ( + Invalid BasicKind = iota // type is invalid + + // predeclared types + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + + // types for untyped values + UntypedBool + UntypedInt + UntypedRune + UntypedFloat + UntypedComplex + UntypedString + UntypedNil + + // aliases + Byte = Uint8 + Rune = Int32 +) + +// BasicInfo is a set of flags describing properties of a basic type. +type BasicInfo int + +// Properties of basic types. +const ( + IsBoolean BasicInfo = 1 << iota + IsInteger + IsUnsigned + IsFloat + IsComplex + IsString + IsUntyped + + IsOrdered = IsInteger | IsFloat | IsString + IsNumeric = IsInteger | IsFloat | IsComplex + IsConstType = IsBoolean | IsNumeric | IsString +) + +// A Basic represents a basic type. +type Basic struct { + kind BasicKind + info BasicInfo + name string +} + +// Kind returns the kind of basic type b. +func (b *Basic) Kind() BasicKind { return b.kind } + +// Info returns information about properties of basic type b. +func (b *Basic) Info() BasicInfo { return b.info } + +// Name returns the name of basic type b. +func (b *Basic) Name() string { return b.name } + +// An Array represents an array type. +type Array struct { + len int64 + elem Type +} + +// NewArray returns a new array type for the given element type and length. +func NewArray(elem Type, len int64) *Array { return &Array{len, elem} } + +// Len returns the length of array a. +func (a *Array) Len() int64 { return a.len } + +// Elem returns element type of array a. +func (a *Array) Elem() Type { return a.elem } + +// A Slice represents a slice type. +type Slice struct { + elem Type +} + +// NewSlice returns a new slice type for the given element type. +func NewSlice(elem Type) *Slice { return &Slice{elem} } + +// Elem returns the element type of slice s. +func (s *Slice) Elem() Type { return s.elem } + +// A Struct represents a struct type. +type Struct struct { + fields []*Var + tags []string // field tags; nil if there are no tags + // TODO(gri) access to offsets is not threadsafe - fix this + offsets []int64 // field offsets in bytes, lazily initialized +} + +// NewStruct returns a new struct with the given fields and corresponding field tags. +// If a field with index i has a tag, tags[i] must be that tag, but len(tags) may be +// only as long as required to hold the tag with the largest index i. Consequently, +// if no field has a tag, tags may be nil. +func NewStruct(fields []*Var, tags []string) *Struct { + var fset objset + for _, f := range fields { + if f.name != "_" && fset.insert(f) != nil { + panic("multiple fields with the same name") + } + } + if len(tags) > len(fields) { + panic("more tags than fields") + } + return &Struct{fields: fields, tags: tags} +} + +// NumFields returns the number of fields in the struct (including blank and anonymous fields). +func (s *Struct) NumFields() int { return len(s.fields) } + +// Field returns the i'th field for 0 <= i < NumFields(). +func (s *Struct) Field(i int) *Var { return s.fields[i] } + +// Tag returns the i'th field tag for 0 <= i < NumFields(). +func (s *Struct) Tag(i int) string { + if i < len(s.tags) { + return s.tags[i] + } + return "" +} + +// A Pointer represents a pointer type. +type Pointer struct { + base Type // element type +} + +// NewPointer returns a new pointer type for the given element (base) type. +func NewPointer(elem Type) *Pointer { return &Pointer{base: elem} } + +// Elem returns the element type for the given pointer p. +func (p *Pointer) Elem() Type { return p.base } + +// A Tuple represents an ordered list of variables; a nil *Tuple is a valid (empty) tuple. +// Tuples are used as components of signatures and to represent the type of multiple +// assignments; they are not first class types of Go. +type Tuple struct { + vars []*Var +} + +// NewTuple returns a new tuple for the given variables. +func NewTuple(x ...*Var) *Tuple { + if len(x) > 0 { + return &Tuple{x} + } + return nil +} + +// Len returns the number variables of tuple t. +func (t *Tuple) Len() int { + if t != nil { + return len(t.vars) + } + return 0 +} + +// At returns the i'th variable of tuple t. +func (t *Tuple) At(i int) *Var { return t.vars[i] } + +// A Signature represents a (non-builtin) function or method type. +type Signature struct { + // We need to keep the scope in Signature (rather than passing it around + // and store it in the Func Object) because when type-checking a function + // literal we call the general type checker which returns a general Type. + // We then unpack the *Signature and use the scope for the literal body. + scope *Scope // function scope, present for package-local signatures + recv *Var // nil if not a method + params *Tuple // (incoming) parameters from left to right; or nil + results *Tuple // (outgoing) results from left to right; or nil + variadic bool // true if the last parameter's type is of the form ...T (or string, for append built-in only) +} + +// NewSignature returns a new function type for the given receiver, parameters, +// and results, either of which may be nil. If variadic is set, the function +// is variadic, it must have at least one parameter, and the last parameter +// must be of unnamed slice type. +func NewSignature(recv *Var, params, results *Tuple, variadic bool) *Signature { + if variadic { + n := params.Len() + if n == 0 { + panic("types.NewSignature: variadic function must have at least one parameter") + } + if _, ok := params.At(n - 1).typ.(*Slice); !ok { + panic("types.NewSignature: variadic parameter must be of unnamed slice type") + } + } + return &Signature{nil, recv, params, results, variadic} +} + +// Recv returns the receiver of signature s (if a method), or nil if a +// function. +// +// For an abstract method, Recv returns the enclosing interface either +// as a *Named or an *Interface. Due to embedding, an interface may +// contain methods whose receiver type is a different interface. +func (s *Signature) Recv() *Var { return s.recv } + +// Params returns the parameters of signature s, or nil. +func (s *Signature) Params() *Tuple { return s.params } + +// Results returns the results of signature s, or nil. +func (s *Signature) Results() *Tuple { return s.results } + +// Variadic reports whether the signature s is variadic. +func (s *Signature) Variadic() bool { return s.variadic } + +// An Interface represents an interface type. +type Interface struct { + methods []*Func // ordered list of explicitly declared methods + embeddeds []*Named // ordered list of explicitly embedded types + + allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) +} + +// NewInterface returns a new interface for the given methods and embedded types. +func NewInterface(methods []*Func, embeddeds []*Named) *Interface { + typ := new(Interface) + + var mset objset + for _, m := range methods { + if mset.insert(m) != nil { + panic("multiple methods with the same name") + } + // set receiver + // TODO(gri) Ideally, we should use a named type here instead of + // typ, for less verbose printing of interface method signatures. + m.typ.(*Signature).recv = NewVar(m.pos, m.pkg, "", typ) + } + sort.Sort(byUniqueMethodName(methods)) + + if embeddeds == nil { + sort.Sort(byUniqueTypeName(embeddeds)) + } + + typ.methods = methods + typ.embeddeds = embeddeds + return typ +} + +// NumExplicitMethods returns the number of explicitly declared methods of interface t. +func (t *Interface) NumExplicitMethods() int { return len(t.methods) } + +// ExplicitMethod returns the i'th explicitly declared method of interface t for 0 <= i < t.NumExplicitMethods(). +// The methods are ordered by their unique Id. +func (t *Interface) ExplicitMethod(i int) *Func { return t.methods[i] } + +// NumEmbeddeds returns the number of embedded types in interface t. +func (t *Interface) NumEmbeddeds() int { return len(t.embeddeds) } + +// Embedded returns the i'th embedded type of interface t for 0 <= i < t.NumEmbeddeds(). +// The types are ordered by the corresponding TypeName's unique Id. +func (t *Interface) Embedded(i int) *Named { return t.embeddeds[i] } + +// NumMethods returns the total number of methods of interface t. +func (t *Interface) NumMethods() int { return len(t.allMethods) } + +// Method returns the i'th method of interface t for 0 <= i < t.NumMethods(). +// The methods are ordered by their unique Id. +func (t *Interface) Method(i int) *Func { return t.allMethods[i] } + +// Empty returns true if t is the empty interface. +func (t *Interface) Empty() bool { return len(t.allMethods) == 0 } + +// Complete computes the interface's method set. It must be called by users of +// NewInterface after the interface's embedded types are fully defined and +// before using the interface type in any way other than to form other types. +// Complete returns the receiver. +func (t *Interface) Complete() *Interface { + if t.allMethods != nil { + return t + } + + var allMethods []*Func + if t.embeddeds == nil { + if t.methods == nil { + allMethods = make([]*Func, 0, 1) + } else { + allMethods = t.methods + } + } else { + allMethods = append(allMethods, t.methods...) + for _, et := range t.embeddeds { + it := et.Underlying().(*Interface) + it.Complete() + for _, tm := range it.allMethods { + // Make a copy of the method and adjust its receiver type. + newm := *tm + newmtyp := *tm.typ.(*Signature) + newm.typ = &newmtyp + newmtyp.recv = NewVar(newm.pos, newm.pkg, "", t) + allMethods = append(allMethods, &newm) + } + } + sort.Sort(byUniqueMethodName(allMethods)) + } + t.allMethods = allMethods + + return t +} + +// A Map represents a map type. +type Map struct { + key, elem Type +} + +// NewMap returns a new map for the given key and element types. +func NewMap(key, elem Type) *Map { + return &Map{key, elem} +} + +// Key returns the key type of map m. +func (m *Map) Key() Type { return m.key } + +// Elem returns the element type of map m. +func (m *Map) Elem() Type { return m.elem } + +// A Chan represents a channel type. +type Chan struct { + dir ChanDir + elem Type +} + +// A ChanDir value indicates a channel direction. +type ChanDir int + +// The direction of a channel is indicated by one of the following constants. +const ( + SendRecv ChanDir = iota + SendOnly + RecvOnly +) + +// NewChan returns a new channel type for the given direction and element type. +func NewChan(dir ChanDir, elem Type) *Chan { + return &Chan{dir, elem} +} + +// Dir returns the direction of channel c. +func (c *Chan) Dir() ChanDir { return c.dir } + +// Elem returns the element type of channel c. +func (c *Chan) Elem() Type { return c.elem } + +// A Named represents a named type. +type Named struct { + obj *TypeName // corresponding declared object + underlying Type // possibly a *Named during setup; never a *Named once set up completely + methods []*Func // methods declared for this type (not the method set of this type) +} + +// NewNamed returns a new named type for the given type name, underlying type, and associated methods. +// The underlying type must not be a *Named. +func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + if _, ok := underlying.(*Named); ok { + panic("types.NewNamed: underlying type must not be *Named") + } + typ := &Named{obj: obj, underlying: underlying, methods: methods} + if obj.typ == nil { + obj.typ = typ + } + return typ +} + +// TypeName returns the type name for the named type t. +func (t *Named) Obj() *TypeName { return t.obj } + +// NumMethods returns the number of explicit methods whose receiver is named type t. +func (t *Named) NumMethods() int { return len(t.methods) } + +// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). +func (t *Named) Method(i int) *Func { return t.methods[i] } + +// SetUnderlying sets the underlying type and marks t as complete. +// TODO(gri) determine if there's a better solution rather than providing this function +func (t *Named) SetUnderlying(underlying Type) { + if underlying == nil { + panic("types.Named.SetUnderlying: underlying type must not be nil") + } + if _, ok := underlying.(*Named); ok { + panic("types.Named.SetUnderlying: underlying type must not be *Named") + } + t.underlying = underlying +} + +// AddMethod adds method m unless it is already in the method list. +// TODO(gri) find a better solution instead of providing this function +func (t *Named) AddMethod(m *Func) { + if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { + t.methods = append(t.methods, m) + } +} + +// Implementations for Type methods. + +func (t *Basic) Underlying() Type { return t } +func (t *Array) Underlying() Type { return t } +func (t *Slice) Underlying() Type { return t } +func (t *Struct) Underlying() Type { return t } +func (t *Pointer) Underlying() Type { return t } +func (t *Tuple) Underlying() Type { return t } +func (t *Signature) Underlying() Type { return t } +func (t *Interface) Underlying() Type { return t } +func (t *Map) Underlying() Type { return t } +func (t *Chan) Underlying() Type { return t } +func (t *Named) Underlying() Type { return t.underlying } + +func (t *Basic) String() string { return TypeString(t, nil) } +func (t *Array) String() string { return TypeString(t, nil) } +func (t *Slice) String() string { return TypeString(t, nil) } +func (t *Struct) String() string { return TypeString(t, nil) } +func (t *Pointer) String() string { return TypeString(t, nil) } +func (t *Tuple) String() string { return TypeString(t, nil) } +func (t *Signature) String() string { return TypeString(t, nil) } +func (t *Interface) String() string { return TypeString(t, nil) } +func (t *Map) String() string { return TypeString(t, nil) } +func (t *Chan) String() string { return TypeString(t, nil) } +func (t *Named) String() string { return TypeString(t, nil) } diff --git a/libgo/go/go/types/typestring.go b/libgo/go/go/types/typestring.go new file mode 100644 index 00000000000..bd62f4dc229 --- /dev/null +++ b/libgo/go/go/types/typestring.go @@ -0,0 +1,296 @@ +// Copyright 2013 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 printing of types. + +package types + +import ( + "bytes" + "fmt" +) + +// A Qualifier controls how named package-level objects are printed in +// calls to TypeString, ObjectString, and SelectionString. +// +// These three formatting routines call the Qualifier for each +// package-level object O, and if the Qualifier returns a non-empty +// string p, the object is printed in the form p.O. +// If it returns an empty string, only the object name O is printed. +// +// Using a nil Qualifier is equivalent to using (*Package).Path: the +// object is qualified by the import path, e.g., "encoding/json.Marshal". +// +type Qualifier func(*Package) string + +// RelativeTo(pkg) returns a Qualifier that fully qualifies members of +// all packages other than pkg. +func RelativeTo(pkg *Package) Qualifier { + if pkg == nil { + return nil + } + return func(other *Package) string { + if pkg == other { + return "" // same package; unqualified + } + return other.Path() + } +} + +// If gcCompatibilityMode is set, printing of types is modified +// to match the representation of some types in the gc compiler: +// +// - byte and rune lose their alias name and simply stand for +// uint8 and int32 respectively +// - embedded interfaces get flattened (the embedding info is lost, +// and certain recursive interface types cannot be printed anymore) +// +// This makes it easier to compare packages computed with the type- +// checker vs packages imported from gc export data. +// +// Caution: This flag affects all uses of WriteType, globally. +// It is only provided for testing in conjunction with +// gc-generated data. +// +// This flag is exported in the x/tools/go/types package. We don't +// need it at the moment in the std repo and so we don't export it +// anymore. We should eventually try to remove it altogether. +var gcCompatibilityMode bool + +// TypeString returns the string representation of typ. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func TypeString(typ Type, qf Qualifier) string { + var buf bytes.Buffer + WriteType(&buf, typ, qf) + return buf.String() +} + +// WriteType writes the string representation of typ to buf. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func WriteType(buf *bytes.Buffer, typ Type, qf Qualifier) { + writeType(buf, typ, qf, make([]Type, 8)) +} + +func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { + // Theoretically, this is a quadratic lookup algorithm, but in + // practice deeply nested composite types with unnamed component + // types are uncommon. This code is likely more efficient than + // using a map. + for _, t := range visited { + if t == typ { + fmt.Fprintf(buf, "○%T", typ) // cycle to typ + return + } + } + visited = append(visited, typ) + + switch t := typ.(type) { + case nil: + buf.WriteString("<nil>") + + case *Basic: + if t.kind == UnsafePointer { + buf.WriteString("unsafe.") + } + if gcCompatibilityMode { + // forget the alias names + switch t.kind { + case Byte: + t = Typ[Uint8] + case Rune: + t = Typ[Int32] + } + } + buf.WriteString(t.name) + + case *Array: + fmt.Fprintf(buf, "[%d]", t.len) + writeType(buf, t.elem, qf, visited) + + case *Slice: + buf.WriteString("[]") + writeType(buf, t.elem, qf, visited) + + case *Struct: + buf.WriteString("struct{") + for i, f := range t.fields { + if i > 0 { + buf.WriteString("; ") + } + if !f.anonymous { + buf.WriteString(f.name) + buf.WriteByte(' ') + } + writeType(buf, f.typ, qf, visited) + if tag := t.Tag(i); tag != "" { + fmt.Fprintf(buf, " %q", tag) + } + } + buf.WriteByte('}') + + case *Pointer: + buf.WriteByte('*') + writeType(buf, t.base, qf, visited) + + case *Tuple: + writeTuple(buf, t, false, qf, visited) + + case *Signature: + buf.WriteString("func") + writeSignature(buf, t, qf, visited) + + case *Interface: + // We write the source-level methods and embedded types rather + // than the actual method set since resolved method signatures + // may have non-printable cycles if parameters have anonymous + // interface types that (directly or indirectly) embed the + // current interface. For instance, consider the result type + // of m: + // + // type T interface{ + // m() interface{ T } + // } + // + buf.WriteString("interface{") + if gcCompatibilityMode { + // print flattened interface + // (useful to compare against gc-generated interfaces) + for i, m := range t.allMethods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.name) + writeSignature(buf, m.typ.(*Signature), qf, visited) + } + } else { + // print explicit interface methods and embedded types + for i, m := range t.methods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.name) + writeSignature(buf, m.typ.(*Signature), qf, visited) + } + for i, typ := range t.embeddeds { + if i > 0 || len(t.methods) > 0 { + buf.WriteString("; ") + } + writeType(buf, typ, qf, visited) + } + } + buf.WriteByte('}') + + case *Map: + buf.WriteString("map[") + writeType(buf, t.key, qf, visited) + buf.WriteByte(']') + writeType(buf, t.elem, qf, visited) + + case *Chan: + var s string + var parens bool + switch t.dir { + case SendRecv: + s = "chan " + // chan (<-chan T) requires parentheses + if c, _ := t.elem.(*Chan); c != nil && c.dir == RecvOnly { + parens = true + } + case SendOnly: + s = "chan<- " + case RecvOnly: + s = "<-chan " + default: + panic("unreachable") + } + buf.WriteString(s) + if parens { + buf.WriteByte('(') + } + writeType(buf, t.elem, qf, visited) + if parens { + buf.WriteByte(')') + } + + case *Named: + s := "<Named w/o object>" + if obj := t.obj; obj != nil { + if obj.pkg != nil { + writePackage(buf, obj.pkg, qf) + } + // TODO(gri): function-local named types should be displayed + // differently from named types at package level to avoid + // ambiguity. + s = obj.name + } + buf.WriteString(s) + + default: + // For externally defined implementations of Type. + buf.WriteString(t.String()) + } +} + +func writeTuple(buf *bytes.Buffer, tup *Tuple, variadic bool, qf Qualifier, visited []Type) { + buf.WriteByte('(') + if tup != nil { + for i, v := range tup.vars { + if i > 0 { + buf.WriteString(", ") + } + if v.name != "" { + buf.WriteString(v.name) + buf.WriteByte(' ') + } + typ := v.typ + if variadic && i == len(tup.vars)-1 { + if s, ok := typ.(*Slice); ok { + buf.WriteString("...") + typ = s.elem + } else { + // special case: + // append(s, "foo"...) leads to signature func([]byte, string...) + if t, ok := typ.Underlying().(*Basic); !ok || t.kind != String { + panic("internal error: string type expected") + } + writeType(buf, typ, qf, visited) + buf.WriteString("...") + continue + } + } + writeType(buf, typ, qf, visited) + } + } + buf.WriteByte(')') +} + +// WriteSignature writes the representation of the signature sig to buf, +// without a leading "func" keyword. +// The Qualifier controls the printing of +// package-level objects, and may be nil. +func WriteSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier) { + writeSignature(buf, sig, qf, make([]Type, 8)) +} + +func writeSignature(buf *bytes.Buffer, sig *Signature, qf Qualifier, visited []Type) { + writeTuple(buf, sig.params, sig.variadic, qf, visited) + + n := sig.results.Len() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && sig.results.vars[0].name == "" { + // single unnamed result + writeType(buf, sig.results.vars[0].typ, qf, visited) + return + } + + // multiple or named result(s) + writeTuple(buf, sig.results, false, qf, visited) +} diff --git a/libgo/go/go/types/typestring_test.go b/libgo/go/go/types/typestring_test.go new file mode 100644 index 00000000000..913e6c735cc --- /dev/null +++ b/libgo/go/go/types/typestring_test.go @@ -0,0 +1,168 @@ +// 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. + +package types_test + +import ( + "go/ast" + "go/importer" + "go/parser" + "go/token" + "internal/testenv" + "testing" + + . "go/types" +) + +const filename = "<src>" + +func makePkg(t *testing.T, src string) (*Package, error) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors) + if err != nil { + return nil, err + } + // use the package name as package path + conf := Config{Importer: importer.Default()} + return conf.Check(file.Name.Name, fset, []*ast.File{file}, 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} +} + +// types that don't depend on any other type declarations +var independentTestTypes = []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 []complex128 + }`, `struct{string; elems []complex128}`}, + + // 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()}"), + dup(`interface{String() string; m(int) float32}`), + + // maps + dup("map[string]int"), + {"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"}, + + // channels + dup("chan<- chan int"), + dup("chan<- <-chan int"), + dup("<-chan <-chan int"), + dup("chan (<-chan int)"), + dup("chan<- func()"), + dup("<-chan []func() int"), +} + +// types that depend on other type declarations (src in TestTypes) +var dependentTestTypes = []testEntry{ + // interfaces + dup(`interface{io.Reader; io.Writer}`), + dup(`interface{m() int; io.Writer}`), + {`interface{m() interface{T}}`, `interface{m() interface{p.T}}`}, +} + +func TestTypeString(t *testing.T) { + testenv.MustHaveGoBuild(t) + + var tests []testEntry + tests = append(tests, independentTestTypes...) + tests = append(tests, dependentTestTypes...) + + for _, test := range tests { + src := `package p; import "io"; type _ io.Writer; type T ` + test.src + pkg, err := makePkg(t, src) + if err != nil { + t.Errorf("%s: %s", src, err) + continue + } + typ := pkg.Scope().Lookup("T").Type().Underlying() + if got := typ.String(); got != test.str { + t.Errorf("%s: got %s, want %s", test.src, got, test.str) + } + } +} + +func TestQualifiedTypeString(t *testing.T) { + p, _ := pkgFor("p.go", "package p; type T int", nil) + q, _ := pkgFor("q.go", "package q", nil) + + pT := p.Scope().Lookup("T").Type() + for _, test := range []struct { + typ Type + this *Package + want string + }{ + {pT, nil, "p.T"}, + {pT, p, "T"}, + {pT, q, "p.T"}, + {NewPointer(pT), p, "*T"}, + {NewPointer(pT), q, "*p.T"}, + } { + qualifier := func(pkg *Package) string { + if pkg != test.this { + return pkg.Name() + } + return "" + } + if got := TypeString(test.typ, qualifier); got != test.want { + t.Errorf("TypeString(%s, %s) = %s, want %s", + test.this, test.typ, got, test.want) + } + } +} diff --git a/libgo/go/go/types/typexpr.go b/libgo/go/go/types/typexpr.go new file mode 100644 index 00000000000..c744eeaa0c7 --- /dev/null +++ b/libgo/go/go/types/typexpr.go @@ -0,0 +1,712 @@ +// Copyright 2013 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 type-checking of identifiers and type expressions. + +package types + +import ( + "go/ast" + "go/constant" + "go/token" + "sort" + "strconv" +) + +// ident type-checks identifier e and initializes x with the value or type of e. +// If an error occurred, x.mode is set to invalid. +// For the meaning of def and path, see check.typ, below. +// +func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeName) { + x.mode = invalid + x.expr = e + + scope, obj := check.scope.LookupParent(e.Name, check.pos) + if obj == nil { + if e.Name == "_" { + check.errorf(e.Pos(), "cannot use _ as value or type") + } else { + check.errorf(e.Pos(), "undeclared name: %s", e.Name) + } + return + } + check.recordUse(e, obj) + + check.objDecl(obj, def, path) + typ := obj.Type() + assert(typ != nil) + + // The object may be dot-imported: If so, remove its package from + // the map of unused dot imports for the respective file scope. + // (This code is only needed for dot-imports. Without them, + // we only have to mark variables, see *Var case below). + if pkg := obj.Pkg(); pkg != check.pkg && pkg != nil { + delete(check.unusedDotImports[scope], pkg) + } + + switch obj := obj.(type) { + case *PkgName: + check.errorf(e.Pos(), "use of package %s not in selector", obj.name) + return + + case *Const: + check.addDeclDep(obj) + if typ == Typ[Invalid] { + return + } + if obj == universeIota { + if check.iota == nil { + check.errorf(e.Pos(), "cannot use iota outside constant declaration") + return + } + x.val = check.iota + } else { + x.val = obj.val + } + assert(x.val != nil) + x.mode = constant_ + + case *TypeName: + x.mode = typexpr + // check for cycle + // (it's ok to iterate forward because each named type appears at most once in path) + for i, prev := range path { + if prev == obj { + check.errorf(obj.pos, "illegal cycle in declaration of %s", obj.name) + // print cycle + for _, obj := range path[i:] { + check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented + } + check.errorf(obj.Pos(), "\t%s", obj.Name()) + // maintain x.mode == typexpr despite error + typ = Typ[Invalid] + break + } + } + + case *Var: + if obj.pkg == check.pkg { + obj.used = true + } + check.addDeclDep(obj) + if typ == Typ[Invalid] { + return + } + x.mode = variable + + case *Func: + check.addDeclDep(obj) + x.mode = value + + case *Builtin: + x.id = obj.id + x.mode = builtin + + case *Nil: + x.mode = value + + default: + unreachable() + } + + x.typ = typ +} + +// typExpr type-checks the type expression e and returns its type, or Typ[Invalid]. +// If def != nil, e is the type specification for the named type def, declared +// in a type declaration, and def.underlying will be set to the type of e before +// any components of e are type-checked. Path contains the path of named types +// referring to this type. +// +func (check *Checker) typExpr(e ast.Expr, def *Named, path []*TypeName) (T Type) { + if trace { + check.trace(e.Pos(), "%s", e) + check.indent++ + defer func() { + check.indent-- + check.trace(e.Pos(), "=> %s", T) + }() + } + + T = check.typExprInternal(e, def, path) + assert(isTyped(T)) + check.recordTypeAndValue(e, typexpr, T, nil) + + return +} + +func (check *Checker) typ(e ast.Expr) Type { + return check.typExpr(e, nil, nil) +} + +// funcType type-checks a function or method type. +func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) { + scope := NewScope(check.scope, token.NoPos, token.NoPos, "function") + check.recordScope(ftyp, scope) + + recvList, _ := check.collectParams(scope, recvPar, false) + params, variadic := check.collectParams(scope, ftyp.Params, true) + results, _ := check.collectParams(scope, ftyp.Results, false) + + if recvPar != nil { + // recv parameter list present (may be empty) + // spec: "The receiver is specified via an extra parameter section preceding the + // method name. That parameter section must declare a single parameter, the receiver." + var recv *Var + switch len(recvList) { + case 0: + check.error(recvPar.Pos(), "method is missing receiver") + recv = NewParam(0, nil, "", Typ[Invalid]) // ignore recv below + default: + // more than one receiver + check.error(recvList[len(recvList)-1].Pos(), "method must have exactly one receiver") + fallthrough // continue with first receiver + case 1: + recv = recvList[0] + } + // spec: "The receiver type must be of the form T or *T where T is a type name." + // (ignore invalid types - error was reported before) + if t, _ := deref(recv.typ); t != Typ[Invalid] { + var err string + if T, _ := t.(*Named); T != nil { + // spec: "The type denoted by T is called the receiver base type; it must not + // be a pointer or interface type and it must be declared in the same package + // as the method." + if T.obj.pkg != check.pkg { + err = "type not defined in this package" + } else { + // TODO(gri) This is not correct if the underlying type is unknown yet. + switch u := T.underlying.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + err = "unsafe.Pointer" + } + case *Pointer, *Interface: + err = "pointer or interface type" + } + } + } else { + err = "basic or unnamed type" + } + if err != "" { + check.errorf(recv.pos, "invalid receiver %s (%s)", recv.typ, err) + // ok to continue + } + } + sig.recv = recv + } + + sig.scope = scope + sig.params = NewTuple(params...) + sig.results = NewTuple(results...) + sig.variadic = variadic +} + +// typExprInternal drives type checking of types. +// Must only be called by typExpr. +// +func (check *Checker) typExprInternal(e ast.Expr, def *Named, path []*TypeName) Type { + switch e := e.(type) { + case *ast.BadExpr: + // ignore - error reported before + + case *ast.Ident: + var x operand + check.ident(&x, e, def, path) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + + case *ast.SelectorExpr: + var x operand + check.selector(&x, e) + + switch x.mode { + case typexpr: + typ := x.typ + def.setUnderlying(typ) + return typ + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + + case *ast.ParenExpr: + return check.typExpr(e.X, def, path) + + case *ast.ArrayType: + if e.Len != nil { + typ := new(Array) + def.setUnderlying(typ) + typ.len = check.arrayLength(e.Len) + typ.elem = check.typExpr(e.Elt, nil, path) + return typ + + } else { + typ := new(Slice) + def.setUnderlying(typ) + typ.elem = check.typ(e.Elt) + return typ + } + + case *ast.StructType: + typ := new(Struct) + def.setUnderlying(typ) + check.structType(typ, e, path) + return typ + + case *ast.StarExpr: + typ := new(Pointer) + def.setUnderlying(typ) + typ.base = check.typ(e.X) + return typ + + case *ast.FuncType: + typ := new(Signature) + def.setUnderlying(typ) + check.funcType(typ, nil, e) + return typ + + case *ast.InterfaceType: + typ := new(Interface) + def.setUnderlying(typ) + check.interfaceType(typ, e, def, path) + return typ + + case *ast.MapType: + typ := new(Map) + def.setUnderlying(typ) + + typ.key = check.typ(e.Key) + typ.elem = check.typ(e.Value) + + // spec: "The comparison operators == and != must be fully defined + // for operands of the key type; thus the key type must not be a + // function, map, or slice." + // + // Delay this check because it requires fully setup types; + // it is safe to continue in any case (was issue 6667). + check.delay(func() { + if !Comparable(typ.key) { + check.errorf(e.Key.Pos(), "invalid map key type %s", typ.key) + } + }) + + return typ + + case *ast.ChanType: + typ := new(Chan) + def.setUnderlying(typ) + + dir := SendRecv + switch e.Dir { + case ast.SEND | ast.RECV: + // nothing to do + case ast.SEND: + dir = SendOnly + case ast.RECV: + dir = RecvOnly + default: + check.invalidAST(e.Pos(), "unknown channel direction %d", e.Dir) + // ok to continue + } + + typ.dir = dir + typ.elem = check.typ(e.Value) + return typ + + default: + check.errorf(e.Pos(), "%s is not a type", e) + } + + typ := Typ[Invalid] + def.setUnderlying(typ) + return typ +} + +// typeOrNil type-checks the type expression (or nil value) e +// and returns the typ of e, or nil. +// If e is neither a type nor nil, typOrNil returns Typ[Invalid]. +// +func (check *Checker) typOrNil(e ast.Expr) Type { + var x operand + check.rawExpr(&x, e, nil) + switch x.mode { + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + case typexpr: + return x.typ + case value: + if x.isNil() { + return nil + } + fallthrough + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + return Typ[Invalid] +} + +func (check *Checker) arrayLength(e ast.Expr) int64 { + var x operand + check.expr(&x, e) + if x.mode != constant_ { + if x.mode != invalid { + check.errorf(x.pos(), "array length %s must be constant", &x) + } + return 0 + } + if !x.isInteger() { + check.errorf(x.pos(), "array length %s must be integer", &x) + return 0 + } + n, ok := constant.Int64Val(x.val) + if !ok || n < 0 { + check.errorf(x.pos(), "invalid array length %s", &x) + return 0 + } + return n +} + +func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicOk bool) (params []*Var, variadic bool) { + if list == nil { + return + } + + var named, anonymous bool + for i, field := range list.List { + ftype := field.Type + if t, _ := ftype.(*ast.Ellipsis); t != nil { + ftype = t.Elt + if variadicOk && i == len(list.List)-1 { + variadic = true + } else { + check.invalidAST(field.Pos(), "... not permitted") + // ignore ... and continue + } + } + typ := check.typ(ftype) + // The parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag. + if len(field.Names) > 0 { + // named parameter + for _, name := range field.Names { + if name.Name == "" { + check.invalidAST(name.Pos(), "anonymous parameter") + // ok to continue + } + par := NewParam(name.Pos(), check.pkg, name.Name, typ) + check.declare(scope, name, par, scope.pos) + params = append(params, par) + } + named = true + } else { + // anonymous parameter + par := NewParam(ftype.Pos(), check.pkg, "", typ) + check.recordImplicit(field, par) + params = append(params, par) + anonymous = true + } + } + + if named && anonymous { + check.invalidAST(list.Pos(), "list contains both named and anonymous parameters") + // ok to continue + } + + // For a variadic function, change the last parameter's type from T to []T. + if variadic && len(params) > 0 { + last := params[len(params)-1] + last.typ = &Slice{elem: last.typ} + } + + return +} + +func (check *Checker) declareInSet(oset *objset, pos token.Pos, obj Object) bool { + if alt := oset.insert(obj); alt != nil { + check.errorf(pos, "%s redeclared", obj.Name()) + check.reportAltDecl(alt) + return false + } + return true +} + +func (check *Checker) interfaceType(iface *Interface, ityp *ast.InterfaceType, def *Named, path []*TypeName) { + // empty interface: common case + if ityp.Methods == nil { + return + } + + // The parser ensures that field tags are nil and we don't + // care if a constructed AST contains non-nil tags. + + // use named receiver type if available (for better error messages) + var recvTyp Type = iface + if def != nil { + recvTyp = def + } + + // Phase 1: Collect explicitly declared methods, the corresponding + // signature (AST) expressions, and the list of embedded + // type (AST) expressions. Do not resolve signatures or + // embedded types yet to avoid cycles referring to this + // interface. + + var ( + mset objset + signatures []ast.Expr // list of corresponding method signatures + embedded []ast.Expr // list of embedded types + ) + for _, f := range ityp.Methods.List { + if len(f.Names) > 0 { + // The parser ensures that there's only one method + // and we don't care if a constructed AST has more. + name := f.Names[0] + pos := name.Pos() + // spec: "As with all method sets, in an interface type, + // each method must have a unique non-blank name." + if name.Name == "_" { + check.errorf(pos, "invalid method name _") + continue + } + // Don't type-check signature yet - use an + // empty signature now and update it later. + // Since we know the receiver, set it up now + // (required to avoid crash in ptrRecv; see + // e.g. test case for issue 6638). + // TODO(gri) Consider marking methods signatures + // as incomplete, for better error messages. See + // also the T4 and T5 tests in testdata/cycles2.src. + sig := new(Signature) + sig.recv = NewVar(pos, check.pkg, "", recvTyp) + m := NewFunc(pos, check.pkg, name.Name, sig) + if check.declareInSet(&mset, pos, m) { + iface.methods = append(iface.methods, m) + iface.allMethods = append(iface.allMethods, m) + signatures = append(signatures, f.Type) + check.recordDef(name, m) + } + } else { + // embedded type + embedded = append(embedded, f.Type) + } + } + + // Phase 2: Resolve embedded interfaces. Because an interface must not + // embed itself (directly or indirectly), each embedded interface + // can be fully resolved without depending on any method of this + // interface (if there is a cycle or another error, the embedded + // type resolves to an invalid type and is ignored). + // In particular, the list of methods for each embedded interface + // must be complete (it cannot depend on this interface), and so + // those methods can be added to the list of all methods of this + // interface. + + for _, e := range embedded { + pos := e.Pos() + typ := check.typExpr(e, nil, path) + // Determine underlying embedded (possibly incomplete) type + // by following its forward chain. + named, _ := typ.(*Named) + under := underlying(named) + embed, _ := under.(*Interface) + if embed == nil { + if typ != Typ[Invalid] { + check.errorf(pos, "%s is not an interface", typ) + } + continue + } + iface.embeddeds = append(iface.embeddeds, named) + // collect embedded methods + for _, m := range embed.allMethods { + if check.declareInSet(&mset, pos, m) { + iface.allMethods = append(iface.allMethods, m) + } + } + } + + // Phase 3: At this point all methods have been collected for this interface. + // It is now safe to type-check the signatures of all explicitly + // declared methods, even if they refer to this interface via a cycle + // and embed the methods of this interface in a parameter of interface + // type. + + for i, m := range iface.methods { + expr := signatures[i] + typ := check.typ(expr) + sig, _ := typ.(*Signature) + if sig == nil { + if typ != Typ[Invalid] { + check.invalidAST(expr.Pos(), "%s is not a method signature", typ) + } + continue // keep method with empty method signature + } + // update signature, but keep recv that was set up before + old := m.typ.(*Signature) + sig.recv = old.recv + *old = *sig // update signature (don't replace it!) + } + + // TODO(gri) The list of explicit methods is only sorted for now to + // produce the same Interface as NewInterface. We may be able to + // claim source order in the future. Revisit. + sort.Sort(byUniqueMethodName(iface.methods)) + + // TODO(gri) The list of embedded types is only sorted for now to + // produce the same Interface as NewInterface. We may be able to + // claim source order in the future. Revisit. + sort.Sort(byUniqueTypeName(iface.embeddeds)) + + sort.Sort(byUniqueMethodName(iface.allMethods)) +} + +// byUniqueTypeName named type lists can be sorted by their unique type names. +type byUniqueTypeName []*Named + +func (a byUniqueTypeName) Len() int { return len(a) } +func (a byUniqueTypeName) Less(i, j int) bool { return a[i].obj.Id() < a[j].obj.Id() } +func (a byUniqueTypeName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// byUniqueMethodName method lists can be sorted by their unique method names. +type byUniqueMethodName []*Func + +func (a byUniqueMethodName) Len() int { return len(a) } +func (a byUniqueMethodName) Less(i, j int) bool { return a[i].Id() < a[j].Id() } +func (a byUniqueMethodName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +func (check *Checker) tag(t *ast.BasicLit) string { + if t != nil { + if t.Kind == token.STRING { + if val, err := strconv.Unquote(t.Value); err == nil { + return val + } + } + check.invalidAST(t.Pos(), "incorrect tag syntax: %q", t.Value) + } + return "" +} + +func (check *Checker) structType(styp *Struct, e *ast.StructType, path []*TypeName) { + list := e.Fields + if list == nil { + return + } + + // struct fields and tags + var fields []*Var + var tags []string + + // for double-declaration checks + var fset objset + + // current field typ and tag + var typ Type + var tag string + // 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)) + } + if tags != nil { + tags = append(tags, tag) + } + + 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) + check.recordDef(ident, fld) + } + if anonymous != nil { + check.recordUse(ident, anonymous) + } + } + + for _, f := range list.List { + typ = check.typExpr(f.Type, nil, path) + tag = check.tag(f.Tag) + if len(f.Names) > 0 { + // named fields + for _, name := range f.Names { + 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) { + case *Basic: + if t == Typ[Invalid] { + // error was reported before + continue + } + // unsafe.Pointer is treated like a regular pointer + if t.kind == UnsafePointer { + check.errorf(pos, "anonymous field type cannot be unsafe.Pointer") + continue + } + add(f, name, Universe.Lookup(t.name).(*TypeName), pos) + + case *Named: + // spec: "An embedded type must be specified as a type name + // T or as a pointer to a non-interface type name *T, and T + // itself may not be a pointer type." + switch u := t.underlying.(type) { + case *Basic: + // unsafe.Pointer is treated like a regular pointer + if u.kind == UnsafePointer { + check.errorf(pos, "anonymous field type cannot be unsafe.Pointer") + continue + } + case *Pointer: + check.errorf(pos, "anonymous field type cannot be a pointer") + continue + case *Interface: + if isPtr { + check.errorf(pos, "anonymous field type cannot be a pointer to an interface") + continue + } + } + add(f, name, t.obj, pos) + + default: + check.invalidAST(pos, "anonymous field type %s must be named", typ) + } + } + } + + 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/libgo/go/go/types/universe.go b/libgo/go/go/types/universe.go new file mode 100644 index 00000000000..40185c1ad42 --- /dev/null +++ b/libgo/go/go/types/universe.go @@ -0,0 +1,223 @@ +// 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 sets up the universe scope and the unsafe package. + +package types + +import ( + "go/constant" + "go/token" + "strings" +) + +var ( + Universe *Scope + Unsafe *Package + universeIota *Const + universeByte *Basic // uint8 alias, but has name "byte" + universeRune *Basic // int32 alias, but has name "rune" +) + +var Typ = []*Basic{ + Invalid: {Invalid, 0, "invalid type"}, + + Bool: {Bool, IsBoolean, "bool"}, + Int: {Int, IsInteger, "int"}, + Int8: {Int8, IsInteger, "int8"}, + Int16: {Int16, IsInteger, "int16"}, + Int32: {Int32, IsInteger, "int32"}, + Int64: {Int64, IsInteger, "int64"}, + Uint: {Uint, IsInteger | IsUnsigned, "uint"}, + Uint8: {Uint8, IsInteger | IsUnsigned, "uint8"}, + Uint16: {Uint16, IsInteger | IsUnsigned, "uint16"}, + Uint32: {Uint32, IsInteger | IsUnsigned, "uint32"}, + Uint64: {Uint64, IsInteger | IsUnsigned, "uint64"}, + Uintptr: {Uintptr, IsInteger | IsUnsigned, "uintptr"}, + Float32: {Float32, IsFloat, "float32"}, + Float64: {Float64, IsFloat, "float64"}, + Complex64: {Complex64, IsComplex, "complex64"}, + Complex128: {Complex128, IsComplex, "complex128"}, + String: {String, IsString, "string"}, + UnsafePointer: {UnsafePointer, 0, "Pointer"}, + + UntypedBool: {UntypedBool, IsBoolean | IsUntyped, "untyped bool"}, + UntypedInt: {UntypedInt, IsInteger | IsUntyped, "untyped int"}, + UntypedRune: {UntypedRune, IsInteger | IsUntyped, "untyped rune"}, + UntypedFloat: {UntypedFloat, IsFloat | IsUntyped, "untyped float"}, + UntypedComplex: {UntypedComplex, IsComplex | IsUntyped, "untyped complex"}, + UntypedString: {UntypedString, IsString | IsUntyped, "untyped string"}, + UntypedNil: {UntypedNil, IsUntyped, "untyped nil"}, +} + +var aliases = [...]*Basic{ + {Byte, IsInteger | IsUnsigned, "byte"}, + {Rune, IsInteger, "rune"}, +} + +func defPredeclaredTypes() { + for _, t := range Typ { + def(NewTypeName(token.NoPos, nil, t.name, t)) + } + for _, t := range aliases { + def(NewTypeName(token.NoPos, nil, t.name, t)) + } + + // Error has a nil package in its qualified name since it is in no package + res := NewVar(token.NoPos, nil, "", Typ[String]) + sig := &Signature{results: NewTuple(res)} + err := NewFunc(token.NoPos, nil, "Error", sig) + typ := &Named{underlying: NewInterface([]*Func{err}, nil).Complete()} + sig.recv = NewVar(token.NoPos, nil, "", typ) + def(NewTypeName(token.NoPos, nil, "error", typ)) +} + +var predeclaredConsts = [...]struct { + name string + kind BasicKind + val constant.Value +}{ + {"true", UntypedBool, constant.MakeBool(true)}, + {"false", UntypedBool, constant.MakeBool(false)}, + {"iota", UntypedInt, constant.MakeInt64(0)}, +} + +func defPredeclaredConsts() { + for _, c := range predeclaredConsts { + def(NewConst(token.NoPos, nil, c.name, Typ[c.kind], c.val)) + } +} + +func defPredeclaredNil() { + def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}}) +} + +// A builtinId is the id of a builtin function. +type builtinId int + +const ( + // universe scope + _Append builtinId = iota + _Cap + _Close + _Complex + _Copy + _Delete + _Imag + _Len + _Make + _New + _Panic + _Print + _Println + _Real + _Recover + + // package unsafe + _Alignof + _Offsetof + _Sizeof + + // testing support + _Assert + _Trace +) + +var predeclaredFuncs = [...]struct { + name string + nargs int + variadic bool + kind exprKind +}{ + _Append: {"append", 1, true, expression}, + _Cap: {"cap", 1, false, expression}, + _Close: {"close", 1, false, statement}, + _Complex: {"complex", 2, false, expression}, + _Copy: {"copy", 2, false, statement}, + _Delete: {"delete", 2, false, statement}, + _Imag: {"imag", 1, false, expression}, + _Len: {"len", 1, false, expression}, + _Make: {"make", 1, true, expression}, + _New: {"new", 1, false, expression}, + _Panic: {"panic", 1, false, statement}, + _Print: {"print", 0, true, statement}, + _Println: {"println", 0, true, statement}, + _Real: {"real", 1, false, expression}, + _Recover: {"recover", 0, false, statement}, + + _Alignof: {"Alignof", 1, false, expression}, + _Offsetof: {"Offsetof", 1, false, expression}, + _Sizeof: {"Sizeof", 1, false, expression}, + + _Assert: {"assert", 1, false, statement}, + _Trace: {"trace", 0, true, statement}, +} + +func defPredeclaredFuncs() { + for i := range predeclaredFuncs { + id := builtinId(i) + if id == _Assert || id == _Trace { + continue // only define these in testing environment + } + def(newBuiltin(id)) + } +} + +// DefPredeclaredTestFuncs defines the assert and trace built-ins. +// These built-ins are intended for debugging and testing of this +// package only. +func DefPredeclaredTestFuncs() { + if Universe.Lookup("assert") != nil { + return // already defined + } + def(newBuiltin(_Assert)) + def(newBuiltin(_Trace)) +} + +func init() { + Universe = NewScope(nil, token.NoPos, token.NoPos, "universe") + Unsafe = NewPackage("unsafe", "unsafe") + Unsafe.complete = true + + defPredeclaredTypes() + defPredeclaredConsts() + defPredeclaredNil() + defPredeclaredFuncs() + + universeIota = Universe.Lookup("iota").(*Const) + universeByte = Universe.Lookup("byte").(*TypeName).typ.(*Basic) + universeRune = Universe.Lookup("rune").(*TypeName).typ.(*Basic) +} + +// Objects with names containing blanks are internal and not entered into +// a scope. Objects with exported names are inserted in the unsafe package +// scope; other objects are inserted in the universe scope. +// +func def(obj Object) { + name := obj.Name() + if strings.Index(name, " ") >= 0 { + return // nothing to do + } + // fix Obj link for named types + if typ, ok := obj.Type().(*Named); ok { + typ.obj = obj.(*TypeName) + } + // exported identifiers go into package unsafe + scope := Universe + if obj.Exported() { + scope = Unsafe.scope + // set Pkg field + switch obj := obj.(type) { + case *TypeName: + obj.pkg = Unsafe + case *Builtin: + obj.pkg = Unsafe + default: + unreachable() + } + } + if scope.Insert(obj) != nil { + panic("internal error: double declaration") + } +} |