diff options
| author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 12:00:31 +0200 |
|---|---|---|
| committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 12:00:31 +0200 |
| commit | 04f99b387021a8ce32a8795360cba9beaf986a81 (patch) | |
| tree | f806c632c5dec5bb83190946d6d8ff8bd33c0e57 /src/pkg/go | |
| parent | d9514677ddaa705852cbba5034cb6d284261b53a (diff) | |
| download | golang-04f99b387021a8ce32a8795360cba9beaf986a81.tar.gz | |
Imported Upstream version 2011.09.07upstream-weekly/2011.09.07
Diffstat (limited to 'src/pkg/go')
| -rw-r--r-- | src/pkg/go/ast/filter.go | 301 | ||||
| -rw-r--r-- | src/pkg/go/build/build_test.go | 66 | ||||
| -rw-r--r-- | src/pkg/go/build/dir.go | 149 | ||||
| -rw-r--r-- | src/pkg/go/build/pkgtest/pkgtest.go | 6 | ||||
| -rw-r--r-- | src/pkg/go/build/pkgtest/sqrt_386_test.go | 1 | ||||
| -rw-r--r-- | src/pkg/go/build/pkgtest/sqrt_amd64_test.go | 1 | ||||
| -rw-r--r-- | src/pkg/go/build/pkgtest/sqrt_arm_test.go | 1 | ||||
| -rw-r--r-- | src/pkg/go/build/pkgtest/sqrt_test.go | 5 | ||||
| -rw-r--r-- | src/pkg/go/build/pkgtest/xsqrt_test.go | 5 | ||||
| -rw-r--r-- | src/pkg/go/build/syslist_test.go | 2 | ||||
| -rw-r--r-- | src/pkg/go/parser/parser.go | 88 | ||||
| -rw-r--r-- | src/pkg/go/parser/parser_test.go | 8 | ||||
| -rw-r--r-- | src/pkg/go/printer/nodes.go | 16 | ||||
| -rw-r--r-- | src/pkg/go/printer/testdata/declarations.golden | 111 | ||||
| -rw-r--r-- | src/pkg/go/printer/testdata/declarations.input | 76 |
15 files changed, 532 insertions, 304 deletions
diff --git a/src/pkg/go/ast/filter.go b/src/pkg/go/ast/filter.go index 26733430d..1bd8990f8 100644 --- a/src/pkg/go/ast/filter.go +++ b/src/pkg/go/ast/filter.go @@ -9,10 +9,45 @@ import "go/token" // ---------------------------------------------------------------------------- // Export filtering -func identListExports(list []*Ident) []*Ident { +// exportFilter is a special filter function to extract exported nodes. +func exportFilter(name string) bool { + return IsExported(name) +} + +// FileExports trims the AST for a Go source file in place such that +// only exported nodes remain: all top-level identifiers which are not exported +// and their associated information (such as type, initial value, or function +// body) are removed. Non-exported fields and methods of exported types are +// stripped, and the function bodies of exported functions are set to nil. +// The File.Comments list is not changed. +// +// FileExports returns true if there are exported declarationa; +// it returns false otherwise. +// +func FileExports(src *File) bool { + return FilterFile(src, exportFilter) +} + +// PackageExports trims the AST for a Go package in place such that +// 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; +// it returns false otherwise. +// +func PackageExports(pkg *Package) bool { + return FilterPackage(pkg, exportFilter) +} + +// ---------------------------------------------------------------------------- +// General filtering + +type Filter func(string) bool + +func filterIdentList(list []*Ident, f Filter) []*Ident { j := 0 for _, x := range list { - if x.IsExported() { + if f(x.Name) { list[j] = x j++ } @@ -38,33 +73,30 @@ func fieldName(x Expr) *Ident { return nil } -func fieldListExports(fields *FieldList) (removedFields bool) { +func filterFieldList(fields *FieldList, filter Filter) (removedFields bool) { if fields == nil { - return + return false } list := fields.List j := 0 for _, f := range list { - exported := false + keepField := false if len(f.Names) == 0 { // anonymous field - // (Note that a non-exported anonymous field - // may still refer to a type with exported - // fields, so this is not absolutely correct. - // However, this cannot be done w/o complete - // type information.) name := fieldName(f.Type) - exported = name != nil && name.IsExported() + keepField = name != nil && filter(name.Name) } else { n := len(f.Names) - f.Names = identListExports(f.Names) + f.Names = filterIdentList(f.Names, filter) if len(f.Names) < n { removedFields = true } - exported = len(f.Names) > 0 + keepField = len(f.Names) > 0 } - if exported { - typeExports(f.Type) + if keepField { + if filter == exportFilter { + filterType(f.Type, filter) + } list[j] = f j++ } @@ -76,185 +108,75 @@ func fieldListExports(fields *FieldList) (removedFields bool) { return } -func paramListExports(fields *FieldList) { +func filterParamList(fields *FieldList, filter Filter) bool { if fields == nil { - return + return false } + var b bool for _, f := range fields.List { - typeExports(f.Type) + if filterType(f.Type, filter) { + b = true + } } + return b } -func typeExports(typ Expr) { +func filterType(typ Expr, f Filter) bool { switch t := typ.(type) { + case *Ident: + return f(t.Name) + case *ParenExpr: + return filterType(t.X, f) case *ArrayType: - typeExports(t.Elt) + return filterType(t.Elt, f) case *StructType: - if fieldListExports(t.Fields) { + if filterFieldList(t.Fields, f) { t.Incomplete = true } + return len(t.Fields.List) > 0 case *FuncType: - paramListExports(t.Params) - paramListExports(t.Results) + b1 := filterParamList(t.Params, f) + b2 := filterParamList(t.Results, f) + return b1 || b2 case *InterfaceType: - if fieldListExports(t.Methods) { + if filterFieldList(t.Methods, f) { t.Incomplete = true } + return len(t.Methods.List) > 0 case *MapType: - typeExports(t.Key) - typeExports(t.Value) + b1 := filterType(t.Key, f) + b2 := filterType(t.Value, f) + return b1 || b2 case *ChanType: - typeExports(t.Value) - } -} - -func specExports(spec Spec) bool { - switch s := spec.(type) { - case *ValueSpec: - s.Names = identListExports(s.Names) - if len(s.Names) > 0 { - typeExports(s.Type) - return true - } - case *TypeSpec: - if s.Name.IsExported() { - typeExports(s.Type) - return true - } + return filterType(t.Value, f) } return false } -func specListExports(list []Spec) []Spec { - j := 0 - for _, s := range list { - if specExports(s) { - list[j] = s - j++ - } - } - return list[0:j] -} - -func declExports(decl Decl) bool { - switch d := decl.(type) { - case *GenDecl: - d.Specs = specListExports(d.Specs) - return len(d.Specs) > 0 - case *FuncDecl: - d.Body = nil // strip body - return d.Name.IsExported() - } - return false -} - -// FileExports trims the AST for a Go source file in place such that only -// exported nodes remain: all top-level identifiers which are not exported -// and their associated information (such as type, initial value, or function -// body) are removed. Non-exported fields and methods of exported types are -// stripped, and the function bodies of exported functions are set to nil. -// The File.comments list is not changed. -// -// FileExports returns true if there is an exported declaration; it returns -// false otherwise. -// -func FileExports(src *File) bool { - j := 0 - for _, d := range src.Decls { - if declExports(d) { - src.Decls[j] = d - j++ - } - } - src.Decls = src.Decls[0:j] - return j > 0 -} - -// PackageExports trims the AST for a Go package in place such that 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 is an exported declaration; it -// returns false otherwise. -// -func PackageExports(pkg *Package) bool { - hasExports := false - for _, f := range pkg.Files { - if FileExports(f) { - hasExports = true - } - } - return hasExports -} - -// ---------------------------------------------------------------------------- -// General filtering - -type Filter func(string) bool - -func filterIdentList(list []*Ident, f Filter) []*Ident { - j := 0 - for _, x := range list { - if f(x.Name) { - list[j] = x - j++ - } - } - return list[0:j] -} - -func filterFieldList(fields *FieldList, filter Filter) (removedFields bool) { - if fields == nil { - return false - } - list := fields.List - j := 0 - for _, f := range list { - keepField := false - if len(f.Names) == 0 { - // anonymous field - name := fieldName(f.Type) - keepField = name != nil && filter(name.Name) - } else { - n := len(f.Names) - f.Names = filterIdentList(f.Names, filter) - if len(f.Names) < n { - removedFields = true - } - keepField = len(f.Names) > 0 - } - if keepField { - list[j] = f - j++ - } - } - if j < len(list) { - removedFields = true - } - fields.List = list[0:j] - return -} - func filterSpec(spec Spec, f Filter) bool { switch s := spec.(type) { case *ValueSpec: s.Names = filterIdentList(s.Names, f) - return len(s.Names) > 0 + if len(s.Names) > 0 { + if f == exportFilter { + filterType(s.Type, f) + } + return true + } case *TypeSpec: if f(s.Name.Name) { + if f == exportFilter { + filterType(s.Type, f) + } return true } - switch t := s.Type.(type) { - case *StructType: - if filterFieldList(t.Fields, f) { - t.Incomplete = true - } - return len(t.Fields.List) > 0 - case *InterfaceType: - if filterFieldList(t.Methods, f) { - t.Incomplete = true - } - return len(t.Methods.List) > 0 + if f != exportFilter { + // For general filtering (not just exports), + // filter type even if name is not filtered + // out. + // If the type contains filtered elements, + // keep the declaration. + return filterType(s.Type, f) } } return false @@ -284,6 +206,9 @@ func FilterDecl(decl Decl, f Filter) bool { d.Specs = filterSpecList(d.Specs, f) return len(d.Specs) > 0 case *FuncDecl: + if f == exportFilter { + d.Body = nil // strip body + } return f(d.Name.Name) } return false @@ -293,8 +218,8 @@ func FilterDecl(decl Decl, f Filter) 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. The File.Comments list +// is not changed. // // FilterFile returns true if there are any top-level declarations // left after filtering; it returns false otherwise. @@ -311,13 +236,13 @@ func FilterFile(src *File, f Filter) bool { return j > 0 } -// FilterPackage trims the AST for a Go package in place by removing all -// names from top-level declarations (including struct field and +// FilterPackage trims the AST for a Go package in place by removing +// all 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 pkg.Files list is not changed, so that file names and top-level -// package comments don't get lost. +// the declaration is removed from the AST. The pkg.Files list is not +// 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. @@ -344,6 +269,8 @@ const ( // If set, comments that are not associated with a specific // AST node (as Doc or Comment) are excluded. FilterUnassociatedComments + // If set, duplicate import declarations are excluded. + FilterImportDuplicates ) // separator is an empty //-style comment that is interspersed between @@ -459,6 +386,31 @@ func MergePackageFiles(pkg *Package, mode MergeMode) *File { } } + // Collect import specs from all package files. + var imports []*ImportSpec + if mode&FilterImportDuplicates != 0 { + seen := make(map[string]bool) + for _, f := range pkg.Files { + for _, imp := range f.Imports { + if path := imp.Path.Value; !seen[path] { + // TODO: consider handling cases where: + // - 2 imports exist with the same import path but + // have different local names (one should probably + // keep both of them) + // - 2 imports exist but only one has a comment + // - 2 imports exist and they both have (possibly + // different) comments + imports = append(imports, imp) + seen[path] = true + } + } + } + } else { + for _, f := range pkg.Files { + imports = append(imports, f.Imports...) + } + } + // Collect comments from all package files. var comments []*CommentGroup if mode&FilterUnassociatedComments == 0 { @@ -469,7 +421,6 @@ func MergePackageFiles(pkg *Package, mode MergeMode) *File { } } - // TODO(gri) need to compute pkgScope and unresolved identifiers! - // TODO(gri) need to compute imports! - return &File{doc, pos, NewIdent(pkg.Name), decls, nil, nil, nil, comments} + // TODO(gri) need to compute unresolved identifiers! + return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments} } diff --git a/src/pkg/go/build/build_test.go b/src/pkg/go/build/build_test.go index e59d87672..592ebbd9e 100644 --- a/src/pkg/go/build/build_test.go +++ b/src/pkg/go/build/build_test.go @@ -7,44 +7,85 @@ package build import ( "exec" "path/filepath" + "reflect" + "runtime" + "sort" "testing" ) -var buildPkgs = []string{ - "go/build/pkgtest", - "go/build/cmdtest", - "go/build/cgotest", +func sortstr(x []string) []string { + sort.Strings(x) + return x +} + +var buildPkgs = []struct { + dir string + info *DirInfo +}{ + { + "go/build/pkgtest", + &DirInfo{ + GoFiles: []string{"pkgtest.go"}, + SFiles: []string{"sqrt_" + runtime.GOARCH + ".s"}, + PkgName: "pkgtest", + Imports: []string{"os"}, + TestImports: []string{"fmt", "pkgtest"}, + TestGoFiles: sortstr([]string{"sqrt_test.go", "sqrt_" + runtime.GOARCH + "_test.go"}), + XTestGoFiles: []string{"xsqrt_test.go"}, + }, + }, + { + "go/build/cmdtest", + &DirInfo{ + GoFiles: []string{"main.go"}, + PkgName: "main", + Imports: []string{"go/build/pkgtest"}, + }, + }, + { + "go/build/cgotest", + &DirInfo{ + CgoFiles: []string{"cgotest.go"}, + CFiles: []string{"cgotest.c"}, + Imports: []string{"C", "unsafe"}, + PkgName: "cgotest", + }, + }, } const cmdtestOutput = "3" func TestBuild(t *testing.T) { - for _, pkg := range buildPkgs { + for _, tt := range buildPkgs { tree := Path[0] // Goroot - dir := filepath.Join(tree.SrcDir(), pkg) + dir := filepath.Join(tree.SrcDir(), tt.dir) info, err := ScanDir(dir, true) if err != nil { - t.Error("ScanDir:", err) + t.Errorf("ScanDir(%#q): %v", tt.dir, err) + continue + } + if !reflect.DeepEqual(info, tt.info) { + t.Errorf("ScanDir(%#q) = %#v, want %#v\n", tt.dir, info, tt.info) continue } - s, err := Build(tree, pkg, info) + s, err := Build(tree, tt.dir, info) if err != nil { - t.Error("Build:", err) + t.Errorf("Build(%#q): %v", tt.dir, err) continue } if err := s.Run(); err != nil { - t.Error("Run:", err) + t.Errorf("Run(%#q): %v", tt.dir, err) continue } - if pkg == "go/build/cmdtest" { + if tt.dir == "go/build/cmdtest" { bin := s.Output[0] b, err := exec.Command(bin).CombinedOutput() if err != nil { - t.Errorf("exec: %s: %v", bin, err) + t.Errorf("exec %s: %v", bin, err) continue } if string(b) != cmdtestOutput { @@ -52,6 +93,7 @@ func TestBuild(t *testing.T) { } } + // Deferred because cmdtest depends on pkgtest. defer func(s *Script) { if err := s.Nuke(); err != nil { t.Errorf("nuking: %v", err) diff --git a/src/pkg/go/build/dir.go b/src/pkg/go/build/dir.go index e0000b534..fa4d9e913 100644 --- a/src/pkg/go/build/dir.go +++ b/src/pkg/go/build/dir.go @@ -7,27 +7,60 @@ package build import ( "go/parser" "go/token" + "io/ioutil" "log" "os" "path/filepath" + "sort" "strconv" "strings" "runtime" ) +// A Context specifies the supporting context for a build. +type Context struct { + GOARCH string // target architecture + GOOS string // target operating system + // TODO(rsc,adg): GOPATH +} + +// The DefaultContext is the default Context for builds. +// It uses the GOARCH and GOOS environment variables +// if set, or else the compiled code's GOARCH and GOOS. +var DefaultContext = Context{ + envOr("GOARCH", runtime.GOARCH), + envOr("GOOS", runtime.GOOS), +} + +func envOr(name, def string) string { + s := os.Getenv(name) + if s == "" { + return def + } + return s +} + type DirInfo struct { - GoFiles []string // .go files in dir (excluding CgoFiles) - CgoFiles []string // .go files that import "C" - CFiles []string // .c files in dir - SFiles []string // .s files in dir - Imports []string // All packages imported by goFiles - PkgName string // Name of package in dir + GoFiles []string // .go files in dir (excluding CgoFiles) + CgoFiles []string // .go files that import "C" + CFiles []string // .c files in dir + SFiles []string // .s files in dir + Imports []string // All packages imported by GoFiles + TestImports []string // All packages imported by (X)TestGoFiles + PkgName string // Name of package in dir + TestGoFiles []string // _test.go files in package + XTestGoFiles []string // _test.go files outside package } func (d *DirInfo) IsCommand() bool { return d.PkgName == "main" } +// ScanDir calls DefaultContext.ScanDir. +func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { + return DefaultContext.ScanDir(dir, allowMain) +} + // ScanDir returns a structure with details about the Go content found // in the given directory. The file lists exclude: // @@ -36,36 +69,29 @@ func (d *DirInfo) IsCommand() bool { // - files ending in _test.go // - files starting with _ or . // -// Only files that satisfy the goodOSArch function are included. -func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { - f, err := os.Open(dir) - if err != nil { - return nil, err - } - dirs, err := f.Readdir(-1) - f.Close() +func (ctxt *Context) ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { + dirs, err := ioutil.ReadDir(dir) if err != nil { return nil, err } var di DirInfo imported := make(map[string]bool) + testImported := make(map[string]bool) fset := token.NewFileSet() - for i := range dirs { - d := &dirs[i] + for _, d := range dirs { if strings.HasPrefix(d.Name, "_") || strings.HasPrefix(d.Name, ".") { continue } - if !goodOSArch(d.Name) { + if !ctxt.goodOSArch(d.Name) { continue } + isTest := false switch filepath.Ext(d.Name) { case ".go": - if strings.HasSuffix(d.Name, "_test.go") { - continue - } + isTest = strings.HasSuffix(d.Name, "_test.go") case ".c": di.CFiles = append(di.CFiles, d.Name) continue @@ -81,21 +107,24 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { if err != nil { return nil, err } - s := string(pf.Name.Name) - if s == "main" && !allowMain { + pkg := string(pf.Name.Name) + if pkg == "main" && !allowMain { continue } - if s == "documentation" { + if pkg == "documentation" { continue } + if isTest && strings.HasSuffix(pkg, "_test") { + pkg = pkg[:len(pkg)-len("_test")] + } if di.PkgName == "" { - di.PkgName = s - } else if di.PkgName != s { + di.PkgName = pkg + } else if di.PkgName != pkg { // Only if all files in the directory are in package main // do we return PkgName=="main". // A mix of main and another package reverts // to the original (allowMain=false) behaviour. - if s == "main" || di.PkgName == "main" { + if pkg == "main" || di.PkgName == "main" { return ScanDir(dir, false) } return nil, os.NewError("multiple package names in " + dir) @@ -107,13 +136,26 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { if err != nil { log.Panicf("%s: parser returned invalid quoted string: <%s>", filename, quoted) } - imported[path] = true + if isTest { + testImported[path] = true + } else { + imported[path] = true + } if path == "C" { + if isTest { + return nil, os.NewError("use of cgo in test " + filename) + } isCgo = true } } if isCgo { di.CgoFiles = append(di.CgoFiles, d.Name) + } else if isTest { + if pkg == string(pf.Name.Name) { + di.TestGoFiles = append(di.TestGoFiles, d.Name) + } else { + di.XTestGoFiles = append(di.XTestGoFiles, d.Name) + } } else { di.GoFiles = append(di.GoFiles, d.Name) } @@ -124,49 +166,58 @@ func ScanDir(dir string, allowMain bool) (info *DirInfo, err os.Error) { di.Imports[i] = p i++ } + di.TestImports = make([]string, len(testImported)) + i = 0 + for p := range testImported { + di.TestImports[i] = p + i++ + } + // File name lists are sorted because ioutil.ReadDir sorts. + sort.Strings(di.Imports) + sort.Strings(di.TestImports) return &di, nil } -// goodOSArch returns false if the filename contains a $GOOS or $GOARCH +// goodOSArch returns false if the name contains a $GOOS or $GOARCH // suffix which does not match the current system. -// The recognized filename formats are: +// The recognized name formats are: // // name_$(GOOS).* // name_$(GOARCH).* // name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* // -func goodOSArch(filename string) bool { - if dot := strings.Index(filename, "."); dot != -1 { - filename = filename[:dot] +func (ctxt *Context) goodOSArch(name string) bool { + if dot := strings.Index(name, "."); dot != -1 { + name = name[:dot] + } + l := strings.Split(name, "_") + if n := len(l); n > 0 && l[n-1] == "test" { + l = l[:n-1] } - l := strings.Split(filename, "_") n := len(l) - if n == 0 { - return true + if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { + return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH } - if good, known := goodOS[l[n-1]]; known { - return good + if n >= 1 && knownOS[l[n-1]] { + return l[n-1] == ctxt.GOOS } - if good, known := goodArch[l[n-1]]; known { - if !good || n < 2 { - return false - } - good, known = goodOS[l[n-2]] - return good || !known + if n >= 1 && knownArch[l[n-1]] { + return l[n-1] == ctxt.GOARCH } return true } -var goodOS = make(map[string]bool) -var goodArch = make(map[string]bool) +var knownOS = make(map[string]bool) +var knownArch = make(map[string]bool) func init() { - goodOS = make(map[string]bool) - goodArch = make(map[string]bool) for _, v := range strings.Fields(goosList) { - goodOS[v] = v == runtime.GOOS + knownOS[v] = true } for _, v := range strings.Fields(goarchList) { - goodArch[v] = v == runtime.GOARCH + knownArch[v] = true } } diff --git a/src/pkg/go/build/pkgtest/pkgtest.go b/src/pkg/go/build/pkgtest/pkgtest.go index 9322f5ebd..03ebb9893 100644 --- a/src/pkg/go/build/pkgtest/pkgtest.go +++ b/src/pkg/go/build/pkgtest/pkgtest.go @@ -4,6 +4,10 @@ package pkgtest -func Foo() {} +import "os" + +func Foo() os.Error { + return nil +} func Sqrt(x float64) float64 diff --git a/src/pkg/go/build/pkgtest/sqrt_386_test.go b/src/pkg/go/build/pkgtest/sqrt_386_test.go new file mode 100644 index 000000000..26b483fa0 --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_386_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_amd64_test.go b/src/pkg/go/build/pkgtest/sqrt_amd64_test.go new file mode 100644 index 000000000..26b483fa0 --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_amd64_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_arm_test.go b/src/pkg/go/build/pkgtest/sqrt_arm_test.go new file mode 100644 index 000000000..26b483fa0 --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_arm_test.go @@ -0,0 +1 @@ +package pkgtest diff --git a/src/pkg/go/build/pkgtest/sqrt_test.go b/src/pkg/go/build/pkgtest/sqrt_test.go new file mode 100644 index 000000000..95fb62552 --- /dev/null +++ b/src/pkg/go/build/pkgtest/sqrt_test.go @@ -0,0 +1,5 @@ +package pkgtest + +import "fmt" + +var _ = fmt.Printf diff --git a/src/pkg/go/build/pkgtest/xsqrt_test.go b/src/pkg/go/build/pkgtest/xsqrt_test.go new file mode 100644 index 000000000..77e903d96 --- /dev/null +++ b/src/pkg/go/build/pkgtest/xsqrt_test.go @@ -0,0 +1,5 @@ +package pkgtest_test + +import "pkgtest" + +var _ = pkgtest.Foo diff --git a/src/pkg/go/build/syslist_test.go b/src/pkg/go/build/syslist_test.go index eb0e5dcb6..2e8b4c865 100644 --- a/src/pkg/go/build/syslist_test.go +++ b/src/pkg/go/build/syslist_test.go @@ -55,7 +55,7 @@ var tests = []GoodFileTest{ func TestGoodOSArch(t *testing.T) { for _, test := range tests { - if goodOSArch(test.name) != test.result { + if DefaultContext.goodOSArch(test.name) != test.result { t.Fatalf("goodOSArch(%q) != %v", test.name, test.result) } } diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go index 9c14d1667..be82b2f80 100644 --- a/src/pkg/go/parser/parser.go +++ b/src/pkg/go/parser/parser.go @@ -587,7 +587,6 @@ func (p *parser) parseStructType() *ast.StructType { } rbrace := p.expect(token.RBRACE) - // TODO(gri): store struct scope in AST return &ast.StructType{pos, &ast.FieldList{lbrace, list, rbrace}, false} } @@ -611,9 +610,6 @@ func (p *parser) tryVarType(isParam bool) ast.Expr { p.error(pos, "'...' parameter is missing type") typ = &ast.BadExpr{pos, p.pos} } - if p.tok != token.RPAREN { - p.error(pos, "can use '...' with last parameter type only") - } return &ast.Ellipsis{pos, typ} } return p.tryIdentOrType(false) @@ -636,21 +632,21 @@ func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) { } // a list of identifiers looks like a list of type names - for { - // parseVarType accepts any type (including parenthesized ones) - // even though the syntax does not permit them here: we - // accept them all for more robust parsing and complain - // afterwards - list = append(list, p.parseVarType(isParam)) + // + // parse/tryVarType accepts any type (including parenthesized + // ones) even though the syntax does not permit them here: we + // accept them all for more robust parsing and complain later + for typ := p.parseVarType(isParam); typ != nil; { + list = append(list, typ) if p.tok != token.COMMA { break } p.next() + typ = p.tryVarType(isParam) // maybe nil as in: func f(int,) {} } // if we had a list of identifiers, it must be followed by a type - typ = p.tryVarType(isParam) - if typ != nil { + if typ = p.tryVarType(isParam); typ != nil { p.resolve(typ) } @@ -800,7 +796,6 @@ func (p *parser) parseInterfaceType() *ast.InterfaceType { } rbrace := p.expect(token.RBRACE) - // TODO(gri): store interface scope in AST return &ast.InterfaceType{pos, &ast.FieldList{lbrace, list, rbrace}, false} } @@ -1436,14 +1431,14 @@ func (p *parser) parseSimpleStmt(mode int) (ast.Stmt, bool) { case token.ARROW: // send statement arrow := p.pos - p.next() // consume "<-" + p.next() y := p.parseRhs() return &ast.SendStmt{x[0], arrow, y}, false case token.INC, token.DEC: // increment or decrement s := &ast.IncDecStmt{x[0], p.pos, p.tok} - p.next() // consume "++" or "--" + p.next() return s, false } @@ -1591,7 +1586,7 @@ func (p *parser) parseTypeList() (list []ast.Expr) { return } -func (p *parser) parseCaseClause(exprSwitch bool) *ast.CaseClause { +func (p *parser) parseCaseClause(typeSwitch bool) *ast.CaseClause { if p.trace { defer un(trace(p, "CaseClause")) } @@ -1600,10 +1595,10 @@ func (p *parser) parseCaseClause(exprSwitch bool) *ast.CaseClause { var list []ast.Expr if p.tok == token.CASE { p.next() - if exprSwitch { - list = p.parseRhsList() - } else { + if typeSwitch { list = p.parseTypeList() + } else { + list = p.parseRhsList() } } else { p.expect(token.DEFAULT) @@ -1617,15 +1612,19 @@ func (p *parser) parseCaseClause(exprSwitch bool) *ast.CaseClause { return &ast.CaseClause{pos, list, colon, body} } -func isExprSwitch(s ast.Stmt) bool { - if s == nil { - return true - } - if e, ok := s.(*ast.ExprStmt); ok { - if a, ok := e.X.(*ast.TypeAssertExpr); ok { - return a.Type != nil // regular type assertion - } - return true +func isTypeSwitchAssert(x ast.Expr) bool { + a, ok := x.(*ast.TypeAssertExpr) + return ok && a.Type == nil +} + +func isTypeSwitchGuard(s ast.Stmt) bool { + switch t := s.(type) { + case *ast.ExprStmt: + // x.(nil) + return isTypeSwitchAssert(t.X) + case *ast.AssignStmt: + // v := x.(nil) + return len(t.Lhs) == 1 && t.Tok == token.DEFINE && len(t.Rhs) == 1 && isTypeSwitchAssert(t.Rhs[0]) } return false } @@ -1651,28 +1650,41 @@ func (p *parser) parseSwitchStmt() ast.Stmt { s1 = s2 s2 = nil if p.tok != token.LBRACE { + // A TypeSwitchGuard may declare a variable in addition + // to the variable declared in the initial SimpleStmt. + // Introduce extra scope to avoid redeclaration errors: + // + // switch t := 0; t := x.(T) { ... } + // + // (this code is not valid Go because the first t will + // cannot be accessed and thus is never used, the extra + // scope is needed for the correct error message). + // + // If we don't have a type switch, s2 must be an expression. + // Having the extra nested but empty scope won't affect it. + p.openScope() + defer p.closeScope() s2, _ = p.parseSimpleStmt(basic) } } p.exprLev = prevLev } - exprSwitch := isExprSwitch(s2) + typeSwitch := isTypeSwitchGuard(s2) lbrace := p.expect(token.LBRACE) var list []ast.Stmt for p.tok == token.CASE || p.tok == token.DEFAULT { - list = append(list, p.parseCaseClause(exprSwitch)) + list = append(list, p.parseCaseClause(typeSwitch)) } rbrace := p.expect(token.RBRACE) p.expectSemi() body := &ast.BlockStmt{lbrace, list, rbrace} - if exprSwitch { - return &ast.SwitchStmt{pos, s1, p.makeExpr(s2), body} + if typeSwitch { + return &ast.TypeSwitchStmt{pos, s1, s2, body} } - // type switch - // TODO(gri): do all the checks! - return &ast.TypeSwitchStmt{pos, s1, s2, body} + + return &ast.SwitchStmt{pos, s1, p.makeExpr(s2), body} } func (p *parser) parseCommClause() *ast.CommClause { @@ -2001,14 +2013,12 @@ func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList { defer un(trace(p, "Receiver")) } - pos := p.pos par := p.parseParameters(scope, false) // must have exactly one receiver if par.NumFields() != 1 { - p.errorExpected(pos, "exactly one receiver") - // TODO determine a better range for BadExpr below - par.List = []*ast.Field{&ast.Field{Type: &ast.BadExpr{pos, pos}}} + p.errorExpected(par.Opening, "exactly one receiver") + par.List = []*ast.Field{&ast.Field{Type: &ast.BadExpr{par.Opening, par.Closing + 1}}} return par } diff --git a/src/pkg/go/parser/parser_test.go b/src/pkg/go/parser/parser_test.go index 39a78e515..9705dcff2 100644 --- a/src/pkg/go/parser/parser_test.go +++ b/src/pkg/go/parser/parser_test.go @@ -26,6 +26,9 @@ var illegalInputs = []interface{}{ `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 */`, @@ -61,6 +64,9 @@ var validPrograms = []interface{}{ `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]} {} };`, @@ -74,7 +80,7 @@ var validPrograms = []interface{}{ func TestParseValidPrograms(t *testing.T) { for _, src := range validPrograms { - _, err := ParseFile(fset, "", src, 0) + _, err := ParseFile(fset, "", src, SpuriousErrors) if err != nil { t.Errorf("ParseFile(%q): %v", src, err) } diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go index 9cd975ec1..364530634 100644 --- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -269,6 +269,7 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) { p.print(fields.Opening, token.LPAREN) if len(fields.List) > 0 { + ws := indent var prevLine, line int for i, par := range fields.List { if i > 0 { @@ -278,19 +279,30 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) { } else { line = p.fset.Position(par.Type.Pos()).Line } - if 0 < prevLine && prevLine < line && p.linebreak(line, 0, ignore, true) { + if 0 < prevLine && prevLine < line && p.linebreak(line, 0, ws, true) { + ws = ignore *multiLine = true } else { p.print(blank) } } if len(par.Names) > 0 { - p.identList(par.Names, false, multiLine) + // Very subtle: If we indented before (ws == ignore), identList + // won't indent again. If we didn't (ws == indent), identList will + // indent if the identList spans multiple lines, and it will outdent + // again at the end (and still ws == indent). Thus, a subsequent indent + // by a linebreak call after a type, or in the next multi-line identList + // will do the right thing. + p.identList(par.Names, ws == indent, multiLine) p.print(blank) } p.expr(par.Type, multiLine) prevLine = p.fset.Position(par.Type.Pos()).Line } + if ws == ignore { + // unindent if we indented + p.print(unindent) + } } p.print(fields.Closing, token.RPAREN) } diff --git a/src/pkg/go/printer/testdata/declarations.golden b/src/pkg/go/printer/testdata/declarations.golden index 970533e8c..bfa2568c2 100644 --- a/src/pkg/go/printer/testdata/declarations.golden +++ b/src/pkg/go/printer/testdata/declarations.golden @@ -692,56 +692,119 @@ func _(x ...chan int) // these parameter lists must remain multi-line since they are multi-line in the source func _(bool, -int) { + int) { } func _(x bool, -y int) { + y int) { } func _(x, -y bool) { + y bool) { } func _(bool, // comment -int) { + int) { } func _(x bool, // comment -y int) { + y int) { } func _(x, // comment -y bool) { + y bool) { } func _(bool, // comment -// comment -int) { + // comment + int) { } func _(x bool, // comment -// comment -y int) { + // comment + y int) { } func _(x, // comment -// comment -y bool) { + // comment + y bool) { } func _(bool, -// comment -int) { + // comment + int) { } func _(x bool, -// comment -y int) { + // comment + y int) { } func _(x, -// comment -y bool) { + // comment + y bool) { } func _(x, // comment -y, // comment -z bool) { + y, // comment + z bool) { } func _(x, // comment -y, // comment -z bool) { + y, // comment + z bool) { } func _(x int, // comment -y float, // comment -z bool) { + y float, // comment + z bool) { +} + +// properly indent multi-line signatures +func ManageStatus(in <-chan *Status, req <-chan Request, + stat chan<- *TargetInfo, + TargetHistorySize int) { +} + +func MultiLineSignature0(a, b, c int) { +} + +func MultiLineSignature1(a, b, c int, + u, v, w float) { +} + +func MultiLineSignature2(a, b, + c int) { +} + +func MultiLineSignature3(a, b, + c int, u, v, + w float, + x ...int) { +} + +func MultiLineSignature4(a, b, c int, + u, v, + w float, + x ...int) { +} + +func MultiLineSignature5(a, b, c int, + u, v, w float, + p, q, + r string, + x ...int) { +} + +// make sure it also works for methods in interfaces +type _ interface { + MultiLineSignature0(a, b, c int) + + MultiLineSignature1(a, b, c int, + u, v, w float) + + MultiLineSignature2(a, b, + c int) + + MultiLineSignature3(a, b, + c int, u, v, + w float, + x ...int) + + MultiLineSignature4(a, b, c int, + u, v, + w float, + x ...int) + + MultiLineSignature5(a, b, c int, + u, v, w float, + p, q, + r string, + x ...int) } diff --git a/src/pkg/go/printer/testdata/declarations.input b/src/pkg/go/printer/testdata/declarations.input index c6134096b..1d69c57b5 100644 --- a/src/pkg/go/printer/testdata/declarations.input +++ b/src/pkg/go/printer/testdata/declarations.input @@ -755,3 +755,79 @@ func _(x int, // comment y float, // comment z bool) { } + + +// properly indent multi-line signatures +func ManageStatus(in <-chan *Status, req <-chan Request, +stat chan<- *TargetInfo, +TargetHistorySize int) { +} + +func MultiLineSignature0( +a, b, c int, +) {} + +func MultiLineSignature1( +a, b, c int, +u, v, w float, +) {} + +func MultiLineSignature2( +a, b, +c int, +) {} + +func MultiLineSignature3( +a, b, +c int, u, v, +w float, + x ...int) {} + +func MultiLineSignature4( +a, b, c int, +u, v, +w float, + x ...int) {} + +func MultiLineSignature5( +a, b, c int, +u, v, w float, +p, q, +r string, + x ...int) {} + +// make sure it also works for methods in interfaces +type _ interface { +MultiLineSignature0( +a, b, c int, +) + +MultiLineSignature1( +a, b, c int, +u, v, w float, +) + +MultiLineSignature2( +a, b, +c int, +) + +MultiLineSignature3( +a, b, +c int, u, v, +w float, + x ...int) + +MultiLineSignature4( +a, b, c int, +u, v, +w float, + x ...int) + +MultiLineSignature5( +a, b, c int, +u, v, w float, +p, q, +r string, + x ...int) +} |
