diff options
Diffstat (limited to 'libgo/go/go')
35 files changed, 1266 insertions, 315 deletions
diff --git a/libgo/go/go/ast/resolve.go b/libgo/go/go/ast/resolve.go index c7c8e7c101e..908e61c5da0 100644 --- a/libgo/go/go/ast/resolve.go +++ b/libgo/go/go/ast/resolve.go @@ -14,12 +14,12 @@ import ( ) type pkgBuilder struct { - scanner.ErrorVector - fset *token.FileSet + fset *token.FileSet + errors scanner.ErrorList } func (p *pkgBuilder) error(pos token.Pos, msg string) { - p.Error(p.fset.Position(pos), msg) + p.errors.Add(p.fset.Position(pos), msg) } func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...interface{}) { @@ -169,5 +169,6 @@ func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, pkgScope.Outer = universe // reset universe scope } - return &Package{pkgName, pkgScope, imports, files}, p.GetError(scanner.Sorted) + p.errors.Sort() + return &Package{pkgName, pkgScope, imports, files}, p.errors.Err() } diff --git a/libgo/go/go/build/dir.go b/libgo/go/go/build/dir.go index 0917e736aa4..6b30f76265b 100644 --- a/libgo/go/go/build/dir.go +++ b/libgo/go/go/build/dir.go @@ -25,10 +25,11 @@ import ( // A Context specifies the supporting context for a build. type Context struct { - GOARCH string // target architecture - GOOS string // target operating system - CgoEnabled bool // whether cgo can be used - BuildTags []string // additional tags to recognize in +build lines + GOARCH string // target architecture + GOOS string // target operating system + 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 // By default, ScanDir uses the operating system's // file system calls to read directories and files. @@ -225,6 +226,7 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { var Sfiles []string // files with ".S" (capital S) var di DirInfo + var firstFile string imported := make(map[string][]token.Position) testImported := make(map[string][]token.Position) fset := token.NewFileSet() @@ -237,7 +239,7 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { strings.HasPrefix(name, ".") { continue } - if !ctxt.goodOSArchFile(name) { + if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) { continue } @@ -250,12 +252,13 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { continue } - // Look for +build comments to accept or reject the file. filename, data, err := ctxt.readFile(dir, name) if err != nil { return nil, err } - if !ctxt.shouldBuild(data) { + + // Look for +build comments to accept or reject the file. + if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) { continue } @@ -281,9 +284,6 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { } pkg := string(pf.Name.Name) - if pkg == "main" && di.Package != "" && di.Package != "main" { - continue - } if pkg == "documentation" { continue } @@ -293,15 +293,11 @@ func (ctxt *Context) ScanDir(dir string) (info *DirInfo, err error) { pkg = pkg[:len(pkg)-len("_test")] } - if pkg != di.Package && di.Package == "main" { - // Found non-main package but was recording - // information about package main. Reset. - di = DirInfo{} - } if di.Package == "" { di.Package = pkg + firstFile = name } else if pkg != di.Package { - return nil, fmt.Errorf("%s: found packages %s and %s", dir, pkg, di.Package) + return nil, fmt.Errorf("%s: found packages %s (%s) and %s (%s)", dir, di.Package, firstFile, pkg, name) } if pf.Doc != nil { if di.PackageComment != nil { diff --git a/libgo/go/go/build/path.go b/libgo/go/go/build/path.go index 7e931faff19..e160ac3b280 100644 --- a/libgo/go/go/build/path.go +++ b/libgo/go/go/build/path.go @@ -12,6 +12,9 @@ import ( "runtime" ) +// ToolDir is the directory containing build tools. +var ToolDir = filepath.Join(runtime.GOROOT(), "pkg/tool/"+runtime.GOOS+"_"+runtime.GOARCH) + // Path is a validated list of Trees derived from $GOROOT and $GOPATH at init. var Path []*Tree diff --git a/libgo/go/go/doc/doc_test.go b/libgo/go/go/doc/doc_test.go index 9ffe72032c2..f957ede4abf 100644 --- a/libgo/go/go/doc/doc_test.go +++ b/libgo/go/go/doc/doc_test.go @@ -14,12 +14,14 @@ import ( "io/ioutil" "os" "path/filepath" + "regexp" "strings" "testing" "text/template" ) var update = flag.Bool("update", false, "update golden (.out) files") +var files = flag.String("files", "", "consider only Go test files matching this regular expression") const dataDir = "testdata" @@ -66,14 +68,26 @@ type bundle struct { } func test(t *testing.T, mode Mode) { - // get all packages + // determine file filter + filter := isGoFile + if *files != "" { + rx, err := regexp.Compile(*files) + if err != nil { + t.Fatal(err) + } + filter = func(fi os.FileInfo) bool { + return isGoFile(fi) && rx.MatchString(fi.Name()) + } + } + + // get packages fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, dataDir, isGoFile, parser.ParseComments) + pkgs, err := parser.ParseDir(fset, dataDir, filter, parser.ParseComments) if err != nil { t.Fatal(err) } - // test all packages + // test packages for _, pkg := range pkgs { importpath := dataDir + "/" + pkg.Name doc := New(pkg, importpath, mode) diff --git a/libgo/go/go/doc/example.go b/libgo/go/go/doc/example.go index d5b58d26643..1c23b0d95c3 100644 --- a/libgo/go/go/doc/example.go +++ b/libgo/go/go/doc/example.go @@ -9,6 +9,7 @@ package doc import ( "go/ast" "go/printer" + "go/token" "strings" "unicode" "unicode/utf8" @@ -21,28 +22,47 @@ type Example struct { } func Examples(pkg *ast.Package) []*Example { - var examples []*Example - for _, src := range pkg.Files { - for _, decl := range src.Decls { + var list []*Example + for _, file := range pkg.Files { + hasTests := false // file contains tests or benchmarks + numDecl := 0 // number of non-import declarations in the file + var flist []*Example + for _, decl := range file.Decls { + if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT { + numDecl++ + continue + } f, ok := decl.(*ast.FuncDecl) if !ok { continue } + numDecl++ name := f.Name.Name + if isTest(name, "Test") || isTest(name, "Benchmark") { + hasTests = true + continue + } if !isTest(name, "Example") { continue } - examples = append(examples, &Example{ + flist = append(flist, &Example{ Name: name[len("Example"):], Body: &printer.CommentedNode{ Node: f.Body, - Comments: src.Comments, + Comments: file.Comments, }, Output: f.Doc.Text(), }) } + if !hasTests && numDecl > 1 && len(flist) == 1 { + // If this file only has one example function, some + // other top-level declarations, and no tests or + // benchmarks, use the whole file as the example. + flist[0].Body.Node = file + } + list = append(list, flist...) } - return examples + return list } // isTest tells whether name looks like a test, example, or benchmark. diff --git a/libgo/go/go/doc/exports.go b/libgo/go/go/doc/exports.go index 68dd3841bed..146be5d8707 100644 --- a/libgo/go/go/doc/exports.go +++ b/libgo/go/go/doc/exports.go @@ -22,12 +22,38 @@ func filterIdentList(list []*ast.Ident) []*ast.Ident { return list[0:j] } +// 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. +// +func removeErrorField(ityp *ast.InterfaceType) { + list := ityp.Methods.List // we know that ityp.Methods != nil + j := 0 + for _, field := range list { + keepField := true + if n := len(field.Names); n == 0 { + // anonymous field + if fname, _ := baseTypeName(field.Type); fname == "error" { + keepField = false + } + } + if keepField { + list[j] = field + j++ + } + } + if j < len(list) { + ityp.Incomplete = true + } + ityp.Methods.List = list[0:j] +} + // filterFieldList removes unexported fields (field names) from the field list // in place and returns true if fields were removed. Anonymous fields are // recorded with the parent type. filterType is called with the types of // all remaining fields. // -func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList) (removedFields bool) { +func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) { if fields == nil { return } @@ -37,9 +63,15 @@ func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList) (remo keepField := false if n := len(field.Names); n == 0 { // anonymous field - name := r.recordAnonymousField(parent, field.Type) - if ast.IsExported(name) { + fname := r.recordAnonymousField(parent, field.Type) + if ast.IsExported(fname) { + keepField = true + } else if ityp != nil && fname == "error" { + // possibly the predeclared error interface; keep + // it for now but remember this interface so that + // it can be fixed if error is also defined locally keepField = true + r.remember(ityp) } } else { field.Names = filterIdentList(field.Names) @@ -86,14 +118,14 @@ func (r *reader) filterType(parent *namedType, typ ast.Expr) { case *ast.ArrayType: r.filterType(nil, t.Elt) case *ast.StructType: - if r.filterFieldList(parent, t.Fields) { + if r.filterFieldList(parent, t.Fields, nil) { t.Incomplete = true } case *ast.FuncType: r.filterParamList(t.Params) r.filterParamList(t.Results) case *ast.InterfaceType: - if r.filterFieldList(parent, t.Methods) { + if r.filterFieldList(parent, t.Methods, t) { t.Incomplete = true } case *ast.MapType: @@ -116,9 +148,12 @@ func (r *reader) filterSpec(spec ast.Spec) bool { return true } case *ast.TypeSpec: - if ast.IsExported(s.Name.Name) { + if name := s.Name.Name; ast.IsExported(name) { r.filterType(r.lookupType(s.Name.Name), s.Type) return true + } else if name == "error" { + // special case: remember that error is declared locally + r.errorDecl = true } } return false diff --git a/libgo/go/go/doc/headscan.go b/libgo/go/go/doc/headscan.go index 37486b126fd..f5593476382 100644 --- a/libgo/go/go/doc/headscan.go +++ b/libgo/go/go/doc/headscan.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build ignore + /* The headscan command extracts comment headings from package files; it is used to detect false positives which may require an adjustment diff --git a/libgo/go/go/doc/reader.go b/libgo/go/go/doc/reader.go index dcf49f68fd3..bdfb294adb0 100644 --- a/libgo/go/go/doc/reader.go +++ b/libgo/go/go/doc/reader.go @@ -17,7 +17,7 @@ import ( // // Internally, we treat functions like methods and collect them in method sets. -// methodSet describes a set of methods. Entries where Decl == nil are conflict +// A methodSet describes a set of methods. Entries where Decl == nil are conflict // entries (more then one method with the same name at the same embedding level). // type methodSet map[string]*Func @@ -110,6 +110,9 @@ func baseTypeName(x ast.Expr) (name string, imported bool) { return } +// An embeddedSet describes a set of embedded types. +type embeddedSet map[*namedType]bool + // A namedType represents a named unqualified (package local, or possibly // predeclared) type. The namedType for a type name is always found via // reader.lookupType. @@ -119,9 +122,9 @@ type namedType struct { name string // type name decl *ast.GenDecl // nil if declaration hasn't been seen yet - isEmbedded bool // true if this type is embedded - isStruct bool // true if this type is a struct - embedded map[*namedType]bool // true if the embedded type is a pointer + isEmbedded bool // true if this type is embedded + isStruct bool // true if this type is a struct + embedded embeddedSet // true if the embedded type is a pointer // associated declarations values []*Value // consts and vars @@ -152,6 +155,10 @@ type reader struct { values []*Value // consts and vars types map[string]*namedType funcs methodSet + + // support for package-local error type declarations + errorDecl bool // if set, type "error" was declared locally + fixlist []*ast.InterfaceType // list of interfaces containing anonymous field "error" } func (r *reader) isVisible(name string) bool { @@ -173,7 +180,7 @@ func (r *reader) lookupType(name string) *namedType { // type not found - add one without declaration typ := &namedType{ name: name, - embedded: make(map[*namedType]bool), + embedded: make(embeddedSet), funcs: make(methodSet), methods: make(methodSet), } @@ -210,6 +217,10 @@ func (r *reader) readDoc(comment *ast.CommentGroup) { r.doc += "\n" + text } +func (r *reader) remember(typ *ast.InterfaceType) { + r.fixlist = append(r.fixlist, typ) +} + func specNames(specs []ast.Spec) []string { names := make([]string, 0, len(specs)) // reasonable estimate for _, s := range specs { @@ -274,7 +285,7 @@ func (r *reader) readValue(decl *ast.GenDecl) { // determine values list with which to associate the Value for this decl values := &r.values const threshold = 0.75 - if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) { + if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) { // typed entries are sufficiently frequent if typ := r.lookupType(domName); typ != nil { values = &typ.values // associate with that type @@ -315,7 +326,7 @@ func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) { return // no name or blank name - ignore the type } - // A type should be added at most once, so info.decl + // A type should be added at most once, so typ.decl // should be nil - if it is not, simply overwrite it. typ.decl = decl @@ -543,7 +554,8 @@ func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) // collectEmbeddedMethods collects the embedded methods of typ in mset. // -func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int) { +func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) { + visited[typ] = true for embedded, isPtr := range typ.embedded { // Once an embedded type is embedded as a pointer type // all embedded types in those types are treated like @@ -557,8 +569,11 @@ func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvType mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level)) } } - r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1) + if !visited[embedded] { + r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited) + } } + delete(visited, typ) } // computeMethodSets determines the actual method sets for each type encountered. @@ -568,12 +583,19 @@ func (r *reader) computeMethodSets() { // collect embedded methods for t if t.isStruct { // struct - r.collectEmbeddedMethods(t.methods, t, t.name, false, 1) + r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet)) } else { // interface // TODO(gri) fix this } } + + // if error was declared locally, don't treat it as exported field anymore + if r.errorDecl { + for _, ityp := range r.fixlist { + removeErrorField(ityp) + } + } } // cleanupTypes removes the association of functions and methods with diff --git a/libgo/go/go/doc/testdata/b.0.golden b/libgo/go/go/doc/testdata/b.0.golden index 7c33300616d..9d93392eaa5 100644 --- a/libgo/go/go/doc/testdata/b.0.golden +++ b/libgo/go/go/doc/testdata/b.0.golden @@ -12,18 +12,46 @@ FILENAMES CONSTANTS // + const ( + C1 notExported = iota + C2 + + C4 + C5 + ) + + // + const C notExported = 0 + + // const Pi = 3.14 // Pi VARIABLES // + var ( + U1, U2, U4, U5 notExported + + U7 notExported = 7 + ) + + // var MaxInt int // MaxInt + // + var V notExported + + // + var V1, V2, V4, V5 notExported + FUNCTIONS // func F(x int) int + // + func F1() notExported + // Always under the package functions list. func NotAFactory() int diff --git a/libgo/go/go/doc/testdata/b.1.golden b/libgo/go/go/doc/testdata/b.1.golden index f30380516bd..66c47b5c2a7 100644 --- a/libgo/go/go/doc/testdata/b.1.golden +++ b/libgo/go/go/doc/testdata/b.1.golden @@ -38,8 +38,42 @@ TYPES // func (x *T) M() + // + type notExported int + + // + const ( + C1 notExported = iota + C2 + c3 + C4 + C5 + ) + + // + const C notExported = 0 + + // + var ( + U1, U2, u3, U4, U5 notExported + u6 notExported + U7 notExported = 7 + ) + + // + var V notExported + + // + var V1, V2, v3, V4, V5 notExported + + // + func F1() notExported + + // + func f2() notExported + // Should only appear if AllDecls is set. - type uint struct{} + type uint struct{} // overrides a predeclared type uint // Associated with uint type if AllDecls is set. func UintFactory() uint diff --git a/libgo/go/go/doc/testdata/b.2.golden b/libgo/go/go/doc/testdata/b.2.golden index 7c33300616d..9d93392eaa5 100644 --- a/libgo/go/go/doc/testdata/b.2.golden +++ b/libgo/go/go/doc/testdata/b.2.golden @@ -12,18 +12,46 @@ FILENAMES CONSTANTS // + const ( + C1 notExported = iota + C2 + + C4 + C5 + ) + + // + const C notExported = 0 + + // const Pi = 3.14 // Pi VARIABLES // + var ( + U1, U2, U4, U5 notExported + + U7 notExported = 7 + ) + + // var MaxInt int // MaxInt + // + var V notExported + + // + var V1, V2, V4, V5 notExported + FUNCTIONS // func F(x int) int + // + func F1() notExported + // Always under the package functions list. func NotAFactory() int diff --git a/libgo/go/go/doc/testdata/b.go b/libgo/go/go/doc/testdata/b.go index 28660f9be7c..e50663b3dfa 100644 --- a/libgo/go/go/doc/testdata/b.go +++ b/libgo/go/go/doc/testdata/b.go @@ -6,6 +6,7 @@ package b import "a" +// ---------------------------------------------------------------------------- // Basic declarations const Pi = 3.14 // Pi @@ -28,3 +29,30 @@ func uintFactory() uint {} // Should only appear if AllDecls is set. type uint struct{} // overrides a predeclared type uint + +// ---------------------------------------------------------------------------- +// Exported declarations associated with non-exported types must always be shown. + +type notExported int + +const C notExported = 0 + +const ( + C1 notExported = iota + C2 + c3 + C4 + C5 +) + +var V notExported +var V1, V2, v3, V4, V5 notExported + +var ( + U1, U2, u3, U4, U5 notExported + u6 notExported + U7 notExported = 7 +) + +func F1() notExported {} +func f2() notExported {} diff --git a/libgo/go/go/doc/testdata/e.0.golden b/libgo/go/go/doc/testdata/e.0.golden index 096a50ff41f..6987e5867cf 100644 --- a/libgo/go/go/doc/testdata/e.0.golden +++ b/libgo/go/go/doc/testdata/e.0.golden @@ -40,3 +40,70 @@ TYPES T4 } + // + type U1 struct { + *U1 + } + + // U1.M should appear as method of U1. + func (*U1) M() + + // + type U2 struct { + *U3 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (*U2) M() + + // + type U3 struct { + *U2 + } + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (*U3) N() + + // + type U4 struct { + // contains filtered or unexported fields + } + + // U4.M should appear as method of U4. + func (*U4) M() + + // + type V1 struct { + *V2 + *V5 + } + + // + type V2 struct { + *V3 + } + + // + type V3 struct { + *V4 + } + + // + type V4 struct { + *V5 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (*V4) M() + + // + type V5 struct { + *V6 + } + + // + type V6 struct{} + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (*V6) M() + diff --git a/libgo/go/go/doc/testdata/e.1.golden b/libgo/go/go/doc/testdata/e.1.golden index 28be74a1fd6..cbe22e0bf63 100644 --- a/libgo/go/go/doc/testdata/e.1.golden +++ b/libgo/go/go/doc/testdata/e.1.golden @@ -43,6 +43,73 @@ TYPES } // + type U1 struct { + *U1 + } + + // U1.M should appear as method of U1. + func (*U1) M() + + // + type U2 struct { + *U3 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (*U2) M() + + // + type U3 struct { + *U2 + } + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (*U3) N() + + // + type U4 struct { + *u5 + } + + // U4.M should appear as method of U4. + func (*U4) M() + + // + type V1 struct { + *V2 + *V5 + } + + // + type V2 struct { + *V3 + } + + // + type V3 struct { + *V4 + } + + // + type V4 struct { + *V5 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (*V4) M() + + // + type V5 struct { + *V6 + } + + // + type V6 struct{} + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (*V6) M() + + // type t1 struct{} // t1.M should not appear as method in a Tx type. @@ -70,3 +137,8 @@ TYPES // t2.M should not appear as method in a Tx type. func (t2e) M() + // + type u5 struct { + *U4 + } + diff --git a/libgo/go/go/doc/testdata/e.2.golden b/libgo/go/go/doc/testdata/e.2.golden index f9a2b816774..e7b05e80faf 100644 --- a/libgo/go/go/doc/testdata/e.2.golden +++ b/libgo/go/go/doc/testdata/e.2.golden @@ -43,3 +43,88 @@ TYPES // T4.M should appear as method of T5 only if AllMethods is set. func (*T5) M() + // + type U1 struct { + *U1 + } + + // U1.M should appear as method of U1. + func (*U1) M() + + // + type U2 struct { + *U3 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (*U2) M() + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (U2) N() + + // + type U3 struct { + *U2 + } + + // U2.M should appear as method of U2 and as method of U3 only if ... + func (U3) M() + + // U3.N should appear as method of U3 and as method of U2 only if ... + func (*U3) N() + + // + type U4 struct { + // contains filtered or unexported fields + } + + // U4.M should appear as method of U4. + func (*U4) M() + + // + type V1 struct { + *V2 + *V5 + } + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (V1) M() + + // + type V2 struct { + *V3 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (V2) M() + + // + type V3 struct { + *V4 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (V3) M() + + // + type V4 struct { + *V5 + } + + // V4.M should appear as method of V2 and V3 if AllMethods is set. + func (*V4) M() + + // + type V5 struct { + *V6 + } + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (V5) M() + + // + type V6 struct{} + + // V6.M should appear as method of V1 and V5 if AllMethods is set. + func (*V6) M() + diff --git a/libgo/go/go/doc/testdata/e.go b/libgo/go/go/doc/testdata/e.go index 526a91f4f00..19dd138cf40 100644 --- a/libgo/go/go/doc/testdata/e.go +++ b/libgo/go/go/doc/testdata/e.go @@ -1,4 +1,4 @@ -// Copyright 2011 The Go Authors. All rights reserved. +// 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. @@ -77,3 +77,71 @@ func (*T4) M() {} type T5 struct { T4 } + +// ---------------------------------------------------------------------------- +// Recursive type declarations must not lead to endless recursion. + +type U1 struct { + *U1 +} + +// U1.M should appear as method of U1. +func (*U1) M() {} + +type U2 struct { + *U3 +} + +// U2.M should appear as method of U2 and as method of U3 only if AllMethods is set. +func (*U2) M() {} + +type U3 struct { + *U2 +} + +// U3.N should appear as method of U3 and as method of U2 only if AllMethods is set. +func (*U3) N() {} + +type U4 struct { + *u5 +} + +// U4.M should appear as method of U4. +func (*U4) M() {} + +type u5 struct { + *U4 +} + +// ---------------------------------------------------------------------------- +// A higher-level embedded type (and its methods) wins over the same type (and +// its methods) embedded at a lower level. + +type V1 struct { + *V2 + *V5 +} + +type V2 struct { + *V3 +} + +type V3 struct { + *V4 +} + +type V4 struct { + *V5 +} + +type V5 struct { + *V6 +} + +type V6 struct{} + +// V4.M should appear as method of V2 and V3 if AllMethods is set. +func (*V4) M() {} + +// V6.M should appear as method of V1 and V5 if AllMethods is set. +func (*V6) M() {} diff --git a/libgo/go/go/doc/testdata/error1.0.golden b/libgo/go/go/doc/testdata/error1.0.golden new file mode 100644 index 00000000000..6c6fe5d49bd --- /dev/null +++ b/libgo/go/go/doc/testdata/error1.0.golden @@ -0,0 +1,30 @@ +// +PACKAGE error1 + +IMPORTPATH + testdata/error1 + +FILENAMES + testdata/error1.go + +TYPES + // + type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // error should be visible + error + } + } + diff --git a/libgo/go/go/doc/testdata/error1.1.golden b/libgo/go/go/doc/testdata/error1.1.golden new file mode 100644 index 00000000000..a8dc2e71dc3 --- /dev/null +++ b/libgo/go/go/doc/testdata/error1.1.golden @@ -0,0 +1,32 @@ +// +PACKAGE error1 + +IMPORTPATH + testdata/error1 + +FILENAMES + testdata/error1.go + +TYPES + // + type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error + } + + // + type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error + } + + // + type T0 struct { + ExportedField interface { + // error should be visible + error + } + } + diff --git a/libgo/go/go/doc/testdata/error1.2.golden b/libgo/go/go/doc/testdata/error1.2.golden new file mode 100644 index 00000000000..6c6fe5d49bd --- /dev/null +++ b/libgo/go/go/doc/testdata/error1.2.golden @@ -0,0 +1,30 @@ +// +PACKAGE error1 + +IMPORTPATH + testdata/error1 + +FILENAMES + testdata/error1.go + +TYPES + // + type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // error should be visible + error + } + } + diff --git a/libgo/go/go/doc/testdata/error1.go b/libgo/go/go/doc/testdata/error1.go new file mode 100644 index 00000000000..3c777a78005 --- /dev/null +++ b/libgo/go/go/doc/testdata/error1.go @@ -0,0 +1,24 @@ +// 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 error1 + +type I0 interface { + // When embedded, the predeclared error interface + // must remain visible in interface types. + error +} + +type T0 struct { + ExportedField interface { + // error should be visible + error + } +} + +type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error +} diff --git a/libgo/go/go/doc/testdata/error2.0.golden b/libgo/go/go/doc/testdata/error2.0.golden new file mode 100644 index 00000000000..dedfe412a0f --- /dev/null +++ b/libgo/go/go/doc/testdata/error2.0.golden @@ -0,0 +1,27 @@ +// +PACKAGE error2 + +IMPORTPATH + testdata/error2 + +FILENAMES + testdata/error2.go + +TYPES + // + type I0 interface { + // contains filtered or unexported methods + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // contains filtered or unexported methods + } + } + diff --git a/libgo/go/go/doc/testdata/error2.1.golden b/libgo/go/go/doc/testdata/error2.1.golden new file mode 100644 index 00000000000..776bd1b3e40 --- /dev/null +++ b/libgo/go/go/doc/testdata/error2.1.golden @@ -0,0 +1,37 @@ +// +PACKAGE error2 + +IMPORTPATH + testdata/error2 + +FILENAMES + testdata/error2.go + +TYPES + // + type I0 interface { + // When embedded, the the locally declared error interface + // is only visible if all declarations are shown. + error + } + + // + type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error + } + + // + type T0 struct { + ExportedField interface { + // error should not be visible + error + } + } + + // This error declaration shadows the predeclared error type. + type error interface { + Error() string + } + diff --git a/libgo/go/go/doc/testdata/error2.2.golden b/libgo/go/go/doc/testdata/error2.2.golden new file mode 100644 index 00000000000..dedfe412a0f --- /dev/null +++ b/libgo/go/go/doc/testdata/error2.2.golden @@ -0,0 +1,27 @@ +// +PACKAGE error2 + +IMPORTPATH + testdata/error2 + +FILENAMES + testdata/error2.go + +TYPES + // + type I0 interface { + // contains filtered or unexported methods + } + + // + type S0 struct { + // contains filtered or unexported fields + } + + // + type T0 struct { + ExportedField interface { + // contains filtered or unexported methods + } + } + diff --git a/libgo/go/go/doc/testdata/error2.go b/libgo/go/go/doc/testdata/error2.go new file mode 100644 index 00000000000..6cc36feef3e --- /dev/null +++ b/libgo/go/go/doc/testdata/error2.go @@ -0,0 +1,29 @@ +// 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 error2 + +type I0 interface { + // When embedded, the the locally declared error interface + // is only visible if all declarations are shown. + error +} + +type T0 struct { + ExportedField interface { + // error should not be visible + error + } +} + +type S0 struct { + // In struct types, an embedded error must only be visible + // if AllDecls is set. + error +} + +// This error declaration shadows the predeclared error type. +type error interface { + Error() string +} diff --git a/libgo/go/go/doc/testdata/f.go b/libgo/go/go/doc/testdata/f.go index a3051e1fb3b..7e9add90784 100644 --- a/libgo/go/go/doc/testdata/f.go +++ b/libgo/go/go/doc/testdata/f.go @@ -1,4 +1,4 @@ -// Copyright 2011 The Go Authors. All rights reserved. +// 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. diff --git a/libgo/go/go/parser/interface.go b/libgo/go/go/parser/interface.go index f1b4ce34d1a..5c203a7846e 100644 --- a/libgo/go/go/parser/interface.go +++ b/libgo/go/go/parser/interface.go @@ -80,13 +80,25 @@ const ( // are returned via a scanner.ErrorList which is sorted by file position. // func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) (*ast.File, error) { + // get source text, err := readSource(filename, src) if err != nil { return nil, err } + + // parse source var p parser p.init(fset, filename, text, mode) - return p.parseFile(), p.errors() + f := p.parseFile() + + // sort errors + if p.mode&SpuriousErrors == 0 { + p.errors.RemoveMultiples() + } else { + p.errors.Sort() + } + + return f, p.errors.Err() } // ParseDir calls ParseFile for the files in the directory specified by path and diff --git a/libgo/go/go/parser/parser.go b/libgo/go/go/parser/parser.go index 6bee8de9f65..6a0b61eb48c 100644 --- a/libgo/go/go/parser/parser.go +++ b/libgo/go/go/parser/parser.go @@ -18,8 +18,8 @@ import ( // The parser structure holds the parser's internal state. type parser struct { - file *token.File - scanner.ErrorVector + file *token.File + errors scanner.ErrorList scanner scanner.Scanner // Tracing/debugging @@ -58,7 +58,8 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod if mode&ParseComments != 0 { m = scanner.ScanComments } - p.scanner.Init(p.file, src, p, m) + eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } + p.scanner.Init(p.file, src, eh, m) p.mode = mode p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) @@ -74,14 +75,6 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod p.openLabelScope() } -func (p *parser) errors() error { - m := scanner.Sorted - if p.mode&SpuriousErrors == 0 { - m = scanner.NoMultiples - } - return p.GetError(m) -} - // ---------------------------------------------------------------------------- // Scoping support @@ -334,7 +327,7 @@ func (p *parser) next() { } func (p *parser) error(pos token.Pos, msg string) { - p.Error(p.file.Position(pos), msg) + p.errors.Add(p.file.Position(pos), msg) } func (p *parser) errorExpected(pos token.Pos, msg string) { @@ -342,7 +335,7 @@ func (p *parser) errorExpected(pos token.Pos, msg string) { if pos == p.pos { // the error happened at the current position; // make the error message more specific - if p.tok == token.SEMICOLON && p.lit[0] == '\n' { + if p.tok == token.SEMICOLON && p.lit == "\n" { msg += ", found newline" } else { msg += ", found '" + p.tok.String() + "'" @@ -363,6 +356,17 @@ func (p *parser) expect(tok token.Token) token.Pos { return 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 { + if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" { + p.error(p.pos, "missing ',' before newline in "+construct) + p.next() + } + return p.expect(tok) +} + func (p *parser) expectSemi() { if p.tok != token.RPAREN && p.tok != token.RBRACE { p.expect(token.SEMICOLON) @@ -1063,7 +1067,7 @@ func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { p.next() } p.exprLev-- - rparen := p.expect(token.RPAREN) + rparen := p.expectClosing(token.RPAREN, "argument list") return &ast.CallExpr{fun, lparen, list, ellipsis, rparen} } @@ -1118,7 +1122,7 @@ func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr { elts = p.parseElementList() } p.exprLev-- - rbrace := p.expect(token.RBRACE) + rbrace := p.expectClosing(token.RBRACE, "composite literal") return &ast.CompositeLit{typ, lbrace, elts, rbrace} } @@ -2123,7 +2127,7 @@ func (p *parser) parseFile() *ast.File { // Don't bother parsing the rest if we had errors already. // Likely not a Go source file at all. - if p.ErrorCount() == 0 && p.mode&PackageClauseOnly == 0 { + if p.errors.Len() == 0 && p.mode&PackageClauseOnly == 0 { // import decls for p.tok == token.IMPORT { decls = append(decls, p.parseGenDecl(token.IMPORT, parseImportSpec)) diff --git a/libgo/go/go/printer/performance_test.go b/libgo/go/go/printer/performance_test.go index dbd942292b5..0c6a4e71f13 100644 --- a/libgo/go/go/printer/performance_test.go +++ b/libgo/go/go/printer/performance_test.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // This file implements a simple printer performance benchmark: -// gotest -bench=BenchmarkPrint +// go test -bench=BenchmarkPrint package printer diff --git a/libgo/go/go/printer/printer.go b/libgo/go/go/printer/printer.go index fe99e675eb0..c9949205e8a 100644 --- a/libgo/go/go/printer/printer.go +++ b/libgo/go/go/printer/printer.go @@ -6,7 +6,6 @@ package printer import ( - "bytes" "fmt" "go/ast" "go/token" @@ -51,22 +50,22 @@ type printer struct { fset *token.FileSet // Current state - output bytes.Buffer // raw printer result + output []byte // raw printer result indent int // current indentation mode pmode // current printer mode impliedSemi bool // if set, a linebreak implies a semicolon lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace) wsbuf []whiteSpace // delayed white space - // The (possibly estimated) position in the generated output; - // in AST space (i.e., pos is set whenever a token position is - // known accurately, and updated dependending on what has been - // written). - pos token.Position - - // The value of pos immediately after the last item has been - // written using writeItem. - last token.Position + // Positions + // The out position differs from the pos position when the result + // formatting differs from the source formatting (in the amount of + // white space). If there's a difference and SourcePos is set in + // ConfigMode, //line comments are used in the output to restore + // original source positions for a reader. + pos token.Position // current position in AST (source) space + out token.Position // current position in output space + last token.Position // value of pos after calling writeString // The list of all source comments, in order of appearance. comments []*ast.CommentGroup // may be nil @@ -89,6 +88,8 @@ type printer struct { func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { p.Config = *cfg p.fset = fset + p.pos = token.Position{Line: 1, Column: 1} + p.out = token.Position{Line: 1, Column: 1} p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short p.nodeSizes = nodeSizes p.cachedPos = -1 @@ -151,41 +152,57 @@ func (p *printer) lineFor(pos token.Pos) int { return p.cachedLine } -// writeByte writes ch to p.output and updates p.pos. -func (p *printer) writeByte(ch byte) { - p.output.WriteByte(ch) - p.pos.Offset++ - p.pos.Column++ - - if ch == '\n' || ch == '\f' { - // write indentation - // use "hard" htabs - indentation columns - // must not be discarded by the tabwriter - const htabs = "\t\t\t\t\t\t\t\t" - j := p.indent - for j > len(htabs) { - p.output.WriteString(htabs) - j -= len(htabs) - } - p.output.WriteString(htabs[0:j]) +// atLineBegin emits a //line comment if necessary and prints indentation. +func (p *printer) atLineBegin(pos token.Position) { + // write a //line comment if necessary + if p.Config.Mode&SourcePos != 0 && pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { + p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation + p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) + p.output = append(p.output, tabwriter.Escape) + // p.out must match the //line comment + p.out.Filename = pos.Filename + p.out.Line = pos.Line + } - // update p.pos - p.pos.Line++ - p.pos.Offset += p.indent - p.pos.Column = 1 + p.indent + // write indentation + // use "hard" htabs - indentation columns + // must not be discarded by the tabwriter + for i := 0; i < p.indent; i++ { + p.output = append(p.output, '\t') } + + // update positions + i := p.indent + p.pos.Offset += i + p.pos.Column += i + p.out.Column += i } -// writeByteN writes ch n times to p.output and updates p.pos. -func (p *printer) writeByteN(ch byte, n int) { - for n > 0 { - p.writeByte(ch) - n-- +// writeByte writes ch n times to p.output and updates p.pos. +func (p *printer) writeByte(ch byte, n int) { + if p.out.Column == 1 { + p.atLineBegin(p.pos) + } + + for i := 0; i < n; i++ { + p.output = append(p.output, ch) + } + + // update positions + p.pos.Offset += n + if ch == '\n' || ch == '\f' { + p.pos.Line += n + p.out.Line += n + p.pos.Column = 1 + p.out.Column = 1 + return } + p.pos.Column += n + p.out.Column += n } -// writeString writes the string s to p.output and updates p.pos. -// If isLit is set, s is escaped w/ tabwriter.Escape characters +// writeString writes the string s to p.output and updates p.pos, p.out, +// and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters // to protect s from being interpreted by the tabwriter. // // Note: writeString is only used to write Go tokens, literals, and @@ -195,64 +212,69 @@ func (p *printer) writeByteN(ch byte, n int) { // avoids processing extra escape characters and reduces run time of the // printer benchmark by up to 10%. // -func (p *printer) writeString(s string, isLit bool) { +func (p *printer) writeString(pos token.Position, s string, isLit bool) { + if p.out.Column == 1 { + p.atLineBegin(pos) + } + + if pos.IsValid() { + // update p.pos (if pos is invalid, continue with existing p.pos) + // Note: Must do this after handling line beginnings because + // atLineBegin updates p.pos if there's indentation, but p.pos + // is the position of s. + p.pos = pos + // reset state if the file changed + // (used when printing merged ASTs of different files + // e.g., the result of ast.MergePackageFiles) + if p.last.IsValid() && p.last.Filename != pos.Filename { + p.indent = 0 + p.mode = 0 + p.wsbuf = p.wsbuf[0:0] + } + } + if isLit { // Protect s such that is passes through the tabwriter // unchanged. Note that valid Go programs cannot contain // tabwriter.Escape bytes since they do not appear in legal // UTF-8 sequences. - p.output.WriteByte(tabwriter.Escape) + p.output = append(p.output, tabwriter.Escape) } - p.output.WriteString(s) + if debug { + p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! + } + p.output = append(p.output, s...) - // update p.pos + // update positions nlines := 0 - column := p.pos.Column + len(s) + var li int // index of last newline; valid if nlines > 0 for i := 0; i < len(s); i++ { + // Go tokens cannot contain '\f' - no need to look for it if s[i] == '\n' { nlines++ - column = len(s) - i + li = i } } p.pos.Offset += len(s) - p.pos.Line += nlines - p.pos.Column = column + if nlines > 0 { + p.pos.Line += nlines + p.out.Line += nlines + c := len(s) - li + p.pos.Column = c + p.out.Column = c + } else { + p.pos.Column += len(s) + p.out.Column += len(s) + } if isLit { - p.output.WriteByte(tabwriter.Escape) + p.output = append(p.output, tabwriter.Escape) } -} -// writeItem writes data at position pos. data is the text corresponding to -// a single lexical token, but may also be comment text. pos is the actual -// (or at least very accurately estimated) position of the data in the original -// source text. writeItem updates p.last to the position immediately following -// the data. -// -func (p *printer) writeItem(pos token.Position, data string, isLit bool) { - if pos.IsValid() { - // continue with previous position if we don't have a valid pos - if p.last.IsValid() && p.last.Filename != pos.Filename { - // the file has changed - reset state - // (used when printing merged ASTs of different files - // e.g., the result of ast.MergePackageFiles) - p.indent = 0 - p.mode = 0 - p.wsbuf = p.wsbuf[0:0] - } - p.pos = pos - } - if debug { - // do not update p.pos - use write0 - fmt.Fprintf(&p.output, "/*%s*/", pos) - } - p.writeString(data, isLit) p.last = p.pos } -const linePrefix = "//line " - // writeCommentPrefix writes the whitespace before a comment. // If there is any pending whitespace, it consumes as much of // it as is likely to help position the comment nicely. @@ -262,14 +284,14 @@ const linePrefix = "//line " // next item is a keyword. // func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, isKeyword bool) { - if p.output.Len() == 0 { + if len(p.output) == 0 { // the comment is the first item to be printed - don't write any whitespace return } if pos.IsValid() && pos.Filename != p.last.Filename { // comment in a different file - separate with newlines - p.writeByteN('\f', maxNewlines) + p.writeByte('\f', maxNewlines) return } @@ -309,7 +331,7 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as // with a blank instead of a tab sep = ' ' } - p.writeByte(sep) + p.writeByte(sep, 1) } } else { @@ -373,23 +395,17 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *as } if n > 0 { - // turn off indent if we're about to print a line directive - indent := p.indent - if strings.HasPrefix(comment.Text, linePrefix) { - p.indent = 0 - } // use formfeeds to break columns before a comment; // this is analogous to using formfeeds to separate // individual lines of /*-style comments - p.writeByteN('\f', nlimit(n)) - p.indent = indent // restore indent + p.writeByte('\f', nlimit(n)) } } } // Split comment text into lines // (using strings.Split(text, "\n") is significantly slower for -// this specific purpose, as measured with: gotest -bench=Print) +// this specific purpose, as measured with: go test -bench=Print) func split(text string) []string { // count lines (comment text never ends in a newline) n := 1 @@ -564,30 +580,33 @@ func stripCommonPrefix(lines []string) { func (p *printer) writeComment(comment *ast.Comment) { text := comment.Text + pos := p.posFor(comment.Pos()) - if strings.HasPrefix(text, linePrefix) { - pos := strings.TrimSpace(text[len(linePrefix):]) - i := strings.LastIndex(pos, ":") - if i >= 0 { - // The line directive we are about to print changed - // the Filename and Line number used by go/token - // as it was reading the input originally. - // In order to match the original input, we have to - // update our own idea of the file and line number - // accordingly, after printing the directive. - file := pos[:i] - line, _ := strconv.Atoi(pos[i+1:]) - defer func() { - p.pos.Filename = file - p.pos.Line = line - p.pos.Column = 1 - }() + const linePrefix = "//line " + if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) { + // possibly a line directive + ldir := strings.TrimSpace(text[len(linePrefix):]) + if i := strings.LastIndex(ldir, ":"); i >= 0 { + if line, err := strconv.Atoi(ldir[i+1:]); err == nil && line > 0 { + // The line directive we are about to print changed + // the Filename and Line number used for subsequent + // tokens. We have to update our AST-space position + // accordingly and suspend indentation temporarily. + indent := p.indent + p.indent = 0 + defer func() { + p.pos.Filename = ldir[:i] + p.pos.Line = line + p.pos.Column = 1 + p.indent = indent + }() + } } } // shortcut common case of //-style comments if text[1] == '/' { - p.writeItem(p.posFor(comment.Pos()), text, true) + p.writeString(pos, text, true) return } @@ -598,14 +617,13 @@ func (p *printer) writeComment(comment *ast.Comment) { // write comment lines, separated by formfeed, // without a line break after the last line - pos := p.posFor(comment.Pos()) for i, line := range lines { if i > 0 { - p.writeByte('\f') + p.writeByte('\f', 1) pos = p.pos } if len(line) > 0 { - p.writeItem(pos, line, true) + p.writeString(pos, line, true) } } } @@ -643,7 +661,7 @@ func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, dropped // make sure we have a line break if needsLinebreak { - p.writeByte('\n') + p.writeByte('\n', 1) wroteNewline = true } @@ -671,7 +689,7 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro if last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line { // the last comment is a /*-style comment and the next item // follows on the same line: separate with an extra blank - p.writeByte(' ') + p.writeByte(' ', 1) } // ensure that there is a line break after a //-style comment, // before a closing '}' unless explicitly disabled, or at eof @@ -722,7 +740,7 @@ func (p *printer) writeWhitespace(n int) { } fallthrough default: - p.writeByte(byte(ch)) + p.writeByte(byte(ch), 1) } } @@ -886,12 +904,12 @@ func (p *printer) print(args ...interface{}) { if droppedFF { ch = '\f' // use formfeed since we dropped one before } - p.writeByteN(ch, n) + p.writeByte(ch, n) impliedSemi = false } } - p.writeItem(next, data, isLit) + p.writeString(next, data, isLit) p.impliedSemi = impliedSemi } } @@ -1027,7 +1045,7 @@ unsupported: type trimmer struct { output io.Writer state int - space bytes.Buffer + space []byte } // trimmer is implemented as a state machine. @@ -1038,6 +1056,11 @@ const ( inText // inside text ) +func (p *trimmer) resetSpace() { + p.state = inSpace + p.space = p.space[0:0] +} + // Design note: It is tempting to eliminate extra blanks occurring in // whitespace in this function as it could simplify some // of the blanks logic in the node printing functions. @@ -1062,36 +1085,33 @@ func (p *trimmer) Write(data []byte) (n int, err error) { case inSpace: switch b { case '\t', ' ': - p.space.WriteByte(b) // WriteByte returns no errors + p.space = append(p.space, b) case '\n', '\f': - p.space.Reset() // discard trailing space + p.resetSpace() // discard trailing space _, err = p.output.Write(aNewline) case tabwriter.Escape: - _, err = p.output.Write(p.space.Bytes()) + _, err = p.output.Write(p.space) p.state = inEscape m = n + 1 // +1: skip tabwriter.Escape default: - _, err = p.output.Write(p.space.Bytes()) + _, err = p.output.Write(p.space) p.state = inText m = n } case inEscape: if b == tabwriter.Escape { _, err = p.output.Write(data[m:n]) - p.state = inSpace - p.space.Reset() + p.resetSpace() } case inText: switch b { case '\t', ' ': _, err = p.output.Write(data[m:n]) - p.state = inSpace - p.space.Reset() - p.space.WriteByte(b) // WriteByte returns no errors + p.resetSpace() + p.space = append(p.space, b) case '\n', '\f': _, err = p.output.Write(data[m:n]) - p.state = inSpace - p.space.Reset() + p.resetSpace() _, err = p.output.Write(aNewline) case tabwriter.Escape: _, err = p.output.Write(data[m:n]) @@ -1110,8 +1130,7 @@ func (p *trimmer) Write(data []byte) (n int, err error) { switch p.state { case inEscape, inText: _, err = p.output.Write(data[m:n]) - p.state = inSpace - p.space.Reset() + p.resetSpace() } return @@ -1120,16 +1139,19 @@ func (p *trimmer) Write(data []byte) (n int, err error) { // ---------------------------------------------------------------------------- // Public interface -// General printing is controlled with these Config.Mode flags. +// A Mode value is a set of flags (or 0). They coontrol printing. +type Mode uint + const ( - RawFormat uint = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored + RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored TabIndent // use tabs for indentation independent of UseSpaces UseSpaces // use spaces instead of tabs for alignment + SourcePos // emit //line comments to preserve original source positions ) // A Config node controls the output of Fprint. type Config struct { - Mode uint // default: 0 + Mode Mode // default: 0 Tabwidth int // default: 8 } @@ -1170,7 +1192,7 @@ func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{ } // write printer result via tabwriter/trimmer to output - if _, err = output.Write(p.output.Bytes()); err != nil { + if _, err = output.Write(p.output); err != nil { return } diff --git a/libgo/go/go/printer/printer_test.go b/libgo/go/go/printer/printer_test.go index 9adf48cda61..fa133cd35f0 100644 --- a/libgo/go/go/printer/printer_test.go +++ b/libgo/go/go/printer/printer_test.go @@ -67,6 +67,13 @@ func runcheck(t *testing.T, source, golden string, mode checkMode) { } res := buf.Bytes() + // formatted source must be valid + if _, err := parser.ParseFile(fset, "", res, 0); err != nil { + t.Error(err) + t.Logf("\n%s", res) + return + } + // update golden files if necessary if *update { if err := ioutil.WriteFile(golden, res, 0644); err != nil { @@ -133,7 +140,7 @@ type entry struct { mode checkMode } -// Use gotest -update to create/update the respective golden files. +// Use go test -update to create/update the respective golden files. var data = []entry{ {"empty.input", "empty.golden", 0}, {"comments.input", "comments.golden", 0}, @@ -223,7 +230,8 @@ func TestBadNodes(t *testing.T) { } } -// Print and parse f with +// testComment verifies that f can be parsed again after printing it +// with its first comment set to comment at any possible source offset. func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { f.Comments[0].List[0] = comment var buf bytes.Buffer @@ -280,3 +288,128 @@ func fibo(n int) { testComment(t, f, len(src), &ast.Comment{pos, "/*-style \n comment */"}) testComment(t, f, len(src), &ast.Comment{pos, "/*-style comment \n\n\n */"}) } + +type visitor chan *ast.Ident + +func (v visitor) Visit(n ast.Node) (w ast.Visitor) { + if ident, ok := n.(*ast.Ident); ok { + v <- ident + } + return v +} + +// idents is an iterator that returns all idents in f via the result channel. +func idents(f *ast.File) <-chan *ast.Ident { + v := make(visitor) + go func() { + ast.Walk(v, f) + close(v) + }() + return v +} + +// identCount returns the number of identifiers found in f. +func identCount(f *ast.File) int { + n := 0 + for _ = range idents(f) { + n++ + } + return n +} + +// Verify that the SourcePos mode emits correct //line comments +// by testing that position information for matching identifiers +// is maintained. +func TestSourcePos(t *testing.T) { + const src = ` +package p +import ( "go/printer"; "math" ) +const pi = 3.14; var x = 0 +type t struct{ x, y, z int; u, v, w float32 } +func (t *t) foo(a, b, c int) int { + return a*t.x + b*t.y + + // two extra lines here + // ... + c*t.z +} +` + + // parse original + f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + // pretty-print original + var buf bytes.Buffer + err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) + if err != nil { + t.Fatal(err) + } + + // parse pretty printed original + // (//line comments must be interpreted even w/o parser.ParseComments set) + f2, err := parser.ParseFile(fset, "", buf.Bytes(), 0) + if err != nil { + t.Fatalf("%s\n%s", err, buf.Bytes()) + } + + // At this point the position information of identifiers in f2 should + // match the position information of corresponding identifiers in f1. + + // number of identifiers must be > 0 (test should run) and must match + n1 := identCount(f1) + n2 := identCount(f2) + if n1 == 0 { + t.Fatal("got no idents") + } + if n2 != n1 { + t.Errorf("got %d idents; want %d", n2, n1) + } + + // verify that all identifiers have correct line information + i2range := idents(f2) + for i1 := range idents(f1) { + i2 := <-i2range + + if i2.Name != i1.Name { + t.Errorf("got ident %s; want %s", i2.Name, i1.Name) + } + + l1 := fset.Position(i1.Pos()).Line + l2 := fset.Position(i2.Pos()).Line + if l2 != l1 { + t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) + } + } + + if t.Failed() { + t.Logf("\n%s", buf.Bytes()) + } +} + +// TextX is a skeleton test that can be filled in for debugging one-off cases. +// Do not remove. +func TestX(t *testing.T) { + const src = ` +package p +func _() {} +` + // parse original + f, err := parser.ParseFile(fset, "src", src, parser.ParseComments) + if err != nil { + t.Fatal(err) + } + + // pretty-print original + var buf bytes.Buffer + if err = (&Config{Mode: UseSpaces, Tabwidth: 8}).Fprint(&buf, fset, f); err != nil { + t.Fatal(err) + } + + // parse pretty printed original + if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { + t.Fatalf("%s\n%s", err, buf.Bytes()) + } + +} diff --git a/libgo/go/go/printer/testdata/comments.golden b/libgo/go/go/printer/testdata/comments.golden index d2ad9e3a2ff..e5826eecefe 100644 --- a/libgo/go/go/printer/testdata/comments.golden +++ b/libgo/go/go/printer/testdata/comments.golden @@ -404,7 +404,7 @@ func _() { */ } -// Some interesting interspersed comments +// Some interesting interspersed comments. func _( /* this */ x /* is */ /* an */ int) { } @@ -428,6 +428,26 @@ func _() { _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */ } } +// Test cases from issue 1542: +// Comments must not be placed before commas and cause invalid programs. +func _() { + var a = []int{1, 2 /*jasldf*/} + _ = a +} + +func _() { + var a = []int{1, 2}/*jasldf + */ + + _ = a +} + +func _() { + var a = []int{1, 2}// jasldf + + _ = a +} + // Comments immediately adjacent to punctuation (for which the go/printer // may only have estimated position information) must remain after the punctuation. func _() { @@ -459,6 +479,25 @@ func _() { } } +// Print line directives correctly. + +// The following is a legal line directive. +//line foo:1 +func _() { + _ = 0 + // The following is a legal line directive. It must not be indented: +//line foo:2 + _ = 1 + + // The following is not a legal line directive (it doesn't start in column 1): + //line foo:2 + _ = 2 + + // The following is not a legal line directive (negative line number): + //line foo:-3 + _ = 3 +} + // Line comments with tabs func _() { var finput *bufio.Reader // input file diff --git a/libgo/go/go/printer/testdata/comments.input b/libgo/go/go/printer/testdata/comments.input index 222e0a713d4..55f6b61f21f 100644 --- a/libgo/go/go/printer/testdata/comments.input +++ b/libgo/go/go/printer/testdata/comments.input @@ -410,7 +410,7 @@ func _() { } -// Some interesting interspersed comments +// Some interesting interspersed comments. func _(/* this */x/* is *//* an */ int) { } @@ -432,6 +432,26 @@ func _() { _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */} } +// Test cases from issue 1542: +// Comments must not be placed before commas and cause invalid programs. +func _() { + var a = []int{1, 2, /*jasldf*/ + } + _ = a +} + +func _() { + var a = []int{1, 2, /*jasldf + */ + } + _ = a +} + +func _() { + var a = []int{1, 2, // jasldf + } + _ = a +} // Comments immediately adjacent to punctuation (for which the go/printer // may only have estimated position information) must remain after the punctuation. @@ -467,6 +487,25 @@ func _() { } +// Print line directives correctly. + +// The following is a legal line directive. +//line foo:1 +func _() { + _ = 0 +// The following is a legal line directive. It must not be indented: +//line foo:2 + _ = 1 + +// The following is not a legal line directive (it doesn't start in column 1): + //line foo:2 + _ = 2 + +// The following is not a legal line directive (negative line number): +//line foo:-3 + _ = 3 +} + // Line comments with tabs func _() { var finput *bufio.Reader // input file diff --git a/libgo/go/go/scanner/errors.go b/libgo/go/go/scanner/errors.go index cd9620b878b..8a75a96508e 100644 --- a/libgo/go/go/scanner/errors.go +++ b/libgo/go/go/scanner/errors.go @@ -11,44 +11,18 @@ import ( "sort" ) -// An implementation of an ErrorHandler may be provided to the Scanner. -// If a syntax error is encountered and a handler was installed, Error -// is called with a position and an error message. The position points -// to the beginning of the offending token. -// -type ErrorHandler interface { - Error(pos token.Position, msg string) -} - -// ErrorVector implements the ErrorHandler interface. It maintains a list -// of errors which can be retrieved with GetErrorList and GetError. The -// zero value for an ErrorVector is an empty ErrorVector ready to use. -// -// A common usage pattern is to embed an ErrorVector alongside a -// scanner in a data structure that uses the scanner. By passing a -// reference to an ErrorVector to the scanner's Init call, default -// error handling is obtained. -// -type ErrorVector struct { - errors []*Error -} - -// Reset resets an ErrorVector to no errors. -func (h *ErrorVector) Reset() { h.errors = h.errors[:0] } - -// ErrorCount returns the number of errors collected. -func (h *ErrorVector) ErrorCount() int { return len(h.errors) } - -// Within ErrorVector, an error is represented by an Error node. The -// position Pos, if valid, points to the beginning of the offending -// token, and the error condition is described by Msg. +// In an ErrorList, an error is represented by an *Error. +// The position Pos, if valid, points to the beginning of +// the offending token, and the error condition is described +// by Msg. // type Error struct { Pos token.Position Msg string } -func (e *Error) Error() string { +// Error implements the error interface. +func (e Error) Error() string { if e.Pos.Filename != "" || e.Pos.IsValid() { // don't print "<unknown position>" // TODO(gri) reconsider the semantics of Position.IsValid @@ -57,9 +31,19 @@ func (e *Error) Error() string { return e.Msg } -// An ErrorList is a (possibly sorted) list of Errors. +// ErrorList is a list of *Errors. +// The zero value for an ErrorList is an empty ErrorList ready to use. +// type ErrorList []*Error +// Add adds an Error with given position and error message to an ErrorList. +func (p *ErrorList) Add(pos token.Position, msg string) { + *p = append(*p, &Error{pos, msg}) +} + +// Reset resets an ErrorList to no errors. +func (p *ErrorList) Reset() { *p = (*p)[0:0] } + // ErrorList implements the sort Interface. func (p ErrorList) Len() int { return len(p) } func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } @@ -84,72 +68,47 @@ func (p ErrorList) Less(i, j int) bool { return false } +// Sort sorts an ErrorList. *Error entries are sorted by position, +// other errors are sorted by error message, and before any *Error +// entry. +// +func (p ErrorList) Sort() { + sort.Sort(p) +} + +// RemoveMultiples sorts an ErrorList and removes all but the first error per line. +func (p *ErrorList) RemoveMultiples() { + sort.Sort(p) + var last token.Position // initial last.Line is != any legal error line + i := 0 + for _, e := range *p { + if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { + last = e.Pos + (*p)[i] = e + i++ + } + } + (*p) = (*p)[0:i] +} + +// An ErrorList implements the error interface. func (p ErrorList) Error() string { switch len(p) { case 0: - return "unspecified error" + return "no errors" case 1: return p[0].Error() } return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) } -// These constants control the construction of the ErrorList -// returned by GetErrors. -// -const ( - Raw = iota // leave error list unchanged - Sorted // sort error list by file, line, and column number - NoMultiples // sort error list and leave only the first error per line -) - -// GetErrorList returns the list of errors collected by an ErrorVector. -// The construction of the ErrorList returned is controlled by the mode -// parameter. If there are no errors, the result is nil. -// -func (h *ErrorVector) GetErrorList(mode int) ErrorList { - if len(h.errors) == 0 { - return nil - } - - list := make(ErrorList, len(h.errors)) - copy(list, h.errors) - - if mode >= Sorted { - sort.Sort(list) - } - - if mode >= NoMultiples { - var last token.Position // initial last.Line is != any legal error line - i := 0 - for _, e := range list { - if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { - last = e.Pos - list[i] = e - i++ - } - } - list = list[0:i] - } - - return list -} - -// GetError is like GetErrorList, but it returns an error instead -// so that a nil result can be assigned to an error variable and -// remains nil. -// -func (h *ErrorVector) GetError(mode int) error { - if len(h.errors) == 0 { +// Err returns an error equivalent to this error list. +// If the list is empty, Err returns nil. +func (p ErrorList) Err() error { + if len(p) == 0 { return nil } - - return h.GetErrorList(mode) -} - -// ErrorVector implements the ErrorHandler interface. -func (h *ErrorVector) Error(pos token.Position, msg string) { - h.errors = append(h.errors, &Error{pos, msg}) + return p } // PrintError is a utility function that prints a list of errors to w, diff --git a/libgo/go/go/scanner/scanner.go b/libgo/go/go/scanner/scanner.go index 0aabfe34c41..458e1f9f37a 100644 --- a/libgo/go/go/scanner/scanner.go +++ b/libgo/go/go/scanner/scanner.go @@ -30,6 +30,13 @@ import ( "unicode/utf8" ) +// An ErrorHandler may be provided to Scanner.Init. If a syntax error is +// encountered and a handler was installed, the handler is called with a +// position and an error message. The position points to the beginning of +// the offending token. +// +type ErrorHandler func(pos token.Position, msg string) + // A Scanner holds the scanner's internal state while processing // a given text. It can be allocated as part of another data // structure but must be initialized via Init before use. @@ -103,7 +110,7 @@ const ( // line information which is already present is ignored. Init causes a // panic if the file size does not match the src size. // -// Calls to Scan will use the error handler err if they encounter a +// Calls to Scan will invoke the error handler err if they encounter a // syntax error and err is not nil. Also, for each error encountered, // the Scanner field ErrorCount is incremented by one. The mode parameter // determines how comments are handled. @@ -134,7 +141,7 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode func (s *Scanner) error(offs int, msg string) { if s.err != nil { - s.err.Error(s.file.Position(s.file.Pos(offs)), msg) + s.err(s.file.Position(s.file.Pos(offs)), msg) } s.ErrorCount++ } diff --git a/libgo/go/go/scanner/scanner_test.go b/libgo/go/go/scanner/scanner_test.go index e7f7cd1c1e9..06223e23bd8 100644 --- a/libgo/go/go/scanner/scanner_test.go +++ b/libgo/go/go/scanner/scanner_test.go @@ -186,14 +186,6 @@ var source = func() []byte { return src }() -type testErrorHandler struct { - t *testing.T -} - -func (h *testErrorHandler) Error(pos token.Position, msg string) { - h.t.Errorf("Error() called (msg = %s)", msg) -} - func newlineCount(s string) int { n := 0 for i := 0; i < len(s); i++ { @@ -226,9 +218,14 @@ func TestScan(t *testing.T) { src_linecount := newlineCount(string(source)) whitespace_linecount := newlineCount(whitespace) + // error handler + eh := func(_ token.Position, msg string) { + t.Errorf("error handler called (msg = %s)", msg) + } + // verify scan var s Scanner - s.Init(fset.AddFile("", fset.Base(), len(source)), source, &testErrorHandler{t}, ScanComments|dontInsertSemis) + s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh, ScanComments|dontInsertSemis) index := 0 // epos is the expected position epos := token.Position{ @@ -569,36 +566,37 @@ func TestStdErrorHander(t *testing.T) { "//line File1:1\n" + "@ @ @" // original file, line 1 again - v := new(ErrorVector) + var list ErrorList + eh := func(pos token.Position, msg string) { list.Add(pos, msg) } + var s Scanner - s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), v, dontInsertSemis) + s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), eh, dontInsertSemis) for { if _, tok, _ := s.Scan(); tok == token.EOF { break } } - list := v.GetErrorList(Raw) + if len(list) != s.ErrorCount { + t.Errorf("found %d errors, expected %d", len(list), s.ErrorCount) + } + if len(list) != 9 { t.Errorf("found %d raw errors, expected 9", len(list)) PrintError(os.Stderr, list) } - list = v.GetErrorList(Sorted) + list.Sort() if len(list) != 9 { t.Errorf("found %d sorted errors, expected 9", len(list)) PrintError(os.Stderr, list) } - list = v.GetErrorList(NoMultiples) + list.RemoveMultiples() if len(list) != 4 { t.Errorf("found %d one-per-line errors, expected 4", len(list)) PrintError(os.Stderr, list) } - - if v.ErrorCount() != s.ErrorCount { - t.Errorf("found %d errors, expected %d", v.ErrorCount(), s.ErrorCount) - } } type errorCollector struct { @@ -607,16 +605,15 @@ type errorCollector struct { pos token.Position // last error position encountered } -func (h *errorCollector) Error(pos token.Position, msg string) { - h.cnt++ - h.msg = msg - h.pos = pos -} - func checkError(t *testing.T, src string, tok token.Token, pos int, err string) { var s Scanner var h errorCollector - s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), &h, ScanComments|dontInsertSemis) + eh := func(pos token.Position, msg string) { + h.cnt++ + h.msg = msg + h.pos = pos + } + s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), eh, ScanComments|dontInsertSemis) _, tok0, _ := s.Scan() _, tok1, _ := s.Scan() if tok0 != tok { |