diff options
Diffstat (limited to 'src/pkg/go')
-rw-r--r-- | src/pkg/go/build/Makefile | 11 | ||||
-rw-r--r-- | src/pkg/go/build/build.go | 85 | ||||
-rw-r--r-- | src/pkg/go/build/build_test.go | 1 | ||||
-rw-r--r-- | src/pkg/go/build/deps_test.go | 424 | ||||
-rw-r--r-- | src/pkg/go/build/syslist.go | 4 | ||||
-rw-r--r-- | src/pkg/go/parser/error_test.go | 166 | ||||
-rw-r--r-- | src/pkg/go/parser/parser.go | 152 | ||||
-rw-r--r-- | src/pkg/go/parser/parser_test.go | 83 | ||||
-rw-r--r-- | src/pkg/go/parser/short_test.go | 75 | ||||
-rw-r--r-- | src/pkg/go/parser/testdata/commas.src | 19 | ||||
-rw-r--r-- | src/pkg/go/parser/testdata/issue3106.src | 46 | ||||
-rw-r--r-- | src/pkg/go/printer/example_test.go | 67 | ||||
-rw-r--r-- | src/pkg/go/printer/nodes.go | 58 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/declarations.golden | 15 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/declarations.input | 10 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/statements.golden | 59 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/statements.input | 43 | ||||
-rw-r--r-- | src/pkg/go/scanner/scanner.go | 2 |
18 files changed, 1156 insertions, 164 deletions
diff --git a/src/pkg/go/build/Makefile b/src/pkg/go/build/Makefile deleted file mode 100644 index 3bb3912cb..000000000 --- a/src/pkg/go/build/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright 2009 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. - -syslist.go: ../../../Make.inc Makefile - echo '// Generated automatically by make.' >$@ - echo >>$@ - echo 'package build' >>$@ - echo >>$@ - echo 'const goosList = "$(GOOS_LIST)"' >>$@ - echo 'const goarchList = "$(GOARCH_LIST)"' >>$@ diff --git a/src/pkg/go/build/build.go b/src/pkg/go/build/build.go index eece76105..d113dc135 100644 --- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -34,7 +34,7 @@ type Context struct { CgoEnabled bool // whether cgo can be used BuildTags []string // additional tags to recognize in +build lines UseAllFiles bool // use files regardless of +build lines, file names - Gccgo bool // assume use of gccgo when computing object paths + Compiler string // compiler to assume when computing target paths // By default, Import uses the operating system's file system calls // to read directories and files. To read from other sources, @@ -210,6 +210,7 @@ func (ctxt *Context) SrcDirs() []string { // if set, or else the compiled code's GOARCH, GOOS, and GOROOT. var Default Context = defaultContext() +// This list is also known to ../../../cmd/dist/build.c. var cgoEnabled = map[string]bool{ "darwin/386": true, "darwin/amd64": true, @@ -228,6 +229,7 @@ func defaultContext() Context { c.GOOS = envOr("GOOS", runtime.GOOS) c.GOROOT = runtime.GOROOT() c.GOPATH = envOr("GOPATH", "") + c.Compiler = runtime.Compiler switch os.Getenv("CGO_ENABLED") { case "1": @@ -277,11 +279,12 @@ type Package struct { PkgObj string // installed .a file // Source files - GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) - CgoFiles []string // .go source files that import "C" - CFiles []string // .c source files - HFiles []string // .h source files - SFiles []string // .s source files + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) + CgoFiles []string // .go source files that import "C" + CFiles []string // .c source files + HFiles []string // .h source files + SFiles []string // .s source files + SysoFiles []string // .syso system object files to add to archive // Cgo directives CgoPkgConfig []string // Cgo pkg-config directives @@ -314,42 +317,58 @@ func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { return ctxt.Import(".", dir, mode) } +// NoGoError is the error used by Import to describe a directory +// containing no Go source files. +type NoGoError struct { + Dir string +} + +func (e *NoGoError) Error() string { + return "no Go source files in " + e.Dir +} + // Import returns details about the Go package named by the import path, -// interpreting local import paths relative to the src directory. If the path -// is a local import path naming a package that can be imported using a -// standard import path, the returned package will set p.ImportPath to -// that path. +// interpreting local import paths relative to the srcDir directory. +// If the path is a local import path naming a package that can be imported +// using a standard import path, the returned package will set p.ImportPath +// to that path. // // In the directory containing the package, .go, .c, .h, and .s files are // considered part of the package except for: // // - .go files in package documentation -// - files starting with _ or . +// - files starting with _ or . (likely editor temporary files) // - files with build constraints not satisfied by the context // // If an error occurs, Import returns a non-nil error also returns a non-nil // *Package containing partial information. // -func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, error) { +func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Package, error) { p := &Package{ ImportPath: path, } var pkga string - if ctxt.Gccgo { + var pkgerr error + switch ctxt.Compiler { + case "gccgo": dir, elem := pathpkg.Split(p.ImportPath) pkga = "pkg/gccgo/" + dir + "lib" + elem + ".a" - } else { + case "gc": pkga = "pkg/" + ctxt.GOOS + "_" + ctxt.GOARCH + "/" + p.ImportPath + ".a" + default: + // Save error for end of function. + pkgerr = fmt.Errorf("import %q: unknown compiler %q", path, ctxt.Compiler) } binaryOnly := false if IsLocalImport(path) { - if src == "" { + pkga = "" // local imports have no installed path + if srcDir == "" { return p, fmt.Errorf("import %q: import relative to unknown directory", path) } if !ctxt.isAbsPath(path) { - p.Dir = ctxt.joinPath(src, path) + p.Dir = ctxt.joinPath(srcDir, path) } // Determine canonical import path, if any. if ctxt.GOROOT != "" { @@ -396,7 +415,7 @@ func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, if ctxt.GOROOT != "" { dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", path) isDir := ctxt.isDir(dir) - binaryOnly = !isDir && mode&AllowBinary != 0 && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) + binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(ctxt.GOROOT, pkga)) if isDir || binaryOnly { p.Dir = dir p.Goroot = true @@ -407,7 +426,7 @@ func (ctxt *Context) Import(path string, src string, mode ImportMode) (*Package, for _, root := range ctxt.gopath() { dir := ctxt.joinPath(root, "src", path) isDir := ctxt.isDir(dir) - binaryOnly = !isDir && mode&AllowBinary != 0 && ctxt.isFile(ctxt.joinPath(root, pkga)) + binaryOnly = !isDir && mode&AllowBinary != 0 && pkga != "" && ctxt.isFile(ctxt.joinPath(root, pkga)) if isDir || binaryOnly { p.Dir = dir p.Root = root @@ -426,14 +445,16 @@ Found: } p.PkgRoot = ctxt.joinPath(p.Root, "pkg") p.BinDir = ctxt.joinPath(p.Root, "bin") - p.PkgObj = ctxt.joinPath(p.Root, pkga) + if pkga != "" { + p.PkgObj = ctxt.joinPath(p.Root, pkga) + } } if mode&FindOnly != 0 { - return p, nil + return p, pkgerr } if binaryOnly && (mode&AllowBinary) != 0 { - return p, nil + return p, pkgerr } dirs, err := ctxt.readDir(p.Dir) @@ -467,7 +488,13 @@ Found: ext := name[i:] switch ext { case ".go", ".c", ".s", ".h", ".S": - // tentatively okay + // tentatively okay - read to make sure + case ".syso": + // binary objects to add to package archive + // Likely of the form foo_windows.syso, but + // the name was vetted above with goodOSArchFile. + p.SysoFiles = append(p.SysoFiles, name) + continue default: // skip continue @@ -586,7 +613,7 @@ Found: } } if p.Name == "" { - return p, fmt.Errorf("no Go source files in %s", p.Dir) + return p, &NoGoError{p.Dir} } p.Imports, p.ImportPos = cleanImports(imported) @@ -601,7 +628,7 @@ Found: sort.Strings(p.SFiles) } - return p, nil + return p, pkgerr } func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) { @@ -614,8 +641,8 @@ func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.P } // Import is shorthand for Default.Import. -func Import(path, src string, mode ImportMode) (*Package, error) { - return Default.Import(path, src, mode) +func Import(path, srcDir string, mode ImportMode) (*Package, error) { + return Default.Import(path, srcDir, mode) } // ImportDir is shorthand for Default.ImportDir. @@ -848,7 +875,7 @@ func splitQuoted(s string) (r []string, err error) { // !cgo (if cgo is disabled) // tag (if tag is listed in ctxt.BuildTags) // !tag (if tag is not listed in ctxt.BuildTags) -// a slash-separated list of any of these +// a comma-separated list of any of these // func (ctxt *Context) match(name string) bool { if name == "" { @@ -862,11 +889,11 @@ func (ctxt *Context) match(name string) bool { return false } if strings.HasPrefix(name, "!") { // negation - return !ctxt.match(name[1:]) + return len(name) > 1 && !ctxt.match(name[1:]) } // Tags must be letters, digits, underscores. - // Unlike in Go identifiers, all digits is fine (e.g., "386"). + // Unlike in Go identifiers, all digits are fine (e.g., "386"). for _, c := range name { if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' { return false diff --git a/src/pkg/go/build/build_test.go b/src/pkg/go/build/build_test.go index 06b8b0e94..560ebad5c 100644 --- a/src/pkg/go/build/build_test.go +++ b/src/pkg/go/build/build_test.go @@ -36,6 +36,7 @@ func TestMatch(t *testing.T) { nomatch(runtime.GOOS + "," + runtime.GOARCH + ",!foo") match(runtime.GOOS + "," + runtime.GOARCH + ",!bar") nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar") + nomatch("!") } func TestDotSlashImport(t *testing.T) { diff --git a/src/pkg/go/build/deps_test.go b/src/pkg/go/build/deps_test.go new file mode 100644 index 000000000..4e9f32a03 --- /dev/null +++ b/src/pkg/go/build/deps_test.go @@ -0,0 +1,424 @@ +// 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 exercises the import parser but also checks that +// some low-level packages do not have new dependencies added. + +package build_test + +import ( + "go/build" + "sort" + "testing" +) + +// pkgDeps defines the expected dependencies between packages in +// the Go source tree. It is a statement of policy. +// Changes should not be made to this map without prior discussion. +// +// The map contains two kinds of entries: +// 1) Lower-case keys are standard import paths and list the +// allowed imports in that package. +// 2) Upper-case keys define aliases for package sets, which can then +// be used as dependencies by other rules. +// +// DO NOT CHANGE THIS DATA TO FIX BUILDS. +// +var pkgDeps = map[string][]string{ + // L0 is the lowest level, core, nearly unavoidable packages. + "errors": {}, + "io": {"errors", "sync"}, + "runtime": {"unsafe"}, + "sync": {"sync/atomic"}, + "sync/atomic": {"unsafe"}, + "unsafe": {}, + + "L0": { + "errors", + "io", + "runtime", + "sync", + "sync/atomic", + "unsafe", + }, + + // L1 adds simple functions and strings processing, + // but not Unicode tables. + "math": {"unsafe"}, + "math/cmplx": {"math"}, + "math/rand": {"L0", "math"}, + "sort": {"math"}, + "strconv": {"L0", "unicode/utf8", "math"}, + "unicode/utf16": {}, + "unicode/utf8": {}, + + "L1": { + "L0", + "math", + "math/cmplx", + "math/rand", + "sort", + "strconv", + "unicode/utf16", + "unicode/utf8", + }, + + // L2 adds Unicode and strings processing. + "bufio": {"L0", "unicode/utf8", "bytes"}, + "bytes": {"L0", "unicode", "unicode/utf8"}, + "path": {"L0", "unicode/utf8", "strings"}, + "strings": {"L0", "unicode", "unicode/utf8"}, + "unicode": {}, + + "L2": { + "L1", + "bufio", + "bytes", + "path", + "strings", + "unicode", + }, + + // L3 adds reflection and some basic utility packages + // and interface definitions, but nothing that makes + // system calls. + "crypto": {"L2", "hash"}, // interfaces + "crypto/cipher": {"L2"}, // interfaces + "encoding/base32": {"L2"}, + "encoding/base64": {"L2"}, + "encoding/binary": {"L2", "reflect"}, + "hash": {"L2"}, // interfaces + "hash/adler32": {"L2", "hash"}, + "hash/crc32": {"L2", "hash"}, + "hash/crc64": {"L2", "hash"}, + "hash/fnv": {"L2", "hash"}, + "image": {"L2", "image/color"}, // interfaces + "image/color": {"L2"}, // interfaces + "reflect": {"L2"}, + + "L3": { + "L2", + "crypto", + "crypto/cipher", + "encoding/base32", + "encoding/base64", + "encoding/binary", + "hash", + "hash/adler32", + "hash/crc32", + "hash/crc64", + "hash/fnv", + "image", + "image/color", + "reflect", + }, + + // End of linear dependency definitions. + + // Operating system access. + "syscall": {"L0", "unicode/utf16"}, + "time": {"L0", "syscall"}, + "os": {"L1", "os", "syscall", "time"}, + "path/filepath": {"L2", "os", "syscall"}, + "io/ioutil": {"L2", "os", "path/filepath", "time"}, + "os/exec": {"L2", "os", "syscall"}, + "os/signal": {"L2", "os", "syscall"}, + + // OS enables basic operating system functionality, + // but not direct use of package syscall, nor os/signal. + "OS": { + "io/ioutil", + "os", + "os/exec", + "path/filepath", + "time", + }, + + // Formatted I/O: few dependencies (L1) but we must add reflect. + "fmt": {"L1", "os", "reflect"}, + "log": {"L1", "os", "fmt", "time"}, + + // Packages used by testing must be low-level (L2+fmt). + "regexp": {"L2", "regexp/syntax"}, + "regexp/syntax": {"L2"}, + "runtime/debug": {"L2", "fmt", "io/ioutil", "os"}, + "runtime/pprof": {"L2", "fmt", "text/tabwriter"}, + "text/tabwriter": {"L2"}, + + "testing": {"L2", "flag", "fmt", "os", "runtime/pprof", "time"}, + "testing/iotest": {"L2", "log"}, + "testing/quick": {"L2", "flag", "fmt", "reflect"}, + + // 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. + "L4": { + "L3", + "fmt", + "log", + "time", + }, + + // Go parser. + "go/ast": {"L4", "OS", "go/scanner", "go/token"}, + "go/doc": {"L4", "go/ast", "go/token", "regexp", "text/template"}, + "go/parser": {"L4", "OS", "go/ast", "go/scanner", "go/token"}, + "go/printer": {"L4", "OS", "go/ast", "go/scanner", "go/token", "text/tabwriter"}, + "go/scanner": {"L4", "OS", "go/token"}, + "go/token": {"L4"}, + + "GOPARSER": { + "go/ast", + "go/doc", + "go/parser", + "go/printer", + "go/scanner", + "go/token", + }, + + // One of a kind. + "archive/tar": {"L4", "OS"}, + "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", "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/ascii85": {"L4"}, + "encoding/asn1": {"L4", "math/big"}, + "encoding/csv": {"L4"}, + "encoding/gob": {"L4", "OS"}, + "encoding/hex": {"L4"}, + "encoding/json": {"L4"}, + "encoding/pem": {"L4"}, + "encoding/xml": {"L4"}, + "flag": {"L4", "OS"}, + "go/build": {"L4", "OS", "GOPARSER"}, + "html": {"L4"}, + "image/draw": {"L4"}, + "image/gif": {"L4", "compress/lzw"}, + "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"}, + + "html/template": { + "L4", "OS", "encoding/json", "html", "text/template", + "text/template/parse", + }, + "text/template": { + "L4", "OS", "net/url", "text/template/parse", + }, + + // Cgo. + "runtime/cgo": {"L0", "C"}, + "CGO": {"C", "runtime/cgo"}, + + // Fake entry to satisfy the pseudo-import "C" + // that shows up in programs that use cgo. + "C": {}, + + "os/user": {"L4", "CGO", "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 enables use of basic network-related packages. + "NET": { + "net", + "mime", + "net/textproto", + "net/url", + }, + + // Uses of networking. + "log/syslog": {"L4", "OS", "net"}, + "net/mail": {"L4", "NET", "OS"}, + "net/textproto": {"L4", "OS", "net"}, + + // Core crypto. + "crypto/aes": {"L3"}, + "crypto/des": {"L3"}, + "crypto/hmac": {"L3"}, + "crypto/md5": {"L3"}, + "crypto/rc4": {"L3"}, + "crypto/sha1": {"L3"}, + "crypto/sha256": {"L3"}, + "crypto/sha512": {"L3"}, + "crypto/subtle": {"L3"}, + + "CRYPTO": { + "crypto/aes", + "crypto/des", + "crypto/hmac", + "crypto/md5", + "crypto/rc4", + "crypto/sha1", + "crypto/sha256", + "crypto/sha512", + "crypto/subtle", + }, + + // 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"}, + + // Mathematical crypto: dependencies on fmt (L4) and math/big. + // We could avoid some of the fmt, but math/big imports fmt anyway. + "crypto/dsa": {"L4", "CRYPTO", "math/big"}, + "crypto/ecdsa": {"L4", "CRYPTO", "crypto/elliptic", "math/big"}, + "crypto/elliptic": {"L4", "CRYPTO", "math/big"}, + "crypto/rsa": {"L4", "CRYPTO", "crypto/rand", "math/big"}, + + "CRYPTO-MATH": { + "CRYPTO", + "crypto/dsa", + "crypto/ecdsa", + "crypto/elliptic", + "crypto/rand", + "crypto/rsa", + "encoding/asn1", + "math/big", + }, + + // SSL/TLS. + "crypto/tls": { + "L4", "CRYPTO-MATH", "CGO", "OS", + "crypto/x509", "encoding/pem", "net", "syscall", + }, + "crypto/x509": {"L4", "CRYPTO-MATH", "OS", "CGO", "crypto/x509/pkix", "encoding/pem", "syscall"}, + "crypto/x509/pkix": {"L4", "CRYPTO-MATH"}, + + // Simple net+crypto-aware packages. + "mime/multipart": {"L4", "OS", "mime", "crypto/rand", "net/textproto"}, + "net/smtp": {"L4", "CRYPTO", "NET", "crypto/tls"}, + + // HTTP, kingpin of dependencies. + "net/http": { + "L4", "NET", "OS", + "compress/gzip", "crypto/tls", "mime/multipart", "runtime/debug", + }, + + // 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/pprof": {"L4", "OS", "html/template", "net/http", "runtime/pprof"}, + "net/rpc": {"L4", "NET", "encoding/gob", "net/http", "text/template"}, + "net/rpc/jsonrpc": {"L4", "NET", "encoding/json", "net/rpc"}, +} + +// isMacro reports whether p is a package dependency macro +// (uppercase name). +func isMacro(p string) bool { + return 'A' <= p[0] && p[0] <= 'Z' +} + +func allowed(pkg string) map[string]bool { + m := map[string]bool{} + var allow func(string) + allow = func(p string) { + if m[p] { + return + } + m[p] = true // set even for macros, to avoid loop on cycle + + // Upper-case names are macro-expanded. + if isMacro(p) { + for _, pp := range pkgDeps[p] { + allow(pp) + } + } + } + for _, pp := range pkgDeps[pkg] { + allow(pp) + } + return m +} + +var bools = []bool{false, true} +var geese = []string{"darwin", "freebsd", "linux", "netbsd", "openbsd", "plan9", "windows"} +var goarches = []string{"386", "amd64", "arm"} + +type osPkg struct { + goos, pkg string +} + +// allowedErrors are the operating systems and packages known to contain errors +// (currently just "no Go source files") +var allowedErrors = map[osPkg]bool{ + osPkg{"windows", "log/syslog"}: true, + osPkg{"plan9", "log/syslog"}: true, +} + +func TestDependencies(t *testing.T) { + var all []string + + for k := range pkgDeps { + all = append(all, k) + } + sort.Strings(all) + + ctxt := build.Default + test := func(mustImport bool) { + for _, pkg := range all { + if isMacro(pkg) { + continue + } + p, err := ctxt.Import(pkg, "", 0) + if err != nil { + if allowedErrors[osPkg{ctxt.GOOS, pkg}] { + 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) + continue + } + ok := allowed(pkg) + var bad []string + for _, imp := range p.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) + } + } + } + test(true) + + if testing.Short() { + t.Logf("skipping other systems") + return + } + + for _, ctxt.GOOS = range geese { + for _, ctxt.GOARCH = range goarches { + for _, ctxt.CgoEnabled = range bools { + test(false) + } + } + } +} diff --git a/src/pkg/go/build/syslist.go b/src/pkg/go/build/syslist.go index 8a2db8fa3..ea21f3c74 100644 --- a/src/pkg/go/build/syslist.go +++ b/src/pkg/go/build/syslist.go @@ -1,4 +1,6 @@ -// Generated automatically by make. +// 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 build diff --git a/src/pkg/go/parser/error_test.go b/src/pkg/go/parser/error_test.go new file mode 100644 index 000000000..377c8b80c --- /dev/null +++ b/src/pkg/go/parser/error_test.go @@ -0,0 +1,166 @@ +// 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 a parser test harness. The files in the testdata +// directory are parsed and the errors reported are compared against the +// error messages expected in the test files. The test files must end in +// .src rather than .go so that they are not disturbed by gofmt runs. +// +// 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. +// +// For instance, the following test file indicates that a "not declared" +// error should be reported for the undeclared variable x: +// +// package p +// func f() { +// _ = x /* ERROR "not declared" */ + 1 +// } + +package parser + +import ( + "go/scanner" + "go/token" + "io/ioutil" + "path/filepath" + "regexp" + "strings" + "testing" +) + +const testdata = "testdata" + +// getFile assumes that each filename occurs at most once +func getFile(filename string) (file *token.File) { + fset.Iterate(func(f *token.File) bool { + if f.Name() == filename { + if file != nil { + panic(filename + " used multiple times") + } + file = f + } + return true + }) + return file +} + +func getPos(filename string, offset int) token.Pos { + if f := getFile(filename); f != nil { + return f.Pos(offset) + } + return token.NoPos +} + +// ERROR comments must be of the form /* ERROR "rx" */ and rx is +// a regular expression that matches the expected error message. +// +var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`) + +// expectedErrors collects the regular expressions of ERROR comments found +// in files and returns them as a map of error positions to error messages. +// +func expectedErrors(t *testing.T, 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) + var prev token.Pos // position of last non-comment, non-semicolon token + + for { + pos, tok, lit := s.Scan() + switch tok { + case token.EOF: + return errors + case token.COMMENT: + s := errRx.FindStringSubmatch(lit) + if len(s) == 2 { + errors[prev] = string(s[1]) + } + default: + prev = pos + } + } + + panic("unreachable") +} + +// 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) { + 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) + if msg, found := expected[pos]; found { + // we expect a message at pos; check if it matches + rx, err := regexp.Compile(msg) + if err != nil { + t.Errorf("%s: %v", error.Pos, err) + continue + } + if match := rx.MatchString(error.Msg); !match { + t.Errorf("%s: %q does not match %q", error.Pos, error.Msg, msg) + continue + } + // we have a match - eliminate this error + delete(expected, pos) + } else { + // To keep in mind when analyzing failed test output: + // If the same error position occurs multiple times in errors, + // this message will be triggered (because the first error at + // the position removes this position from the expected errors). + t.Errorf("%s: unexpected error: %s", error.Pos, error.Msg) + } + } + + // there should be no expected errors left + if len(expected) > 0 { + t.Errorf("%d errors not reported:", len(expected)) + for pos, msg := range expected { + t.Errorf("%s: %s\n", fset.Position(pos), msg) + } + } +} + +func checkErrors(t *testing.T, filename string, input interface{}) { + src, err := readSource(filename, input) + if err != nil { + t.Error(err) + return + } + + _, err = ParseFile(fset, filename, src, DeclarationErrors) + found, ok := err.(scanner.ErrorList) + if err != nil && !ok { + t.Error(err) + return + } + + // 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) + + // verify errors returned by the parser + compareErrors(t, expected, found) +} + +func TestErrors(t *testing.T) { + list, err := ioutil.ReadDir(testdata) + if err != nil { + t.Fatal(err) + } + for _, fi := range list { + name := fi.Name() + if !fi.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".src") { + checkErrors(t, filepath.Join(testdata, name), nil) + } + } +} diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go index a122baf08..e362e13a7 100644 --- a/src/pkg/go/parser/parser.go +++ b/src/pkg/go/parser/parser.go @@ -40,6 +40,13 @@ type parser struct { tok token.Token // one token look-ahead lit string // token literal + // Error recovery + // (used to limit the number of calls to syncXXX functions + // w/o making scanning progress - avoids potential endless + // loops across multiple parser functions during error recovery) + syncPos token.Pos // last synchronization position + syncCnt int // number of calls to syncXXX without progress + // Non-syntactic parser control exprLev int // < 0: in control clause, >= 0: in expression @@ -362,18 +369,36 @@ func (p *parser) expect(tok token.Token) token.Pos { // expectClosing is like expect but provides a better error message // for the common case of a missing comma before a newline. // -func (p *parser) expectClosing(tok token.Token, construct string) token.Pos { +func (p *parser) expectClosing(tok token.Token, context string) token.Pos { if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" { - p.error(p.pos, "missing ',' before newline in "+construct) + p.error(p.pos, "missing ',' before newline in "+context) p.next() } return p.expect(tok) } func (p *parser) expectSemi() { + // semicolon is optional before a closing ')' or '}' if p.tok != token.RPAREN && p.tok != token.RBRACE { - p.expect(token.SEMICOLON) + if p.tok == token.SEMICOLON { + p.next() + } else { + p.errorExpected(p.pos, "';'") + syncStmt(p) + } + } +} + +func (p *parser) atComma(context string) 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 + } + return false } func assert(cond bool, msg string) { @@ -382,6 +407,68 @@ func assert(cond bool, msg string) { } } +// syncStmt advances to the next statement. +// Used for synchronization after an error. +// +func syncStmt(p *parser) { + for { + switch p.tok { + case token.BREAK, token.CONST, token.CONTINUE, token.DEFER, + token.FALLTHROUGH, token.FOR, token.GO, token.GOTO, + token.IF, token.RETURN, token.SELECT, token.SWITCH, + token.TYPE, token.VAR: + // Return only if parser made some progress since last + // sync or if it has not reached 10 sync calls without + // progress. Otherwise consume at least one token to + // avoid an endless parser loop (it is possible that + // both parseOperand and parseStmt call syncStmt and + // correctly do not advance, thus the need for the + // invocation limit p.syncCnt). + if p.pos == p.syncPos && p.syncCnt < 10 { + p.syncCnt++ + return + } + if p.pos > p.syncPos { + p.syncPos = p.pos + p.syncCnt = 0 + return + } + // Reaching here indicates a parser bug, likely an + // incorrect token list in this function, but it only + // leads to skipping of possibly correct code if a + // previous error is present, and thus is preferred + // over a non-terminating parse. + case token.EOF: + return + } + p.next() + } +} + +// syncDecl advances to the next declaration. +// Used for synchronization after an error. +// +func syncDecl(p *parser) { + for { + switch p.tok { + case token.CONST, token.TYPE, token.VAR: + // see comments in syncStmt + if p.pos == p.syncPos && p.syncCnt < 10 { + p.syncCnt++ + return + } + if p.pos > p.syncPos { + p.syncPos = p.pos + p.syncCnt = 0 + return + } + case token.EOF: + return + } + p.next() + } +} + // ---------------------------------------------------------------------------- // Identifiers @@ -522,9 +609,11 @@ func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident { for i, x := range list { ident, isIdent := x.(*ast.Ident) if !isIdent { - pos := x.Pos() - p.errorExpected(pos, "identifier") - ident = &ast.Ident{NamePos: pos, Name: "_"} + if _, isBad := x.(*ast.BadExpr); !isBad { + // only report error if it's a new one + p.errorExpected(x.Pos(), "identifier") + } + ident = &ast.Ident{NamePos: x.Pos(), Name: "_"} } idents[i] = ident } @@ -688,7 +777,7 @@ func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params [ // Go spec: The scope of an identifier denoting a function // parameter or result variable is the function body. p.declare(field, nil, scope, ast.Var, idents...) - if p.tok != token.COMMA { + if !p.atComma("parameter list") { break } p.next() @@ -991,19 +1080,19 @@ func (p *parser) parseOperand(lhs bool) ast.Expr { case token.FUNC: return p.parseFuncTypeOrLit() + } - default: - if typ := p.tryIdentOrType(true); typ != nil { - // could be type for composite literal or conversion - _, isIdent := typ.(*ast.Ident) - assert(!isIdent, "type cannot be identifier") - return typ - } + if typ := p.tryIdentOrType(true); typ != nil { + // could be type for composite literal or conversion + _, isIdent := typ.(*ast.Ident) + assert(!isIdent, "type cannot be identifier") + return typ } + // we have an error pos := p.pos p.errorExpected(pos, "operand") - p.next() // make progress + syncStmt(p) return &ast.BadExpr{From: pos, To: p.pos} } @@ -1078,7 +1167,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { ellipsis = p.pos p.next() } - if p.tok != token.COMMA { + if !p.atComma("argument list") { break } p.next() @@ -1118,7 +1207,7 @@ func (p *parser) parseElementList() (list []ast.Expr) { for p.tok != token.RBRACE && p.tok != token.EOF { list = append(list, p.parseElement(true)) - if p.tok != token.COMMA { + if !p.atComma("composite literal") { break } p.next() @@ -1262,8 +1351,8 @@ L: x = p.parseTypeAssertion(p.checkExpr(x)) default: pos := p.pos - p.next() // make progress p.errorExpected(pos, "selector or type assertion") + p.next() // make progress x = &ast.BadExpr{From: pos, To: p.pos} } case token.LBRACK: @@ -1471,7 +1560,10 @@ func (p *parser) parseCallExpr() *ast.CallExpr { if call, isCall := x.(*ast.CallExpr); isCall { return call } - p.errorExpected(x.Pos(), "function/method call") + if _, isBad := x.(*ast.BadExpr); !isBad { + // only report error if it's a new one + p.errorExpected(x.Pos(), "function/method call") + } return nil } @@ -1862,7 +1954,7 @@ func (p *parser) parseStmt() (s ast.Stmt) { switch p.tok { case token.CONST, token.TYPE, token.VAR: - s = &ast.DeclStmt{Decl: p.parseDecl()} + s = &ast.DeclStmt{Decl: p.parseDecl(syncStmt)} case // tokens that may start an expression token.IDENT, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operands @@ -1904,7 +1996,7 @@ func (p *parser) parseStmt() (s ast.Stmt) { // no statement found pos := p.pos p.errorExpected(pos, "statement") - p.next() // make progress + syncStmt(p) s = &ast.BadStmt{From: pos, To: p.pos} } @@ -2095,8 +2187,13 @@ func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList { recv := par.List[0] base := deref(recv.Type) if _, isIdent := base.(*ast.Ident); !isIdent { - p.errorExpected(base.Pos(), "(unqualified) identifier") - par.List = []*ast.Field{{Type: &ast.BadExpr{From: recv.Pos(), To: recv.End()}}} + if _, isBad := base.(*ast.BadExpr); !isBad { + // only report error if it's a new one + p.errorExpected(base.Pos(), "(unqualified) identifier") + } + par.List = []*ast.Field{ + {Type: &ast.BadExpr{From: recv.Pos(), To: recv.End()}}, + } } return par @@ -2152,7 +2249,7 @@ func (p *parser) parseFuncDecl() *ast.FuncDecl { return decl } -func (p *parser) parseDecl() ast.Decl { +func (p *parser) parseDecl(sync func(*parser)) ast.Decl { if p.trace { defer un(trace(p, "Declaration")) } @@ -2174,9 +2271,8 @@ func (p *parser) parseDecl() ast.Decl { default: pos := p.pos p.errorExpected(pos, "declaration") - p.next() // make progress - decl := &ast.BadDecl{From: pos, To: p.pos} - return decl + sync(p) + return &ast.BadDecl{From: pos, To: p.pos} } return p.parseGenDecl(p.tok, f) @@ -2215,7 +2311,7 @@ func (p *parser) parseFile() *ast.File { if p.mode&ImportsOnly == 0 { // rest of package body for p.tok != token.EOF { - decls = append(decls, p.parseDecl()) + decls = append(decls, p.parseDecl(syncDecl)) } } } diff --git a/src/pkg/go/parser/parser_test.go b/src/pkg/go/parser/parser_test.go index 93ca3d6aa..5e45acd00 100644 --- a/src/pkg/go/parser/parser_test.go +++ b/src/pkg/go/parser/parser_test.go @@ -14,87 +14,14 @@ import ( var fset = token.NewFileSet() -var illegalInputs = []interface{}{ - nil, - 3.14, - []byte(nil), - "foo!", - `package p; func f() { if /* should have condition */ {} };`, - `package p; func f() { if ; /* should have condition */ {} };`, - `package p; func f() { if f(); /* should have condition */ {} };`, - `package p; const c; /* should have constant value */`, - `package p; func f() { if _ = range x; true {} };`, - `package p; func f() { switch _ = range x; true {} };`, - `package p; func f() { for _ = range x ; ; {} };`, - `package p; func f() { for ; ; _ = range x {} };`, - `package p; func f() { for ; _ = range x ; {} };`, - `package p; func f() { switch t = t.(type) {} };`, - `package p; func f() { switch t, t = t.(type) {} };`, - `package p; func f() { switch t = t.(type), t {} };`, - `package p; var a = [1]int; /* illegal expression */`, - `package p; var a = [...]int; /* illegal expression */`, - `package p; var a = struct{} /* illegal expression */`, - `package p; var a = func(); /* illegal expression */`, - `package p; var a = interface{} /* illegal expression */`, - `package p; var a = []int /* illegal expression */`, - `package p; var a = map[int]int /* illegal expression */`, - `package p; var a = chan int; /* illegal expression */`, - `package p; var a = []int{[]int}; /* illegal expression */`, - `package p; var a = ([]int); /* illegal expression */`, - `package p; var a = a[[]int:[]int]; /* illegal expression */`, - `package p; var a = <- chan int; /* illegal expression */`, - `package p; func f() { select { case _ <- chan int: } };`, -} - -func TestParseIllegalInputs(t *testing.T) { - for _, src := range illegalInputs { - _, err := ParseFile(fset, "", src, 0) - if err == nil { - t.Errorf("ParseFile(%v) should have failed", src) - } - } -} - -var validPrograms = []string{ - "package p\n", - `package p;`, - `package p; import "fmt"; func f() { fmt.Println("Hello, World!") };`, - `package p; func f() { if f(T{}) {} };`, - `package p; func f() { _ = (<-chan int)(x) };`, - `package p; func f() { _ = (<-chan <-chan int)(x) };`, - `package p; func f(func() func() func());`, - `package p; func f(...T);`, - `package p; func f(float, ...int);`, - `package p; func f(x int, a ...int) { f(0, a...); f(1, a...,) };`, - `package p; func f(int,) {};`, - `package p; func f(...int,) {};`, - `package p; func f(x ...int,) {};`, - `package p; type T []int; var a []bool; func f() { if a[T{42}[0]] {} };`, - `package p; type T []int; func g(int) bool { return true }; func f() { if g(T{42}[0]) {} };`, - `package p; type T []int; func f() { for _ = range []int{T{42}[0]} {} };`, - `package p; var a = T{{1, 2}, {3, 4}}`, - `package p; func f() { select { case <- c: case c <- d: case c <- <- d: case <-c <- d: } };`, - `package p; func f() { select { case x := (<-c): } };`, - `package p; func f() { if ; true {} };`, - `package p; func f() { switch ; {} };`, - `package p; func f() { for _ = range "foo" + "bar" {} };`, -} - -func TestParseValidPrograms(t *testing.T) { - for _, src := range validPrograms { - _, err := ParseFile(fset, "", src, SpuriousErrors) - if err != nil { - t.Errorf("ParseFile(%q): %v", src, err) - } - } -} - var validFiles = []string{ "parser.go", "parser_test.go", + "error_test.go", + "short_test.go", } -func TestParse3(t *testing.T) { +func TestParse(t *testing.T) { for _, filename := range validFiles { _, err := ParseFile(fset, filename, nil, DeclarationErrors) if err != nil { @@ -116,7 +43,7 @@ func nameFilter(filename string) bool { func dirFilter(f os.FileInfo) bool { return nameFilter(f.Name()) } -func TestParse4(t *testing.T) { +func TestParseDir(t *testing.T) { path := "." pkgs, err := ParseDir(fset, path, dirFilter, 0) if err != nil { @@ -158,7 +85,7 @@ func TestParseExpr(t *testing.T) { } // it must not crash - for _, src := range validPrograms { + for _, src := range valids { ParseExpr(src) } } diff --git a/src/pkg/go/parser/short_test.go b/src/pkg/go/parser/short_test.go new file mode 100644 index 000000000..238492bf3 --- /dev/null +++ b/src/pkg/go/parser/short_test.go @@ -0,0 +1,75 @@ +// Copyright 2009 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 test cases for short valid and invalid programs. + +package parser + +import "testing" + +var valids = []string{ + "package p\n", + `package p;`, + `package p; import "fmt"; func f() { fmt.Println("Hello, World!") };`, + `package p; func f() { if f(T{}) {} };`, + `package p; func f() { _ = (<-chan int)(x) };`, + `package p; func f() { _ = (<-chan <-chan int)(x) };`, + `package p; func f(func() func() func());`, + `package p; func f(...T);`, + `package p; func f(float, ...int);`, + `package p; func f(x int, a ...int) { f(0, a...); f(1, a...,) };`, + `package p; func f(int,) {};`, + `package p; func f(...int,) {};`, + `package p; func f(x ...int,) {};`, + `package p; type T []int; var a []bool; func f() { if a[T{42}[0]] {} };`, + `package p; type T []int; func g(int) bool { return true }; func f() { if g(T{42}[0]) {} };`, + `package p; type T []int; func f() { for _ = range []int{T{42}[0]} {} };`, + `package p; var a = T{{1, 2}, {3, 4}}`, + `package p; func f() { select { case <- c: case c <- d: case c <- <- d: case <-c <- d: } };`, + `package p; func f() { select { case x := (<-c): } };`, + `package p; func f() { if ; true {} };`, + `package p; func f() { switch ; {} };`, + `package p; func f() { for _ = range "foo" + "bar" {} };`, +} + +func TestValid(t *testing.T) { + for _, src := range valids { + checkErrors(t, src, src) + } +} + +var invalids = []string{ + `foo /* ERROR "expected 'package'" */ !`, + `package p; func f() { if { /* ERROR "expected operand" */ } };`, + `package p; func f() { if ; { /* ERROR "expected operand" */ } };`, + `package p; func f() { if f(); { /* ERROR "expected operand" */ } };`, + `package p; const c; /* ERROR "expected '='" */`, + `package p; func f() { if _ /* ERROR "expected condition" */ = range x; true {} };`, + `package p; func f() { switch _ /* ERROR "expected condition" */ = range x; true {} };`, + `package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`, + `package p; func f() { for ; ; _ = range /* ERROR "expected operand" */ x {} };`, + `package p; func f() { for ; _ /* ERROR "expected condition" */ = range x ; {} };`, + `package p; func f() { switch t /* ERROR "expected condition" */ = t.(type) {} };`, + `package p; func f() { switch t /* ERROR "expected condition" */ , t = t.(type) {} };`, + `package p; func f() { switch t /* ERROR "expected condition" */ = t.(type), t {} };`, + `package p; var a = [ /* ERROR "expected expression" */ 1]int;`, + `package p; var a = [ /* ERROR "expected expression" */ ...]int;`, + `package p; var a = struct /* ERROR "expected expression" */ {}`, + `package p; var a = func /* ERROR "expected expression" */ ();`, + `package p; var a = interface /* ERROR "expected expression" */ {}`, + `package p; var a = [ /* ERROR "expected expression" */ ]int`, + `package p; var a = map /* ERROR "expected expression" */ [int]int`, + `package p; var a = chan /* ERROR "expected expression" */ int;`, + `package p; var a = []int{[ /* ERROR "expected expression" */ ]int};`, + `package p; var a = ( /* ERROR "expected expression" */ []int);`, + `package p; var a = a[[ /* ERROR "expected expression" */ ]int:[]int];`, + `package p; var a = <- /* ERROR "expected expression" */ chan int;`, + `package p; func f() { select { case _ <- chan /* ERROR "expected expression" */ int: } };`, +} + +func TestInvalid(t *testing.T) { + for _, src := range invalids { + checkErrors(t, src, src) + } +} diff --git a/src/pkg/go/parser/testdata/commas.src b/src/pkg/go/parser/testdata/commas.src new file mode 100644 index 000000000..af6e70645 --- /dev/null +++ b/src/pkg/go/parser/testdata/commas.src @@ -0,0 +1,19 @@ +// 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. + +// Test case for error messages/parser synchronization +// after missing commas. + +package p + +var _ = []int{ + 0 /* ERROR "missing ','" */ +} + +var _ = []int{ + 0, + 1, + 2, + 3 /* ERROR "missing ','" */ +} diff --git a/src/pkg/go/parser/testdata/issue3106.src b/src/pkg/go/parser/testdata/issue3106.src new file mode 100644 index 000000000..82796c8ce --- /dev/null +++ b/src/pkg/go/parser/testdata/issue3106.src @@ -0,0 +1,46 @@ +// 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. + +// Test case for issue 3106: Better synchronization of +// parser after certain syntax errors. + +package main + +func f() { + var m Mutex + c := MakeCond(&m) + percent := 0 + const step = 10 + for i := 0; i < 5; i++ { + go func() { + for { + // Emulates some useful work. + time.Sleep(1e8) + m.Lock() + defer + if /* ERROR "expected operand, found 'if'" */ percent == 100 { + m.Unlock() + break + } + percent++ + if percent % step == 0 { + //c.Signal() + } + m.Unlock() + } + }() + } + for { + m.Lock() + if percent == 0 || percent % step != 0 { + c.Wait() + } + fmt.Print(",") + if percent == 100 { + m.Unlock() + break + } + m.Unlock() + } +} diff --git a/src/pkg/go/printer/example_test.go b/src/pkg/go/printer/example_test.go new file mode 100644 index 000000000..e570040ba --- /dev/null +++ b/src/pkg/go/printer/example_test.go @@ -0,0 +1,67 @@ +// 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 printer_test + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "strings" + "testing" +) + +// Dummy test function so that godoc does not use the entire file as example. +func Test(*testing.T) {} + +func parseFunc(filename, functionname string) (fun *ast.FuncDecl, fset *token.FileSet) { + fset = token.NewFileSet() + if file, err := parser.ParseFile(fset, filename, nil, 0); err == nil { + for _, d := range file.Decls { + if f, ok := d.(*ast.FuncDecl); ok && f.Name.Name == functionname { + fun = f + return + } + } + } + panic("function not found") +} + +func ExampleFprint() { + // Parse source file and extract the AST without comments for + // this function, with position information referring to the + // file set fset. + funcAST, fset := parseFunc("example_test.go", "ExampleFprint") + + // Print the function body into buffer buf. + // The file set is provided to the printer so that it knows + // about the original source formatting and can add additional + // line breaks where they were present in the source. + var buf bytes.Buffer + printer.Fprint(&buf, fset, funcAST.Body) + + // Remove braces {} enclosing the function body, unindent, + // and trim leading and trailing white space. + s := buf.String() + s = s[1 : len(s)-1] + s = strings.TrimSpace(strings.Replace(s, "\n\t", "\n", -1)) + + // Print the cleaned-up body text to stdout. + fmt.Println(s) + + // output: + // funcAST, fset := parseFunc("example_test.go", "ExampleFprint") + // + // var buf bytes.Buffer + // printer.Fprint(&buf, fset, funcAST.Body) + // + // s := buf.String() + // s = s[1 : len(s)-1] + // s = strings.TrimSpace(strings.Replace(s, "\n\t", "\n", -1)) + // + // fmt.Println(s) +} diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go index 05b4ef59a..727d2a371 100644 --- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -15,7 +15,7 @@ import ( "unicode/utf8" ) -// Other formatting issues: +// Formatting issues: // - better comment formatting for /*-style comments at the end of a line (e.g. a declaration) // when the comment spans multiple lines; if such a comment is just two lines, formatting is // not idempotent @@ -365,7 +365,7 @@ func (p *printer) setLineComment(text string) { } func (p *printer) isMultiLine(n ast.Node) bool { - return p.lineFor(n.End())-p.lineFor(n.Pos()) > 1 + return p.lineFor(n.End())-p.lineFor(n.Pos()) > 0 } func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) { @@ -964,6 +964,41 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po } } +// indentList reports whether an expression list would look better if it +// were indented wholesale (starting with the very first element, rather +// than starting at the first line break). +// +func (p *printer) indentList(list []ast.Expr) bool { + // Heuristic: indentList returns true if there are more than one multi- + // line element in the list, or if there is any element that is not + // starting on the same line as the previous one ends. + if len(list) >= 2 { + var b = p.lineFor(list[0].Pos()) + var e = p.lineFor(list[len(list)-1].End()) + if 0 < b && b < e { + // list spans multiple lines + n := 0 // multi-line element count + line := b + for _, x := range list { + xb := p.lineFor(x.Pos()) + xe := p.lineFor(x.End()) + if line < xb { + // x is not starting on the same + // line as the previous one ended + return true + } + if xb < xe { + // x is a multi-line element + n++ + } + line = xe + } + return n > 1 + } + } + return false +} + func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { p.print(stmt.Pos()) @@ -1030,7 +1065,18 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { p.print(token.RETURN) if s.Results != nil { p.print(blank) - p.exprList(s.Pos(), s.Results, 1, 0, token.NoPos) + // Use indentList heuristic to make corner cases look + // better (issue 1207). A more systematic approach would + // always indent, but this would cause significant + // reformatting of the code base and not necessarily + // lead to more nicely formatted code in general. + if p.indentList(s.Results) { + p.print(indent) + p.exprList(s.Pos(), s.Results, 1, noIndent, token.NoPos) + p.print(unindent) + } else { + p.exprList(s.Pos(), s.Results, 1, 0, token.NoPos) + } } case *ast.BranchStmt: @@ -1200,9 +1246,9 @@ func keepTypeColumn(specs []ast.Spec) []bool { return m } -func (p *printer) valueSpec(s *ast.ValueSpec, keepType, doIndent bool) { +func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) { p.setComment(s.Doc) - p.identList(s.Names, doIndent) // always present + p.identList(s.Names, false) // always present extraTabs := 3 if s.Type != nil || keepType { p.print(vtab) @@ -1290,7 +1336,7 @@ func (p *printer) genDecl(d *ast.GenDecl) { if i > 0 { p.linebreak(p.lineFor(s.Pos()), 1, ignore, newSection) } - p.valueSpec(s.(*ast.ValueSpec), keepType[i], false) + p.valueSpec(s.(*ast.ValueSpec), keepType[i]) newSection = p.isMultiLine(s) } } else { diff --git a/src/pkg/go/printer/testdata/declarations.golden b/src/pkg/go/printer/testdata/declarations.golden index 7ed7cb61a..71ed32ed1 100644 --- a/src/pkg/go/printer/testdata/declarations.golden +++ b/src/pkg/go/printer/testdata/declarations.golden @@ -500,7 +500,7 @@ type _ struct { type _ struct { a, b, - c, d int // this line should be indented + c, d int // this line should be indented u, v, w, x float // this line should be indented p, q, r, s float // this line should be indented @@ -562,10 +562,21 @@ var a2, b2, var ( a3, b3, - c3, d3 int // this line should be indented + c3, d3 int // this line should be indented a4, b4, c4 int // this line should be indented ) +// Test case from issue 3304: multi-line declarations must end +// a formatting section and not influence indentation of the +// next line. +var ( + minRefreshTimeSec = flag.Int64("min_refresh_time_sec", 604800, + "minimum time window between two refreshes for a given user.") + x = flag.Int64("refresh_user_rollout_percent", 100, + "temporary flag to ramp up the refresh user rpc") + aVeryLongVariableName = stats.GetVarInt("refresh-user-count") +) + func _() { var privateKey2 = &Block{Type: "RSA PRIVATE KEY", Headers: map[string]string{}, diff --git a/src/pkg/go/printer/testdata/declarations.input b/src/pkg/go/printer/testdata/declarations.input index df8c2b167..d74cff25d 100644 --- a/src/pkg/go/printer/testdata/declarations.input +++ b/src/pkg/go/printer/testdata/declarations.input @@ -577,6 +577,16 @@ c3, d3 int // this line should be indented a4, b4, c4 int // this line should be indented ) +// Test case from issue 3304: multi-line declarations must end +// a formatting section and not influence indentation of the +// next line. +var ( + minRefreshTimeSec = flag.Int64("min_refresh_time_sec", 604800, + "minimum time window between two refreshes for a given user.") + x = flag.Int64("refresh_user_rollout_percent", 100, + "temporary flag to ramp up the refresh user rpc") + aVeryLongVariableName = stats.GetVarInt("refresh-user-count") +) func _() { var privateKey2 = &Block{Type: "RSA PRIVATE KEY", diff --git a/src/pkg/go/printer/testdata/statements.golden b/src/pkg/go/printer/testdata/statements.golden index ffca21edb..4d70617bf 100644 --- a/src/pkg/go/printer/testdata/statements.golden +++ b/src/pkg/go/printer/testdata/statements.golden @@ -55,12 +55,24 @@ func _f() { return T{ 1, 2, - }, + }, nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, nil + return T{ + 1, + 2, + }, nil return T{ - 1, - 2, - }, + 1, + 2, + }, T{ x: 3, y: 4, @@ -70,10 +82,10 @@ func _f() { z return func() {} return func() { - _ = 0 - }, T{ - 1, 2, - } + _ = 0 + }, T{ + 1, 2, + } return func() { _ = 0 } @@ -84,6 +96,37 @@ func _f() { } } +// Formatting of multi-line returns: test cases from issue 1207. +func F() (*T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + nil +} + +func G() (*T, *T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + &T{ + X: 3, + Y: 4, + }, + nil +} + +func _() interface{} { + return &fileStat{ + name: basename(file.name), + size: mkSize(d.FileSizeHigh, d.FileSizeLow), + modTime: mkModTime(d.LastWriteTime), + mode: mkMode(d.FileAttributes), + sys: mkSysFromFI(&d), + }, nil +} + // Formatting of if-statement headers. func _() { if true { diff --git a/src/pkg/go/printer/testdata/statements.input b/src/pkg/go/printer/testdata/statements.input index 99945e955..bd03bc98b 100644 --- a/src/pkg/go/printer/testdata/statements.input +++ b/src/pkg/go/printer/testdata/statements.input @@ -55,6 +55,18 @@ func _f() { return T{ 1, 2, + }, nil + return T{ + 1, + 2, + }, + T{ + x: 3, + y: 4, + }, nil + return T{ + 1, + 2, }, nil return T{ @@ -84,6 +96,37 @@ func _f() { } } +// Formatting of multi-line returns: test cases from issue 1207. +func F() (*T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + nil +} + +func G() (*T, *T, os.Error) { + return &T{ + X: 1, + Y: 2, + }, + &T{ + X: 3, + Y: 4, + }, + nil +} + +func _() interface{} { + return &fileStat{ + name: basename(file.name), + size: mkSize(d.FileSizeHigh, d.FileSizeLow), + modTime: mkModTime(d.LastWriteTime), + mode: mkMode(d.FileAttributes), + sys: mkSysFromFI(&d), + }, nil +} + // Formatting of if-statement headers. func _() { if true {} diff --git a/src/pkg/go/scanner/scanner.go b/src/pkg/go/scanner/scanner.go index 2395363b0..da508747a 100644 --- a/src/pkg/go/scanner/scanner.go +++ b/src/pkg/go/scanner/scanner.go @@ -109,7 +109,7 @@ const ( func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { // Explicitly initialize all fields since a scanner may be reused. if file.Size() != len(src) { - panic("file size does not match src len") + panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) } s.file = file s.dir, _ = filepath.Split(file.Name()) |