diff options
Diffstat (limited to 'src/pkg/go')
41 files changed, 807 insertions, 321 deletions
diff --git a/src/pkg/go/ast/ast.go b/src/pkg/go/ast/ast.go index bf533d1d2..6e635cd01 100644 --- a/src/pkg/go/ast/ast.go +++ b/src/pkg/go/ast/ast.go @@ -297,6 +297,8 @@ type ( Lbrack token.Pos // position of "[" Low Expr // begin of slice range; or nil High Expr // end of slice range; or nil + Max Expr // maximum capacity of slice; or nil + Slice3 bool // true if 3-index slice (2 colons present) Rbrack token.Pos // position of "]" } @@ -304,8 +306,10 @@ type ( // type assertion. // TypeAssertExpr struct { - X Expr // expression - Type Expr // asserted type; nil means type switch X.(type) + X Expr // expression + Lparen token.Pos // position of "(" + Type Expr // asserted type; nil means type switch X.(type) + Rparen token.Pos // position of ")" } // A CallExpr node represents an expression followed by an argument list. @@ -385,8 +389,8 @@ type ( // A FuncType node represents a function type. FuncType struct { - Func token.Pos // position of "func" keyword - Params *FieldList // (incoming) parameters; or nil + Func token.Pos // position of "func" keyword (token.NoPos if there is no "func") + Params *FieldList // (incoming) parameters; non-nil Results *FieldList // (outgoing) results; or nil } @@ -407,7 +411,7 @@ type ( // A ChanType node represents a channel type. ChanType struct { Begin token.Pos // position of "chan" keyword or "<-" (whichever comes first) - Arrow token.Pos // position of "<-" (noPos if there is no "<-") + Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-") Dir ChanDir // channel direction Value Expr // value type } @@ -438,10 +442,15 @@ func (x *BinaryExpr) Pos() token.Pos { return x.X.Pos() } func (x *KeyValueExpr) Pos() token.Pos { return x.Key.Pos() } func (x *ArrayType) Pos() token.Pos { return x.Lbrack } func (x *StructType) Pos() token.Pos { return x.Struct } -func (x *FuncType) Pos() token.Pos { return x.Func } -func (x *InterfaceType) Pos() token.Pos { return x.Interface } -func (x *MapType) Pos() token.Pos { return x.Map } -func (x *ChanType) Pos() token.Pos { return x.Begin } +func (x *FuncType) Pos() token.Pos { + if x.Func.IsValid() || x.Params == nil { // see issue 3870 + return x.Func + } + return x.Params.Pos() // interface method declarations have no "func" keyword +} +func (x *InterfaceType) Pos() token.Pos { return x.Interface } +func (x *MapType) Pos() token.Pos { return x.Map } +func (x *ChanType) Pos() token.Pos { return x.Begin } func (x *BadExpr) End() token.Pos { return x.To } func (x *Ident) End() token.Pos { return token.Pos(int(x.NamePos) + len(x.Name)) } @@ -451,26 +460,21 @@ func (x *Ellipsis) End() token.Pos { } return x.Ellipsis + 3 // len("...") } -func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) } -func (x *FuncLit) End() token.Pos { return x.Body.End() } -func (x *CompositeLit) End() token.Pos { return x.Rbrace + 1 } -func (x *ParenExpr) End() token.Pos { return x.Rparen + 1 } -func (x *SelectorExpr) End() token.Pos { return x.Sel.End() } -func (x *IndexExpr) End() token.Pos { return x.Rbrack + 1 } -func (x *SliceExpr) End() token.Pos { return x.Rbrack + 1 } -func (x *TypeAssertExpr) End() token.Pos { - if x.Type != nil { - return x.Type.End() - } - return x.X.End() -} -func (x *CallExpr) End() token.Pos { return x.Rparen + 1 } -func (x *StarExpr) End() token.Pos { return x.X.End() } -func (x *UnaryExpr) End() token.Pos { return x.X.End() } -func (x *BinaryExpr) End() token.Pos { return x.Y.End() } -func (x *KeyValueExpr) End() token.Pos { return x.Value.End() } -func (x *ArrayType) End() token.Pos { return x.Elt.End() } -func (x *StructType) End() token.Pos { return x.Fields.End() } +func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) } +func (x *FuncLit) End() token.Pos { return x.Body.End() } +func (x *CompositeLit) End() token.Pos { return x.Rbrace + 1 } +func (x *ParenExpr) End() token.Pos { return x.Rparen + 1 } +func (x *SelectorExpr) End() token.Pos { return x.Sel.End() } +func (x *IndexExpr) End() token.Pos { return x.Rbrack + 1 } +func (x *SliceExpr) End() token.Pos { return x.Rbrack + 1 } +func (x *TypeAssertExpr) End() token.Pos { return x.Rparen + 1 } +func (x *CallExpr) End() token.Pos { return x.Rparen + 1 } +func (x *StarExpr) End() token.Pos { return x.X.End() } +func (x *UnaryExpr) End() token.Pos { return x.X.End() } +func (x *BinaryExpr) End() token.Pos { return x.Y.End() } +func (x *KeyValueExpr) End() token.Pos { return x.Value.End() } +func (x *ArrayType) End() token.Pos { return x.Elt.End() } +func (x *StructType) End() token.Pos { return x.Fields.End() } func (x *FuncType) End() token.Pos { if x.Results != nil { return x.Results.End() @@ -511,23 +515,21 @@ func (*ChanType) exprNode() {} // ---------------------------------------------------------------------------- // Convenience functions for Idents -var noPos token.Pos - // NewIdent creates a new Ident without position. // Useful for ASTs generated by code other than the Go parser. // -func NewIdent(name string) *Ident { return &Ident{noPos, name, nil} } +func NewIdent(name string) *Ident { return &Ident{token.NoPos, name, nil} } -// IsExported returns whether name is an exported Go symbol -// (i.e., whether it begins with an uppercase letter). +// IsExported reports whether name is an exported Go symbol +// (that is, whether it begins with an upper-case letter). // func IsExported(name string) bool { ch, _ := utf8.DecodeRuneInString(name) return unicode.IsUpper(ch) } -// IsExported returns whether id is an exported Go symbol -// (i.e., whether it begins with an uppercase letter). +// IsExported reports whether id is an exported Go symbol +// (that is, whether it begins with an uppercase letter). // func (id *Ident) IsExported() bool { return IsExported(id.Name) } @@ -919,7 +921,7 @@ type ( Doc *CommentGroup // associated documentation; or nil Recv *FieldList // receiver (methods); or nil (functions) Name *Ident // function/method name - Type *FuncType // position of Func keyword, parameters and results + Type *FuncType // function signature: parameters, results, and position of "func" keyword Body *BlockStmt // function body; or nil (forward declaration) } ) diff --git a/src/pkg/go/ast/commentmap.go b/src/pkg/go/ast/commentmap.go index 252d460af..1fb4867dd 100644 --- a/src/pkg/go/ast/commentmap.go +++ b/src/pkg/go/ast/commentmap.go @@ -129,11 +129,11 @@ func (s *nodeStack) pop(pos token.Pos) (top Node) { // // A comment group g is associated with a node n if: // -// - g starts on the same line as n ends -// - g starts on the line immediately following n, and there is -// at least one empty line after g and before the next node -// - g starts before n and is not associated to the node before n -// via the previous rules +// - g starts on the same line as n ends +// - g starts on the line immediately following n, and there is +// at least one empty line after g and before the next node +// - g starts before n and is not associated to the node before n +// via the previous rules // // NewCommentMap tries to associate a comment group to the "largest" // node possible: For instance, if the comment is a line comment diff --git a/src/pkg/go/ast/filter.go b/src/pkg/go/ast/filter.go index 71c9ed776..fc3eeb4a1 100644 --- a/src/pkg/go/ast/filter.go +++ b/src/pkg/go/ast/filter.go @@ -308,7 +308,7 @@ func nameOf(f *FuncDecl) string { // separator is an empty //-style comment that is interspersed between // different comment groups when they are concatenated into a single group // -var separator = &Comment{noPos, "//"} +var separator = &Comment{token.NoPos, "//"} // MergePackageFiles creates a file AST by merging the ASTs of the // files belonging to a package. The mode flags control merging behavior. diff --git a/src/pkg/go/ast/import.go b/src/pkg/go/ast/import.go index a68a4840f..d2770d16c 100644 --- a/src/pkg/go/ast/import.go +++ b/src/pkg/go/ast/import.go @@ -11,6 +11,7 @@ import ( ) // SortImports sorts runs of consecutive import lines in import blocks in f. +// It also removes duplicate imports when it is possible to do so without data loss. func SortImports(fset *token.FileSet, f *File) { for _, d := range f.Decls { d, ok := d.(*GenDecl) @@ -27,14 +28,25 @@ func SortImports(fset *token.FileSet, f *File) { // Identify and sort runs of specs on successive lines. i := 0 + specs := d.Specs[:0] for j, s := range d.Specs { if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { // j begins a new run. End this one. - sortSpecs(fset, f, d.Specs[i:j]) + specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) i = j } } - sortSpecs(fset, f, d.Specs[i:]) + specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) + d.Specs = specs + + // Deduping can leave a blank line before the rparen; clean that up. + if len(d.Specs) > 0 { + lastSpec := d.Specs[len(d.Specs)-1] + lastLine := fset.Position(lastSpec.Pos()).Line + if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 { + fset.File(d.Rparen).MergeLine(rParenLine - 1) + } + } } } @@ -46,22 +58,41 @@ func importPath(s Spec) string { return "" } +func importName(s Spec) string { + n := s.(*ImportSpec).Name + if n == nil { + return "" + } + return n.Name +} + +func importComment(s Spec) string { + c := s.(*ImportSpec).Comment + if c == nil { + return "" + } + return c.Text() +} + +// collapse indicates whether prev may be removed, leaving only next. +func collapse(prev, next Spec) bool { + if importPath(next) != importPath(prev) || importName(next) != importName(prev) { + return false + } + return prev.(*ImportSpec).Comment == nil +} + type posSpan struct { Start token.Pos End token.Pos } -func sortSpecs(fset *token.FileSet, f *File, specs []Spec) { - // Avoid work if already sorted (also catches < 2 entries). - sorted := true - for i, s := range specs { - if i > 0 && importPath(specs[i-1]) > importPath(s) { - sorted = false - break - } - } - if sorted { - return +func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { + // Can't short-circuit here even if specs are already sorted, + // since they might yet need deduplication. + // A lone import, however, may be safely ignored. + if len(specs) <= 1 { + return specs } // Record positions for specs. @@ -101,10 +132,26 @@ func sortSpecs(fset *token.FileSet, f *File, specs []Spec) { } // Sort the import specs by import path. + // Remove duplicates, when possible without data loss. // Reassign the import paths to have the same position sequence. // Reassign each comment to abut the end of its spec. // Sort the comments by new position. - sort.Sort(byImportPath(specs)) + sort.Sort(byImportSpec(specs)) + + // Dedup. Thanks to our sorting, we can just consider + // adjacent pairs of imports. + deduped := specs[:0] + for i, s := range specs { + if i == len(specs)-1 || !collapse(s, specs[i+1]) { + deduped = append(deduped, s) + } else { + p := s.Pos() + fset.File(p).MergeLine(fset.Position(p).Line) + } + } + specs = deduped + + // Fix up comment positions for i, s := range specs { s := s.(*ImportSpec) if s.Name != nil { @@ -118,14 +165,29 @@ func sortSpecs(fset *token.FileSet, f *File, specs []Spec) { } } } + sort.Sort(byCommentPos(comments)) + + return specs } -type byImportPath []Spec // slice of *ImportSpec +type byImportSpec []Spec // slice of *ImportSpec -func (x byImportPath) Len() int { return len(x) } -func (x byImportPath) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x byImportPath) Less(i, j int) bool { return importPath(x[i]) < importPath(x[j]) } +func (x byImportSpec) Len() int { return len(x) } +func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byImportSpec) Less(i, j int) bool { + ipath := importPath(x[i]) + jpath := importPath(x[j]) + if ipath != jpath { + return ipath < jpath + } + iname := importName(x[i]) + jname := importName(x[j]) + if iname != jname { + return iname < jname + } + return importComment(x[i]) < importComment(x[j]) +} type byCommentPos []*CommentGroup diff --git a/src/pkg/go/ast/walk.go b/src/pkg/go/ast/walk.go index fef2503c3..fedffb3f2 100644 --- a/src/pkg/go/ast/walk.go +++ b/src/pkg/go/ast/walk.go @@ -122,6 +122,9 @@ func Walk(v Visitor, node Node) { if n.High != nil { Walk(v, n.High) } + if n.Max != nil { + Walk(v, n.Max) + } case *TypeAssertExpr: Walk(v, n.X) diff --git a/src/pkg/go/build/build.go b/src/pkg/go/build/build.go index cc89afb21..50d2fb4ae 100644 --- a/src/pkg/go/build/build.go +++ b/src/pkg/go/build/build.go @@ -258,21 +258,23 @@ func (ctxt *Context) SrcDirs() []string { var Default Context = defaultContext() var cgoEnabled = map[string]bool{ - "darwin/386": true, - "darwin/amd64": true, - "freebsd/386": true, - "freebsd/amd64": true, - "freebsd/arm": true, - "linux/386": true, - "linux/amd64": true, - "linux/arm": true, - "netbsd/386": true, - "netbsd/amd64": true, - "netbsd/arm": true, - "openbsd/386": true, - "openbsd/amd64": true, - "windows/386": true, - "windows/amd64": true, + "darwin/386": true, + "darwin/amd64": true, + "dragonfly/386": true, + "dragonfly/amd64": true, + "freebsd/386": true, + "freebsd/amd64": true, + "freebsd/arm": true, + "linux/386": true, + "linux/amd64": true, + "linux/arm": true, + "netbsd/386": true, + "netbsd/amd64": true, + "netbsd/arm": true, + "openbsd/386": true, + "openbsd/amd64": true, + "windows/386": true, + "windows/amd64": true, } func defaultContext() Context { @@ -293,7 +295,7 @@ func defaultContext() Context { // When we reach Go 1.3 the line will read // c.ReleaseTags = []string{"go1.1", "go1.2", "go1.3"} // and so on. - c.ReleaseTags = []string{"go1.1"} + c.ReleaseTags = []string{"go1.1", "go1.2"} switch os.Getenv("CGO_ENABLED") { case "1": @@ -337,32 +339,37 @@ const ( // A Package describes the Go package found in a directory. type Package struct { - Dir string // directory containing package sources - Name string // package name - Doc string // documentation synopsis - ImportPath string // import path of package ("" if unknown) - Root string // root of Go tree where this package lives - SrcRoot string // package source root directory ("" if unknown) - PkgRoot string // package install root directory ("" if unknown) - BinDir string // command install directory ("" if unknown) - Goroot bool // package found in Go root - PkgObj string // installed .a file + Dir string // directory containing package sources + Name string // package name + Doc string // documentation synopsis + ImportPath string // import path of package ("" if unknown) + Root string // root of Go tree where this package lives + SrcRoot string // package source root directory ("" if unknown) + PkgRoot string // package install root directory ("" if unknown) + BinDir string // command install directory ("" if unknown) + Goroot bool // package found in Go root + PkgObj string // installed .a file + AllTags []string // tags that can influence file selection in this directory + ConflictDir string // this directory shadows Dir in $GOPATH // Source files GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) CgoFiles []string // .go source files that import "C" IgnoredGoFiles []string // .go source files ignored for this build CFiles []string // .c source files - HFiles []string // .h source files + CXXFiles []string // .cc, .cpp and .cxx source files + HFiles []string // .h, .hh, .hpp and .hxx source files SFiles []string // .s source files - SysoFiles []string // .syso system object files to add to archive SwigFiles []string // .swig files SwigCXXFiles []string // .swigcxx files + SysoFiles []string // .syso system object files to add to archive // Cgo directives - CgoPkgConfig []string // Cgo pkg-config directives CgoCFLAGS []string // Cgo CFLAGS directives + CgoCPPFLAGS []string // Cgo CPPFLAGS directives + CgoCXXFLAGS []string // Cgo CXXFLAGS directives CgoLDFLAGS []string // Cgo LDFLAGS directives + CgoPkgConfig []string // Cgo pkg-config directives // Dependency information Imports []string // imports from GoFiles, CgoFiles @@ -391,13 +398,22 @@ func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { } // NoGoError is the error used by Import to describe a directory -// containing no Go source files. +// containing no buildable Go source files. (It may still contain +// test files, files hidden by build tags, and so on.) type NoGoError struct { Dir string } func (e *NoGoError) Error() string { - return "no Go source files in " + e.Dir + return "no buildable Go source files in " + e.Dir +} + +func nameExt(name string) string { + i := strings.LastIndex(name, ".") + if i < 0 { + return "" + } + return name[i:] } // Import returns details about the Go package named by the import path, @@ -429,7 +445,7 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa switch ctxt.Compiler { case "gccgo": dir, elem := pathpkg.Split(p.ImportPath) - pkga = "pkg/gccgo/" + dir + "lib" + elem + ".a" + pkga = "pkg/gccgo_" + ctxt.GOOS + "_" + ctxt.GOARCH + "/" + dir + "lib" + elem + ".a" case "gc": suffix := "" if ctxt.InstallSuffix != "" { @@ -469,11 +485,13 @@ func (ctxt *Context) Import(path string, srcDir string, mode ImportMode) (*Packa // else first. if ctxt.GOROOT != "" { if dir := ctxt.joinPath(ctxt.GOROOT, "src", "pkg", sub); ctxt.isDir(dir) { + p.ConflictDir = dir goto Found } } for _, earlyRoot := range all[:i] { if dir := ctxt.joinPath(earlyRoot, "src", sub); ctxt.isDir(dir) { + p.ConflictDir = dir goto Found } } @@ -575,63 +593,21 @@ Found: imported := make(map[string][]token.Position) testImported := make(map[string][]token.Position) xTestImported := make(map[string][]token.Position) + allTags := make(map[string]bool) fset := token.NewFileSet() for _, d := range dirs { if d.IsDir() { continue } - name := d.Name() - if strings.HasPrefix(name, "_") || - strings.HasPrefix(name, ".") { - continue - } - - i := strings.LastIndex(name, ".") - if i < 0 { - i = len(name) - } - ext := name[i:] - - if !ctxt.UseAllFiles && !ctxt.goodOSArchFile(name) { - if ext == ".go" { - p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) - } - continue - } - switch ext { - case ".go", ".c", ".s", ".h", ".S", ".swig", ".swigcxx": - // tentatively okay - read to make sure - case ".syso": - // binary objects to add to package archive - // Likely of the form foo_windows.syso, but - // the name was vetted above with goodOSArchFile. - p.SysoFiles = append(p.SysoFiles, name) - continue - default: - // skip - continue - } + name := d.Name() + ext := nameExt(name) - filename := ctxt.joinPath(p.Dir, name) - f, err := ctxt.openFile(filename) + match, data, filename, err := ctxt.matchFile(p.Dir, name, true, allTags) if err != nil { return p, err } - - var data []byte - if strings.HasSuffix(filename, ".go") { - data, err = readImports(f, false) - } else { - data, err = readComments(f) - } - f.Close() - if err != nil { - return p, fmt.Errorf("read %s: %v", filename, err) - } - - // Look for +build comments to accept or reject the file. - if !ctxt.UseAllFiles && !ctxt.shouldBuild(data) { + if !match { if ext == ".go" { p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) } @@ -643,7 +619,10 @@ Found: case ".c": p.CFiles = append(p.CFiles, name) continue - case ".h": + case ".cc", ".cpp", ".cxx": + p.CXXFiles = append(p.CXXFiles, name) + continue + case ".h", ".hh", ".hpp", ".hxx": p.HFiles = append(p.HFiles, name) continue case ".s": @@ -658,6 +637,12 @@ Found: case ".swigcxx": p.SwigCXXFiles = append(p.SwigCXXFiles, name) continue + case ".syso": + // binary objects to add to package archive + // Likely of the form foo_windows.syso, but + // the name was vetted above with goodOSArchFile. + p.SysoFiles = append(p.SysoFiles, name) + continue } pf, err := parser.ParseFile(fset, filename, data, parser.ImportsOnly|parser.ParseComments) @@ -730,8 +715,11 @@ Found: } } if isCgo { + allTags["cgo"] = true if ctxt.CgoEnabled { p.CgoFiles = append(p.CgoFiles, name) + } else { + p.IgnoredGoFiles = append(p.IgnoredGoFiles, name) } } else if isXTest { p.XTestGoFiles = append(p.XTestGoFiles, name) @@ -741,10 +729,15 @@ Found: p.GoFiles = append(p.GoFiles, name) } } - if p.Name == "" { + if len(p.GoFiles)+len(p.CgoFiles)+len(p.TestGoFiles)+len(p.XTestGoFiles) == 0 { return p, &NoGoError{p.Dir} } + for tag := range allTags { + p.AllTags = append(p.AllTags, tag) + } + sort.Strings(p.AllTags) + p.Imports, p.ImportPos = cleanImports(imported) p.TestImports, p.TestImportPos = cleanImports(testImported) p.XTestImports, p.XTestImportPos = cleanImports(xTestImported) @@ -760,6 +753,79 @@ Found: return p, pkgerr } +// MatchFile reports whether the file with the given name in the given directory +// matches the context and would be included in a Package created by ImportDir +// of that directory. +// +// MatchFile considers the name of the file and may use ctxt.OpenFile to +// read some or all of the file's content. +func (ctxt *Context) MatchFile(dir, name string) (match bool, err error) { + match, _, _, err = ctxt.matchFile(dir, name, false, nil) + return +} + +// matchFile determines whether the file with the given name in the given directory +// should be included in the package being constructed. +// It returns the data read from the file. +// If returnImports is true and name denotes a Go program, matchFile reads +// until the end of the imports (and returns that data) even though it only +// considers text until the first non-comment. +// If allTags is non-nil, matchFile records any encountered build tag +// by setting allTags[tag] = true. +func (ctxt *Context) matchFile(dir, name string, returnImports bool, allTags map[string]bool) (match bool, data []byte, filename string, err error) { + if strings.HasPrefix(name, "_") || + strings.HasPrefix(name, ".") { + return + } + + i := strings.LastIndex(name, ".") + if i < 0 { + i = len(name) + } + ext := name[i:] + + if !ctxt.goodOSArchFile(name, allTags) && !ctxt.UseAllFiles { + return + } + + switch ext { + case ".go", ".c", ".cc", ".cxx", ".cpp", ".s", ".h", ".hh", ".hpp", ".hxx", ".S", ".swig", ".swigcxx": + // tentatively okay - read to make sure + case ".syso": + // binary, no reading + match = true + return + default: + // skip + return + } + + filename = ctxt.joinPath(dir, name) + f, err := ctxt.openFile(filename) + if err != nil { + return + } + + if strings.HasSuffix(filename, ".go") { + data, err = readImports(f, false) + } else { + data, err = readComments(f) + } + f.Close() + if err != nil { + err = fmt.Errorf("read %s: %v", filename, err) + return + } + + // Look for +build comments to accept or reject the file. + if !ctxt.shouldBuild(data, allTags) && !ctxt.UseAllFiles { + return + } + + match = true + return +} + func cleanImports(m map[string][]token.Position) ([]string, map[string][]token.Position) { all := make([]string, 0, len(m)) for path := range m { @@ -794,7 +860,7 @@ var slashslash = []byte("//") // // marks the file as applicable only on Windows and Linux. // -func (ctxt *Context) shouldBuild(content []byte) bool { +func (ctxt *Context) shouldBuild(content []byte, allTags map[string]bool) bool { // Pass 1. Identify leading run of // comments and blank lines, // which must be followed by a blank line. end := 0 @@ -819,6 +885,7 @@ func (ctxt *Context) shouldBuild(content []byte) bool { // Pass 2. Process each line in the run. p = content + allok := true for len(p) > 0 { line := p if i := bytes.IndexByte(line, '\n'); i >= 0 { @@ -835,24 +902,24 @@ func (ctxt *Context) shouldBuild(content []byte) bool { if f[0] == "+build" { ok := false for _, tok := range f[1:] { - if ctxt.match(tok) { + if ctxt.match(tok, allTags) { ok = true - break } } if !ok { - return false // this one doesn't match + allok = false } } } } } - return true // everything matches + + return allok } // saveCgo saves the information from the #cgo lines in the import "C" comment. -// These lines set CFLAGS and LDFLAGS and pkg-config directives that affect -// the way cgo's C code is built. +// These lines set CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS and pkg-config directives +// that affect the way cgo's C code is built. // // TODO(rsc): This duplicates code in cgo. // Once the dust settles, remove this code from cgo. @@ -887,7 +954,7 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) if len(cond) > 0 { ok := false for _, c := range cond { - if ctxt.match(c) { + if ctxt.match(c, nil) { ok = true break } @@ -902,7 +969,7 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) return fmt.Errorf("%s: invalid #cgo line: %s", filename, orig) } for _, arg := range args { - if !safeName(arg) { + if !safeCgoName(arg) { return fmt.Errorf("%s: malformed #cgo argument: %s", filename, arg) } } @@ -910,6 +977,10 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) switch verb { case "CFLAGS": di.CgoCFLAGS = append(di.CgoCFLAGS, args...) + case "CPPFLAGS": + di.CgoCPPFLAGS = append(di.CgoCPPFLAGS, args...) + case "CXXFLAGS": + di.CgoCXXFLAGS = append(di.CgoCXXFLAGS, args...) case "LDFLAGS": di.CgoLDFLAGS = append(di.CgoLDFLAGS, args...) case "pkg-config": @@ -921,9 +992,12 @@ func (ctxt *Context) saveCgo(filename string, di *Package, cg *ast.CommentGroup) return nil } -var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:") +// NOTE: $ is not safe for the shell, but it is allowed here because of linker options like -Wl,$ORIGIN. +// We never pass these arguments to a shell (just to programs we construct argv for), so this should be okay. +// See golang.org/issue/6038. +var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz:$") -func safeName(s string) bool { +func safeCgoName(s string) bool { if s == "" { return false } @@ -1008,19 +1082,28 @@ func splitQuoted(s string) (r []string, err error) { // !tag (if tag is not listed in ctxt.BuildTags or ctxt.ReleaseTags) // a comma-separated list of any of these // -func (ctxt *Context) match(name string) bool { +func (ctxt *Context) match(name string, allTags map[string]bool) bool { if name == "" { + if allTags != nil { + allTags[name] = true + } return false } if i := strings.Index(name, ","); i >= 0 { // comma-separated list - return ctxt.match(name[:i]) && ctxt.match(name[i+1:]) + ok1 := ctxt.match(name[:i], allTags) + ok2 := ctxt.match(name[i+1:], allTags) + return ok1 && ok2 } if strings.HasPrefix(name, "!!") { // bad syntax, reject always return false } if strings.HasPrefix(name, "!") { // negation - return len(name) > 1 && !ctxt.match(name[1:]) + return len(name) > 1 && !ctxt.match(name[1:], allTags) + } + + if allTags != nil { + allTags[name] = true } // Tags must be letters, digits, underscores or dots. @@ -1065,7 +1148,7 @@ func (ctxt *Context) match(name string) bool { // name_$(GOARCH)_test.* // name_$(GOOS)_$(GOARCH)_test.* // -func (ctxt *Context) goodOSArchFile(name string) bool { +func (ctxt *Context) goodOSArchFile(name string, allTags map[string]bool) bool { if dot := strings.Index(name, "."); dot != -1 { name = name[:dot] } @@ -1075,12 +1158,22 @@ func (ctxt *Context) goodOSArchFile(name string) bool { } n := len(l) if n >= 2 && knownOS[l[n-2]] && knownArch[l[n-1]] { + if allTags != nil { + allTags[l[n-2]] = true + allTags[l[n-1]] = true + } return l[n-2] == ctxt.GOOS && l[n-1] == ctxt.GOARCH } if n >= 1 && knownOS[l[n-1]] { + if allTags != nil { + allTags[l[n-1]] = true + } return l[n-1] == ctxt.GOOS } if n >= 1 && knownArch[l[n-1]] { + if allTags != nil { + allTags[l[n-1]] = true + } return l[n-1] == ctxt.GOARCH } return true diff --git a/src/pkg/go/build/build_test.go b/src/pkg/go/build/build_test.go index d8cf98840..fca8d4bdb 100644 --- a/src/pkg/go/build/build_test.go +++ b/src/pkg/go/build/build_test.go @@ -5,38 +5,49 @@ package build import ( + "io" "os" "path/filepath" + "reflect" "runtime" + "strings" "testing" ) func TestMatch(t *testing.T) { ctxt := Default what := "default" - match := func(tag string) { - if !ctxt.match(tag) { + match := func(tag string, want map[string]bool) { + m := make(map[string]bool) + if !ctxt.match(tag, m) { t.Errorf("%s context should match %s, does not", what, tag) } + if !reflect.DeepEqual(m, want) { + t.Errorf("%s tags = %v, want %v", tag, m, want) + } } - nomatch := func(tag string) { - if ctxt.match(tag) { + nomatch := func(tag string, want map[string]bool) { + m := make(map[string]bool) + if ctxt.match(tag, m) { t.Errorf("%s context should NOT match %s, does", what, tag) } + if !reflect.DeepEqual(m, want) { + t.Errorf("%s tags = %v, want %v", tag, m, want) + } } - match(runtime.GOOS + "," + runtime.GOARCH) - match(runtime.GOOS + "," + runtime.GOARCH + ",!foo") - nomatch(runtime.GOOS + "," + runtime.GOARCH + ",foo") + match(runtime.GOOS+","+runtime.GOARCH, map[string]bool{runtime.GOOS: true, runtime.GOARCH: true}) + match(runtime.GOOS+","+runtime.GOARCH+",!foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + nomatch(runtime.GOOS+","+runtime.GOARCH+",foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) what = "modified" ctxt.BuildTags = []string{"foo"} - match(runtime.GOOS + "," + runtime.GOARCH) - match(runtime.GOOS + "," + runtime.GOARCH + ",foo") - nomatch(runtime.GOOS + "," + runtime.GOARCH + ",!foo") - match(runtime.GOOS + "," + runtime.GOARCH + ",!bar") - nomatch(runtime.GOOS + "," + runtime.GOARCH + ",bar") - nomatch("!") + match(runtime.GOOS+","+runtime.GOARCH, map[string]bool{runtime.GOOS: true, runtime.GOARCH: true}) + match(runtime.GOOS+","+runtime.GOARCH+",foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + nomatch(runtime.GOOS+","+runtime.GOARCH+",!foo", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "foo": true}) + match(runtime.GOOS+","+runtime.GOARCH+",!bar", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "bar": true}) + nomatch(runtime.GOOS+","+runtime.GOARCH+",bar", map[string]bool{runtime.GOOS: true, runtime.GOARCH: true, "bar": true}) + nomatch("!", map[string]bool{}) } func TestDotSlashImport(t *testing.T) { @@ -92,28 +103,84 @@ func TestLocalDirectory(t *testing.T) { func TestShouldBuild(t *testing.T) { const file1 = "// +build tag1\n\n" + "package main\n" + want1 := map[string]bool{"tag1": true} const file2 = "// +build cgo\n\n" + "// This package implements parsing of tags like\n" + "// +build tag1\n" + "package build" + want2 := map[string]bool{"cgo": true} const file3 = "// Copyright The Go Authors.\n\n" + "package build\n\n" + "// shouldBuild checks tags given by lines of the form\n" + "// +build tag\n" + "func shouldBuild(content []byte)\n" + want3 := map[string]bool{} ctx := &Context{BuildTags: []string{"tag1"}} - if !ctx.shouldBuild([]byte(file1)) { - t.Errorf("should not build file1, expected the contrary") + m := map[string]bool{} + if !ctx.shouldBuild([]byte(file1), m) { + t.Errorf("shouldBuild(file1) = false, want true") + } + if !reflect.DeepEqual(m, want1) { + t.Errorf("shoudBuild(file1) tags = %v, want %v", m, want1) + } + + m = map[string]bool{} + if ctx.shouldBuild([]byte(file2), m) { + t.Errorf("shouldBuild(file2) = true, want fakse") } - if ctx.shouldBuild([]byte(file2)) { - t.Errorf("should build file2, expected the contrary") + if !reflect.DeepEqual(m, want2) { + t.Errorf("shoudBuild(file2) tags = %v, want %v", m, want2) } + m = map[string]bool{} ctx = &Context{BuildTags: nil} - if !ctx.shouldBuild([]byte(file3)) { - t.Errorf("should not build file3, expected the contrary") + if !ctx.shouldBuild([]byte(file3), m) { + t.Errorf("shouldBuild(file3) = false, want true") + } + if !reflect.DeepEqual(m, want3) { + t.Errorf("shoudBuild(file3) tags = %v, want %v", m, want3) + } +} + +type readNopCloser struct { + io.Reader +} + +func (r readNopCloser) Close() error { + return nil +} + +var matchFileTests = []struct { + name string + data string + match bool +}{ + {"foo_arm.go", "", true}, + {"foo1_arm.go", "// +build linux\n\npackage main\n", false}, + {"foo_darwin.go", "", false}, + {"foo.go", "", true}, + {"foo1.go", "// +build linux\n\npackage main\n", false}, + {"foo.badsuffix", "", false}, +} + +func TestMatchFile(t *testing.T) { + for _, tt := range matchFileTests { + ctxt := Context{GOARCH: "arm", GOOS: "plan9"} + ctxt.OpenFile = func(path string) (r io.ReadCloser, err error) { + if path != "x+"+tt.name { + t.Fatalf("OpenFile asked for %q, expected %q", path, "x+"+tt.name) + } + return &readNopCloser{strings.NewReader(tt.data)}, nil + } + ctxt.JoinPath = func(elem ...string) string { + return strings.Join(elem, "+") + } + match, err := ctxt.MatchFile("x", tt.name) + if match != tt.match || err != nil { + t.Fatalf("MatchFile(%q) = %v, %v, want %v, nil", tt.name, match, err, tt.match) + } } } diff --git a/src/pkg/go/build/deps_test.go b/src/pkg/go/build/deps_test.go index 71b1bcf06..dd162c7db 100644 --- a/src/pkg/go/build/deps_test.go +++ b/src/pkg/go/build/deps_test.go @@ -82,24 +82,27 @@ var pkgDeps = map[string][]string{ // L3 adds reflection and some basic utility packages // and interface definitions, but nothing that makes // system calls. - "crypto": {"L2", "hash"}, // interfaces - "crypto/cipher": {"L2"}, // interfaces - "encoding/base32": {"L2"}, - "encoding/base64": {"L2"}, - "encoding/binary": {"L2", "reflect"}, - "hash": {"L2"}, // interfaces - "hash/adler32": {"L2", "hash"}, - "hash/crc32": {"L2", "hash"}, - "hash/crc64": {"L2", "hash"}, - "hash/fnv": {"L2", "hash"}, - "image": {"L2", "image/color"}, // interfaces - "image/color": {"L2"}, // interfaces - "reflect": {"L2"}, + "crypto": {"L2", "hash"}, // interfaces + "crypto/cipher": {"L2", "crypto/subtle"}, // interfaces + "crypto/subtle": {}, + "encoding/base32": {"L2"}, + "encoding/base64": {"L2"}, + "encoding/binary": {"L2", "reflect"}, + "hash": {"L2"}, // interfaces + "hash/adler32": {"L2", "hash"}, + "hash/crc32": {"L2", "hash"}, + "hash/crc64": {"L2", "hash"}, + "hash/fnv": {"L2", "hash"}, + "image": {"L2", "image/color"}, // interfaces + "image/color": {"L2"}, // interfaces + "image/color/palette": {"L2", "image/color"}, + "reflect": {"L2"}, "L3": { "L2", "crypto", "crypto/cipher", + "crypto/subtle", "encoding/base32", "encoding/base64", "encoding/binary", @@ -110,6 +113,7 @@ var pkgDeps = map[string][]string{ "hash/fnv", "image", "image/color", + "image/color/palette", "reflect", }, @@ -183,26 +187,27 @@ var pkgDeps = map[string][]string{ "compress/gzip": {"L4", "compress/flate"}, "compress/lzw": {"L4"}, "compress/zlib": {"L4", "compress/flate"}, - "database/sql": {"L4", "database/sql/driver"}, + "database/sql": {"L4", "container/list", "database/sql/driver"}, "database/sql/driver": {"L4", "time"}, "debug/dwarf": {"L4"}, "debug/elf": {"L4", "OS", "debug/dwarf"}, "debug/gosym": {"L4"}, "debug/macho": {"L4", "OS", "debug/dwarf"}, "debug/pe": {"L4", "OS", "debug/dwarf"}, + "encoding": {"L4"}, "encoding/ascii85": {"L4"}, "encoding/asn1": {"L4", "math/big"}, "encoding/csv": {"L4"}, - "encoding/gob": {"L4", "OS"}, + "encoding/gob": {"L4", "OS", "encoding"}, "encoding/hex": {"L4"}, - "encoding/json": {"L4"}, + "encoding/json": {"L4", "encoding"}, "encoding/pem": {"L4"}, - "encoding/xml": {"L4"}, + "encoding/xml": {"L4", "encoding"}, "flag": {"L4", "OS"}, "go/build": {"L4", "OS", "GOPARSER"}, "html": {"L4"}, "image/draw": {"L4"}, - "image/gif": {"L4", "compress/lzw"}, + "image/gif": {"L4", "compress/lzw", "image/color/palette", "image/draw"}, "image/jpeg": {"L4"}, "image/png": {"L4", "compress/zlib"}, "index/suffixarray": {"L4", "regexp"}, @@ -228,7 +233,8 @@ var pkgDeps = map[string][]string{ // that shows up in programs that use cgo. "C": {}, - "os/user": {"L4", "CGO", "syscall"}, + // Plan 9 alone needs io/ioutil and os. + "os/user": {"L4", "CGO", "io/ioutil", "os", "syscall"}, // Basic networking. // Because net must be used by any package that wants to @@ -248,15 +254,10 @@ var pkgDeps = map[string][]string{ "net/mail": {"L4", "NET", "OS"}, "net/textproto": {"L4", "OS", "net"}, - // Support libraries for crypto that aren't L2. - "CRYPTO-SUPPORT": { - "crypto/subtle", - }, - // Core crypto. "crypto/aes": {"L3"}, "crypto/des": {"L3"}, - "crypto/hmac": {"L3", "CRYPTO-SUPPORT"}, + "crypto/hmac": {"L3"}, "crypto/md5": {"L3"}, "crypto/rc4": {"L3"}, "crypto/sha1": {"L3"}, @@ -264,7 +265,6 @@ var pkgDeps = map[string][]string{ "crypto/sha512": {"L3"}, "CRYPTO": { - "CRYPTO-SUPPORT", "crypto/aes", "crypto/des", "crypto/hmac", @@ -359,7 +359,7 @@ func allowed(pkg string) map[string]bool { } var bools = []bool{false, true} -var geese = []string{"darwin", "freebsd", "linux", "netbsd", "openbsd", "plan9", "windows"} +var geese = []string{"darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "plan9", "windows"} var goarches = []string{"386", "amd64", "arm"} type osPkg struct { @@ -392,6 +392,9 @@ func TestDependencies(t *testing.T) { if allowedErrors[osPkg{ctxt.GOOS, pkg}] { continue } + if !ctxt.CgoEnabled && pkg == "runtime/cgo" { + continue + } // Some of the combinations we try might not // be reasonable (like arm,plan9,cgo), so ignore // errors for the auto-generated combinations. diff --git a/src/pkg/go/build/doc.go b/src/pkg/go/build/doc.go index b5fc071d6..b2f04ea45 100644 --- a/src/pkg/go/build/doc.go +++ b/src/pkg/go/build/doc.go @@ -94,6 +94,7 @@ // - the compiler being used, either "gc" or "gccgo" // - "cgo", if ctxt.CgoEnabled is true // - "go1.1", from Go version 1.1 onward +// - "go1.2", from Go version 1.2 onward // - any additional words listed in ctxt.BuildTags // // If a file's name, after stripping the extension and a possible _test suffix, diff --git a/src/pkg/go/build/syslist.go b/src/pkg/go/build/syslist.go index ea21f3c74..e1fbf6330 100644 --- a/src/pkg/go/build/syslist.go +++ b/src/pkg/go/build/syslist.go @@ -4,5 +4,5 @@ package build -const goosList = "darwin freebsd linux netbsd openbsd plan9 windows " +const goosList = "darwin dragonfly freebsd linux netbsd openbsd plan9 windows " const goarchList = "386 amd64 arm " diff --git a/src/pkg/go/build/syslist_test.go b/src/pkg/go/build/syslist_test.go index 9157faf8c..3be2928f5 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 Default.goodOSArchFile(test.name) != test.result { + if Default.goodOSArchFile(test.name, make(map[string]bool)) != test.result { t.Fatalf("goodOSArchFile(%q) != %v", test.name, test.result) } } diff --git a/src/pkg/go/doc/comment.go b/src/pkg/go/doc/comment.go index c4b7e6ae6..5c8c43e0c 100644 --- a/src/pkg/go/doc/comment.go +++ b/src/pkg/go/doc/comment.go @@ -239,9 +239,14 @@ func anchorID(line string) string { // nor to have trailing spaces at the end of lines. // The comment markers have already been removed. // -// Turn each run of multiple \n into </p><p>. -// Turn each run of indented lines into a <pre> block without indent. -// Enclose headings with header tags. +// Each span of unindented non-blank lines is converted into +// a single paragraph. There is one exception to the rule: a span that +// consists of a single line, is followed by another paragraph span, +// begins with a capital letter, and contains no punctuation +// is formatted as a heading. +// +// A span of indented lines is converted into a <pre> block, +// with the common indent prefix removed. // // URLs in the comment text are converted into links; if the URL also appears // in the words map, the link is taken from the map (if the corresponding map diff --git a/src/pkg/go/doc/doc_test.go b/src/pkg/go/doc/doc_test.go index 8043038b4..ad8ba5378 100644 --- a/src/pkg/go/doc/doc_test.go +++ b/src/pkg/go/doc/doc_test.go @@ -32,6 +32,7 @@ func readTemplate(filename string) *template.Template { t.Funcs(template.FuncMap{ "node": nodeFmt, "synopsis": synopsisFmt, + "indent": indentFmt, }) return template.Must(t.ParseFiles(filepath.Join(dataDir, filename))) } @@ -55,6 +56,15 @@ func synopsisFmt(s string) string { return "// " + strings.Replace(s, "\n", " ", -1) } +func indentFmt(indent, s string) string { + end := "" + if strings.HasSuffix(s, "\n") { + end = "\n" + s = s[:len(s)-1] + } + return indent + strings.Replace(s, "\n", "\n"+indent, -1) + end +} + func isGoFile(fi os.FileInfo) bool { name := fi.Name() return !fi.IsDir() && diff --git a/src/pkg/go/doc/example.go b/src/pkg/go/doc/example.go index 2761083c7..2358ed389 100644 --- a/src/pkg/go/doc/example.go +++ b/src/pkg/go/doc/example.go @@ -265,7 +265,7 @@ func playExample(file *ast.File, body *ast.BlockStmt) *ast.File { // Synthesize main function. funcDecl := &ast.FuncDecl{ Name: ast.NewIdent("main"), - Type: &ast.FuncType{}, + Type: &ast.FuncType{Params: &ast.FieldList{}}, // FuncType.Params must be non-nil Body: body, } diff --git a/src/pkg/go/doc/example_test.go b/src/pkg/go/doc/example_test.go index e0477e3f6..e154ea8bf 100644 --- a/src/pkg/go/doc/example_test.go +++ b/src/pkg/go/doc/example_test.go @@ -159,8 +159,8 @@ func main() { ` func TestExamples(t *testing.T) { - fs := token.NewFileSet() - file, err := parser.ParseFile(fs, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments) + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, "test.go", strings.NewReader(exampleTestFile), parser.ParseComments) if err != nil { t.Fatal(err) } @@ -174,11 +174,11 @@ func TestExamples(t *testing.T) { if e.Play == nil { g = "<nil>" } else { - b := new(bytes.Buffer) - if err := format.Node(b, fs, e.Play); err != nil { + var buf bytes.Buffer + if err := format.Node(&buf, fset, e.Play); err != nil { t.Fatal(err) } - g = b.String() + g = buf.String() } if g != w { t.Errorf("%s: got Play == %q, want %q", c.Name, g, w) diff --git a/src/pkg/go/doc/reader.go b/src/pkg/go/doc/reader.go index 4fa6fd9d5..ed82c47cd 100644 --- a/src/pkg/go/doc/reader.go +++ b/src/pkg/go/doc/reader.go @@ -414,7 +414,7 @@ func (r *reader) readNote(list []*ast.Comment) { // We remove any formatting so that we don't // get spurious line breaks/indentation when // showing the TODO body. - body := clean(text[m[1]:]) + body := clean(text[m[1]:], keepNL) if body != "" { marker := text[m[2]:m[3]] r.notes[marker] = append(r.notes[marker], &Note{ diff --git a/src/pkg/go/doc/synopsis.go b/src/pkg/go/doc/synopsis.go index 2d1817439..c90080b7c 100644 --- a/src/pkg/go/doc/synopsis.go +++ b/src/pkg/go/doc/synopsis.go @@ -22,19 +22,28 @@ func firstSentenceLen(s string) int { if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) { return i } + if p == '。' || p == '.' { + return i + } ppp, pp, p = pp, p, q } return len(s) } +const ( + keepNL = 1 << iota +) + // clean replaces each sequence of space, \n, \r, or \t characters // with a single space and removes any trailing and leading spaces. -func clean(s string) string { +// If the keepNL flag is set, newline characters are passed through +// instead of being change to spaces. +func clean(s string, flags int) string { var b []byte p := byte(' ') for i := 0; i < len(s); i++ { q := s[i] - if q == '\n' || q == '\r' || q == '\t' { + if (flags&keepNL) == 0 && q == '\n' || q == '\r' || q == '\t' { q = ' ' } if q != ' ' || p != ' ' { @@ -57,7 +66,7 @@ func clean(s string) string { // is the empty string. // func Synopsis(s string) string { - s = clean(s[0:firstSentenceLen(s)]) + s = clean(s[0:firstSentenceLen(s)], 0) for _, prefix := range IllegalPrefixes { if strings.HasPrefix(strings.ToLower(s), prefix) { return "" diff --git a/src/pkg/go/doc/synopsis_test.go b/src/pkg/go/doc/synopsis_test.go index fd7081a07..59b253cb8 100644 --- a/src/pkg/go/doc/synopsis_test.go +++ b/src/pkg/go/doc/synopsis_test.go @@ -28,6 +28,8 @@ var tests = []struct { {"P. Q. ", 8, "P. Q."}, {"Package Καλημέρα κόσμε.", 36, "Package Καλημέρα κόσμε."}, {"Package こんにちは 世界\n", 31, "Package こんにちは 世界"}, + {"Package こんにちは。世界", 26, "Package こんにちは。"}, + {"Package 안녕.世界", 17, "Package 안녕."}, {"Package foo does bar.", 21, "Package foo does bar."}, {"Copyright 2012 Google, Inc. Package foo does bar.", 27, ""}, {"All Rights reserved. Package foo does bar.", 20, ""}, diff --git a/src/pkg/go/doc/testdata/a.0.golden b/src/pkg/go/doc/testdata/a.0.golden index cd98f4e0e..7e680b80b 100644 --- a/src/pkg/go/doc/testdata/a.0.golden +++ b/src/pkg/go/doc/testdata/a.0.golden @@ -9,24 +9,44 @@ FILENAMES testdata/a1.go BUGS .Bugs is now deprecated, please use .Notes instead - // bug0 - // bug1 + bug0 + + bug1 + BUGS - // bug0 (uid: uid) - // bug1 (uid: uid) +BUG(uid) bug0 + +BUG(uid) bug1 + NOTES - // 1 of 4 - this is the first line of note 1 - note 1 continues on ... (uid: foo) - // 2 of 4 (uid: foo) - // 3 of 4 (uid: bar) - // 4 of 4 - this is the last line of note 4 (uid: bar) - // This note which contains a (parenthesized) subphrase must ... (uid: bam) - // The ':' after the marker and uid is optional. (uid: xxx) +NOTE(uid) + +NOTE(foo) 1 of 4 - this is the first line of note 1 + - note 1 continues on this 2nd line + - note 1 continues on this 3rd line + +NOTE(foo) 2 of 4 + +NOTE(bar) 3 of 4 + +NOTE(bar) 4 of 4 + - this is the last line of note 4 + +NOTE(bam) This note which contains a (parenthesized) subphrase + must appear in its entirety. + +NOTE(xxx) The ':' after the marker and uid is optional. + SECBUGS - // sec hole 0 need to fix asap (uid: uid) +SECBUG(uid) sec hole 0 + need to fix asap + TODOS - // todo0 (uid: uid) - // todo1 (uid: uid) +TODO(uid) todo0 + +TODO(uid) todo1 + diff --git a/src/pkg/go/doc/testdata/a.1.golden b/src/pkg/go/doc/testdata/a.1.golden index cd98f4e0e..7e680b80b 100644 --- a/src/pkg/go/doc/testdata/a.1.golden +++ b/src/pkg/go/doc/testdata/a.1.golden @@ -9,24 +9,44 @@ FILENAMES testdata/a1.go BUGS .Bugs is now deprecated, please use .Notes instead - // bug0 - // bug1 + bug0 + + bug1 + BUGS - // bug0 (uid: uid) - // bug1 (uid: uid) +BUG(uid) bug0 + +BUG(uid) bug1 + NOTES - // 1 of 4 - this is the first line of note 1 - note 1 continues on ... (uid: foo) - // 2 of 4 (uid: foo) - // 3 of 4 (uid: bar) - // 4 of 4 - this is the last line of note 4 (uid: bar) - // This note which contains a (parenthesized) subphrase must ... (uid: bam) - // The ':' after the marker and uid is optional. (uid: xxx) +NOTE(uid) + +NOTE(foo) 1 of 4 - this is the first line of note 1 + - note 1 continues on this 2nd line + - note 1 continues on this 3rd line + +NOTE(foo) 2 of 4 + +NOTE(bar) 3 of 4 + +NOTE(bar) 4 of 4 + - this is the last line of note 4 + +NOTE(bam) This note which contains a (parenthesized) subphrase + must appear in its entirety. + +NOTE(xxx) The ':' after the marker and uid is optional. + SECBUGS - // sec hole 0 need to fix asap (uid: uid) +SECBUG(uid) sec hole 0 + need to fix asap + TODOS - // todo0 (uid: uid) - // todo1 (uid: uid) +TODO(uid) todo0 + +TODO(uid) todo1 + diff --git a/src/pkg/go/doc/testdata/a.2.golden b/src/pkg/go/doc/testdata/a.2.golden index cd98f4e0e..7e680b80b 100644 --- a/src/pkg/go/doc/testdata/a.2.golden +++ b/src/pkg/go/doc/testdata/a.2.golden @@ -9,24 +9,44 @@ FILENAMES testdata/a1.go BUGS .Bugs is now deprecated, please use .Notes instead - // bug0 - // bug1 + bug0 + + bug1 + BUGS - // bug0 (uid: uid) - // bug1 (uid: uid) +BUG(uid) bug0 + +BUG(uid) bug1 + NOTES - // 1 of 4 - this is the first line of note 1 - note 1 continues on ... (uid: foo) - // 2 of 4 (uid: foo) - // 3 of 4 (uid: bar) - // 4 of 4 - this is the last line of note 4 (uid: bar) - // This note which contains a (parenthesized) subphrase must ... (uid: bam) - // The ':' after the marker and uid is optional. (uid: xxx) +NOTE(uid) + +NOTE(foo) 1 of 4 - this is the first line of note 1 + - note 1 continues on this 2nd line + - note 1 continues on this 3rd line + +NOTE(foo) 2 of 4 + +NOTE(bar) 3 of 4 + +NOTE(bar) 4 of 4 + - this is the last line of note 4 + +NOTE(bam) This note which contains a (parenthesized) subphrase + must appear in its entirety. + +NOTE(xxx) The ':' after the marker and uid is optional. + SECBUGS - // sec hole 0 need to fix asap (uid: uid) +SECBUG(uid) sec hole 0 + need to fix asap + TODOS - // todo0 (uid: uid) - // todo1 (uid: uid) +TODO(uid) todo0 + +TODO(uid) todo1 + diff --git a/src/pkg/go/doc/testdata/bugpara.0.golden b/src/pkg/go/doc/testdata/bugpara.0.golden new file mode 100644 index 000000000..580485950 --- /dev/null +++ b/src/pkg/go/doc/testdata/bugpara.0.golden @@ -0,0 +1,20 @@ +// +PACKAGE bugpara + +IMPORTPATH + testdata/bugpara + +FILENAMES + testdata/bugpara.go + +BUGS .Bugs is now deprecated, please use .Notes instead + Sometimes bugs have multiple paragraphs. + + Like this one. + + +BUGS +BUG(rsc) Sometimes bugs have multiple paragraphs. + + Like this one. + diff --git a/src/pkg/go/doc/testdata/bugpara.1.golden b/src/pkg/go/doc/testdata/bugpara.1.golden new file mode 100644 index 000000000..580485950 --- /dev/null +++ b/src/pkg/go/doc/testdata/bugpara.1.golden @@ -0,0 +1,20 @@ +// +PACKAGE bugpara + +IMPORTPATH + testdata/bugpara + +FILENAMES + testdata/bugpara.go + +BUGS .Bugs is now deprecated, please use .Notes instead + Sometimes bugs have multiple paragraphs. + + Like this one. + + +BUGS +BUG(rsc) Sometimes bugs have multiple paragraphs. + + Like this one. + diff --git a/src/pkg/go/doc/testdata/bugpara.2.golden b/src/pkg/go/doc/testdata/bugpara.2.golden new file mode 100644 index 000000000..580485950 --- /dev/null +++ b/src/pkg/go/doc/testdata/bugpara.2.golden @@ -0,0 +1,20 @@ +// +PACKAGE bugpara + +IMPORTPATH + testdata/bugpara + +FILENAMES + testdata/bugpara.go + +BUGS .Bugs is now deprecated, please use .Notes instead + Sometimes bugs have multiple paragraphs. + + Like this one. + + +BUGS +BUG(rsc) Sometimes bugs have multiple paragraphs. + + Like this one. + diff --git a/src/pkg/go/doc/testdata/bugpara.go b/src/pkg/go/doc/testdata/bugpara.go new file mode 100644 index 000000000..f5345a797 --- /dev/null +++ b/src/pkg/go/doc/testdata/bugpara.go @@ -0,0 +1,5 @@ +package bugpara + +// BUG(rsc): Sometimes bugs have multiple paragraphs. +// +// Like this one. diff --git a/src/pkg/go/doc/testdata/template.txt b/src/pkg/go/doc/testdata/template.txt index 26482f7c2..1b0738261 100644 --- a/src/pkg/go/doc/testdata/template.txt +++ b/src/pkg/go/doc/testdata/template.txt @@ -61,8 +61,8 @@ TYPES */}}{{with .Bugs}} BUGS .Bugs is now deprecated, please use .Notes instead -{{range .}} {{synopsis .}} +{{range .}}{{indent "\t" .}} {{end}}{{end}}{{with .Notes}}{{range $marker, $content := .}} {{$marker}}S -{{range $content}} {{synopsis .Body}} (uid: {{.UID}}) +{{range $content}}{{$marker}}({{.UID}}){{indent "\t" .Body}} {{end}}{{end}}{{end}}
\ No newline at end of file diff --git a/src/pkg/go/doc/testdata/testing.0.golden b/src/pkg/go/doc/testdata/testing.0.golden index 15a903986..f8348f1ac 100644 --- a/src/pkg/go/doc/testdata/testing.0.golden +++ b/src/pkg/go/doc/testdata/testing.0.golden @@ -57,7 +57,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *B) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *B) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). @@ -136,7 +136,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *T) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *T) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). diff --git a/src/pkg/go/doc/testdata/testing.1.golden b/src/pkg/go/doc/testdata/testing.1.golden index ffdb5c3b5..282bb1015 100644 --- a/src/pkg/go/doc/testdata/testing.1.golden +++ b/src/pkg/go/doc/testdata/testing.1.golden @@ -130,7 +130,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *B) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *B) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). @@ -232,7 +232,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *T) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *T) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). @@ -278,7 +278,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *common) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *common) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). diff --git a/src/pkg/go/doc/testdata/testing.2.golden b/src/pkg/go/doc/testdata/testing.2.golden index 15a903986..f8348f1ac 100644 --- a/src/pkg/go/doc/testdata/testing.2.golden +++ b/src/pkg/go/doc/testdata/testing.2.golden @@ -57,7 +57,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *B) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *B) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). @@ -136,7 +136,7 @@ TYPES // FailNow marks the function as having failed and stops its ... func (c *T) FailNow() - // Failed returns whether the function has failed. + // Failed reports whether the function has failed. func (c *T) Failed() bool // Fatal is equivalent to Log() followed by FailNow(). diff --git a/src/pkg/go/doc/testdata/testing.go b/src/pkg/go/doc/testdata/testing.go index c2499ad77..93ed494c3 100644 --- a/src/pkg/go/doc/testdata/testing.go +++ b/src/pkg/go/doc/testdata/testing.go @@ -130,7 +130,7 @@ type T struct { // Fail marks the function as having failed but continues execution. func (c *common) Fail() { c.failed = true } -// Failed returns whether the function has failed. +// Failed reports whether the function has failed. func (c *common) Failed() bool { return c.failed } // FailNow marks the function as having failed and stops its execution. diff --git a/src/pkg/go/format/format_test.go b/src/pkg/go/format/format_test.go index 7d7940bb5..93f099247 100644 --- a/src/pkg/go/format/format_test.go +++ b/src/pkg/go/format/format_test.go @@ -90,7 +90,6 @@ var tests = []string{ "\n\t\t\n\n\t\t\tx := 0\n\t\t\tconst s = `\nfoo\n`\n\n\n", // no indentation inside raw strings // erroneous programs - "ERRORvar x", "ERROR1 + 2 +", "ERRORx := 0", } diff --git a/src/pkg/go/parser/interface.go b/src/pkg/go/parser/interface.go index 149257ca6..0f83ca931 100644 --- a/src/pkg/go/parser/interface.go +++ b/src/pkg/go/parser/interface.go @@ -15,6 +15,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" ) // If src != nil, readSource converts src to a []byte if possible; @@ -115,11 +116,13 @@ func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode) return } -// ParseDir calls ParseFile for the files in the directory specified by path and -// returns a map of package name -> package AST with all the packages found. If -// filter != nil, only the files with os.FileInfo entries passing through the filter -// are considered. The mode bits are passed to ParseFile unchanged. Position -// information is recorded in the file set fset. +// ParseDir calls ParseFile for all files with names ending in ".go" in the +// directory specified by path and returns a map of package name -> package +// AST with all the packages found. +// +// If filter != nil, only the files with os.FileInfo entries passing through +// the filter (and ending in ".go") are considered. The mode bits are passed +// to ParseFile unchanged. Position information is recorded in fset. // // If the directory couldn't be read, a nil map and the respective error are // returned. If a parse error occurred, a non-nil but incomplete map and the @@ -139,7 +142,7 @@ func ParseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool, m pkgs = make(map[string]*ast.Package) for _, d := range list { - if filter == nil || filter(d) { + if strings.HasSuffix(d.Name(), ".go") && (filter == nil || filter(d)) { filename := filepath.Join(path, d.Name()) if src, err := ParseFile(fset, filename, nil, mode); err == nil { name := src.Name.Name diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go index f4a690a6f..c4523318f 100644 --- a/src/pkg/go/parser/parser.go +++ b/src/pkg/go/parser/parser.go @@ -64,7 +64,7 @@ type parser struct { } func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) { - p.file = fset.AddFile(filename, fset.Base(), len(src)) + p.file = fset.AddFile(filename, -1, len(src)) var m scanner.Mode if mode&ParseComments != 0 { m = scanner.ScanComments @@ -1150,7 +1150,7 @@ func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { defer un(trace(p, "TypeAssertion")) } - p.expect(token.LPAREN) + lparen := p.expect(token.LPAREN) var typ ast.Expr if p.tok == token.TYPE { // type switch: typ == nil @@ -1158,9 +1158,9 @@ func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { } else { typ = p.parseType() } - p.expect(token.RPAREN) + rparen := p.expect(token.RPAREN) - return &ast.TypeAssertExpr{X: x, Type: typ} + return &ast.TypeAssertExpr{X: x, Type: typ, Lparen: lparen, Rparen: rparen} } func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { @@ -1170,25 +1170,27 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { lbrack := p.expect(token.LBRACK) p.exprLev++ - var low, high ast.Expr - isSlice := false + var index [3]ast.Expr // change the 3 to 2 to disable slice expressions w/ cap if p.tok != token.COLON { - low = p.parseRhs() + index[0] = p.parseRhs() } - if p.tok == token.COLON { - isSlice = true + ncolons := 0 + for p.tok == token.COLON && ncolons < len(index)-1 { p.next() - if p.tok != token.RBRACK { - high = p.parseRhs() + ncolons++ + if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF { + index[ncolons] = p.parseRhs() } } p.exprLev-- rbrack := p.expect(token.RBRACK) - if isSlice { - return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: low, High: high, Rbrack: rbrack} + if ncolons > 0 { + // slice expression + return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: ncolons == 2, Rbrack: rbrack} } - return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: low, Rbrack: rbrack} + + return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack} } func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { @@ -1406,7 +1408,7 @@ L: } switch p.tok { case token.IDENT: - x = p.parseSelector(p.checkExpr(x)) + x = p.parseSelector(p.checkExprOrType(x)) case token.LPAREN: x = p.parseTypeAssertion(p.checkExpr(x)) default: @@ -2145,12 +2147,13 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as ident = p.parseIdent() } - var path *ast.BasicLit + pos := p.pos + var path string if p.tok == token.STRING { - if !isValidImport(p.lit) { - p.error(p.pos, "invalid import path: "+p.lit) + path = p.lit + if !isValidImport(path) { + p.error(pos, "invalid import path: "+path) } - path = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} p.next() } else { p.expect(token.STRING) // use expect() error handling @@ -2161,7 +2164,7 @@ func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) as spec := &ast.ImportSpec{ Doc: doc, Name: ident, - Path: path, + Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path}, Comment: p.lineComment, } p.imports = append(p.imports, spec) @@ -2177,8 +2180,9 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota idents := p.parseIdentList() typ := p.tryType() var values []ast.Expr - if p.tok == token.ASSIGN || keyword == token.CONST && (typ != nil || iota == 0) || keyword == token.VAR && typ == nil { - p.expect(token.ASSIGN) + // always permit optional initialization for more tolerant parsing + if p.tok == token.ASSIGN { + p.next() values = p.parseRhsList() } p.expectSemi() // call before accessing p.linecomment @@ -2381,7 +2385,7 @@ func (p *parser) parseFile() *ast.File { // Go spec: The package clause is not a declaration; // the package name does not appear in any scope. ident := p.parseIdent() - if ident.Name == "_" { + if ident.Name == "_" && p.mode&DeclarationErrors != 0 { p.error(p.pos, "invalid package name _") } p.expectSemi() diff --git a/src/pkg/go/parser/parser_test.go b/src/pkg/go/parser/parser_test.go index 48813d106..0a34b7e50 100644 --- a/src/pkg/go/parser/parser_test.go +++ b/src/pkg/go/parser/parser_test.go @@ -34,13 +34,12 @@ func TestParse(t *testing.T) { func nameFilter(filename string) bool { switch filename { - case "parser.go": - case "interface.go": - case "parser_test.go": - default: - return false + case "parser.go", "interface.go", "parser_test.go": + return true + case "parser.go.orig": + return true // permit but should be ignored by ParseDir } - return true + return false } func dirFilter(f os.FileInfo) bool { return nameFilter(f.Name()) } @@ -51,14 +50,17 @@ func TestParseDir(t *testing.T) { if err != nil { t.Fatalf("ParseDir(%s): %v", path, err) } - if len(pkgs) != 1 { - t.Errorf("incorrect number of packages: %d", len(pkgs)) + if n := len(pkgs); n != 1 { + t.Errorf("got %d packages; want 1", n) } pkg := pkgs["parser"] if pkg == nil { t.Errorf(`package "parser" not found`) return } + if n := len(pkg.Files); n != 3 { + t.Errorf("got %d package files; want 3", n) + } for filename := range pkg.Files { if !nameFilter(filename) { t.Errorf("unexpected package file: %s", filename) diff --git a/src/pkg/go/parser/short_test.go b/src/pkg/go/parser/short_test.go index 62277c0d2..0ef0c560c 100644 --- a/src/pkg/go/parser/short_test.go +++ b/src/pkg/go/parser/short_test.go @@ -33,6 +33,8 @@ var valids = []string{ `package p; func f() { if ; true {} };`, `package p; func f() { switch ; {} };`, `package p; func f() { for _ = range "foo" + "bar" {} };`, + `package p; func f() { var s []int; g(s[:], s[i:], s[:j], s[i:j], s[i:j:k], s[:j:k]) };`, + `package p; var ( _ = (struct {*T}).m; _ = (interface {T}).m )`, } func TestValid(t *testing.T) { @@ -46,7 +48,6 @@ var invalids = []string{ `package p; func f() { if { /* ERROR "expected operand" */ } };`, `package p; func f() { if ; { /* ERROR "expected operand" */ } };`, `package p; func f() { if f(); { /* ERROR "expected operand" */ } };`, - `package p; const c; /* ERROR "expected '='" */`, `package p; func f() { if _ /* ERROR "expected condition" */ = range x; true {} };`, `package p; func f() { switch _ /* ERROR "expected condition" */ = range x; true {} };`, `package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`, @@ -74,6 +75,9 @@ var invalids = []string{ `package p; func f() { if x := g(); x = /* ERROR "expected '=='" */ 0 {}};`, `package p; func f() { _ = x = /* ERROR "expected '=='" */ 0 {}};`, `package p; func f() { _ = 1 == func()int { var x bool; x = x = /* ERROR "expected '=='" */ true; return x }() };`, + `package p; func f() { var s []int; _ = s[] /* ERROR "expected operand" */ };`, + `package p; func f() { var s []int; _ = s[::: /* ERROR "expected ']'" */ ] };`, + `package p; func f() { var s []int; _ = s[i:j:k: /* ERROR "expected ']'" */ l] };`, } func TestInvalid(t *testing.T) { diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go index 7cd068e22..583c6c370 100644 --- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -754,13 +754,13 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) { case *ast.TypeAssertExpr: p.expr1(x.X, token.HighestPrec, depth) - p.print(token.PERIOD, token.LPAREN) + p.print(token.PERIOD, x.Lparen, token.LPAREN) if x.Type != nil { p.expr(x.Type) } else { p.print(token.TYPE) } - p.print(token.RPAREN) + p.print(x.Rparen, token.RPAREN) case *ast.IndexExpr: // TODO(gri): should treat[] like parentheses and undo one level of depth @@ -773,17 +773,25 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) { // TODO(gri): should treat[] like parentheses and undo one level of depth p.expr1(x.X, token.HighestPrec, 1) p.print(x.Lbrack, token.LBRACK) - if x.Low != nil { - p.expr0(x.Low, depth+1) - } - // blanks around ":" if both sides exist and either side is a binary expression - if depth <= 1 && x.Low != nil && x.High != nil && (isBinary(x.Low) || isBinary(x.High)) { - p.print(blank, token.COLON, blank) - } else { - p.print(token.COLON) + indices := []ast.Expr{x.Low, x.High} + if x.Max != nil { + indices = append(indices, x.Max) } - if x.High != nil { - p.expr0(x.High, depth+1) + for i, y := range indices { + if i > 0 { + // blanks around ":" if both sides exist and either side is a binary expression + // TODO(gri) once we have committed a variant of a[i:j:k] we may want to fine- + // tune the formatting here + x := indices[i-1] + if depth <= 1 && x != nil && y != nil && (isBinary(x) || isBinary(y)) { + p.print(blank, token.COLON, blank) + } else { + p.print(token.COLON) + } + } + if y != nil { + p.expr0(y, depth+1) + } } p.print(x.Rbrack, token.RBRACK) diff --git a/src/pkg/go/printer/testdata/expressions.golden b/src/pkg/go/printer/testdata/expressions.golden index 4291c557c..fbe8275b3 100644 --- a/src/pkg/go/printer/testdata/expressions.golden +++ b/src/pkg/go/printer/testdata/expressions.golden @@ -114,6 +114,23 @@ func _() { x < y || z > 42 } +// slice expressions with cap +func _() { + _ = x[a:b:c] + _ = x[a:b : c+d] + _ = x[a : b+d : c] + _ = x[a : b+d : c+d] + _ = x[a+d : b:c] + _ = x[a+d : b : c+d] + _ = x[a+d : b+d : c] + _ = x[a+d : b+d : c+d] + + _ = x[:b:c] + _ = x[:b : c+d] + _ = x[:b+d : c] + _ = x[:b+d : c+d] +} + func _() { _ = a + b _ = a + b + c diff --git a/src/pkg/go/printer/testdata/expressions.input b/src/pkg/go/printer/testdata/expressions.input index 1ec12a050..f4d20fa0f 100644 --- a/src/pkg/go/printer/testdata/expressions.input +++ b/src/pkg/go/printer/testdata/expressions.input @@ -116,6 +116,23 @@ func _() { } +// slice expressions with cap +func _() { + _ = x[a:b:c] + _ = x[a:b:c+d] + _ = x[a:b+d:c] + _ = x[a:b+d:c+d] + _ = x[a+d:b:c] + _ = x[a+d:b:c+d] + _ = x[a+d:b+d:c] + _ = x[a+d:b+d:c+d] + + _ = x[:b:c] + _ = x[:b:c+d] + _ = x[:b+d:c] + _ = x[:b+d:c+d] +} + func _() { _ = a+b _ = a+b+c diff --git a/src/pkg/go/printer/testdata/expressions.raw b/src/pkg/go/printer/testdata/expressions.raw index 062900e07..97bc81dad 100644 --- a/src/pkg/go/printer/testdata/expressions.raw +++ b/src/pkg/go/printer/testdata/expressions.raw @@ -114,6 +114,23 @@ func _() { x < y || z > 42 } +// slice expressions with cap +func _() { + _ = x[a:b:c] + _ = x[a:b : c+d] + _ = x[a : b+d : c] + _ = x[a : b+d : c+d] + _ = x[a+d : b:c] + _ = x[a+d : b : c+d] + _ = x[a+d : b+d : c] + _ = x[a+d : b+d : c+d] + + _ = x[:b:c] + _ = x[:b : c+d] + _ = x[:b+d : c] + _ = x[:b+d : c+d] +} + func _() { _ = a + b _ = a + b + c diff --git a/src/pkg/go/token/position.go b/src/pkg/go/token/position.go index f5d999561..e6f0ae6a6 100644 --- a/src/pkg/go/token/position.go +++ b/src/pkg/go/token/position.go @@ -97,7 +97,7 @@ type File struct { size int // file size as provided to AddFile // lines and infos are protected by set.mutex - lines []int + lines []int // lines contains the offset of the first character for each line (the first entry is always 0) infos []lineInfo } @@ -136,6 +136,29 @@ func (f *File) AddLine(offset int) { f.set.mutex.Unlock() } +// MergeLine merges a line with the following line. It is akin to replacing +// the newline character at the end of the line with a space (to not change the +// remaining offsets). To obtain the line number, consult e.g. Position.Line. +// MergeLine will panic if given an invalid line number. +// +func (f *File) MergeLine(line int) { + if line <= 0 { + panic("illegal line number (line numbering starts at 1)") + } + f.set.mutex.Lock() + defer f.set.mutex.Unlock() + if line >= len(f.lines) { + panic("illegal line number") + } + // To merge the line numbered <line> with the line numbered <line+1>, + // we need to remove the entry in lines corresponding to the line + // numbered <line+1>. The entry in lines corresponding to the line + // numbered <line+1> is located at index <line>, since indices in lines + // are 0-based and line numbers are 1-based. + copy(f.lines[line:], f.lines[line+1:]) + f.lines = f.lines[:len(f.lines)-1] +} + // SetLines sets the line offsets for a file and returns true if successful. // The line offsets are the offsets of the first character of each line; // for instance for the content "ab\nc\n" the line offsets are {0, 3}. @@ -314,7 +337,8 @@ func (s *FileSet) Base() int { // AddFile adds a new file with a given filename, base offset, and file size // to the file set s and returns the file. Multiple files may have the same // name. The base offset must not be smaller than the FileSet's Base(), and -// size must not be negative. +// size must not be negative. As a special case, if a negative base is provided, +// the current value of the FileSet's Base() is used instead. // // Adding the file will set the file set's Base() value to base + size + 1 // as the minimum base value for the next file. The following relationship @@ -329,6 +353,9 @@ func (s *FileSet) Base() int { func (s *FileSet) AddFile(filename string, base, size int) *File { s.mutex.Lock() defer s.mutex.Unlock() + if base < 0 { + base = s.base + } if base < s.base || size < 0 { panic("illegal base or size") } diff --git a/src/pkg/go/token/position_test.go b/src/pkg/go/token/position_test.go index 1d36c2226..ef6cfd93c 100644 --- a/src/pkg/go/token/position_test.go +++ b/src/pkg/go/token/position_test.go @@ -167,7 +167,13 @@ func TestLineInfo(t *testing.T) { func TestFiles(t *testing.T) { fset := NewFileSet() for i, test := range tests { - fset.AddFile(test.filename, fset.Base(), test.size) + base := fset.Base() + if i%2 == 1 { + // Setting a negative base is equivalent to + // fset.Base(), so test some of each. + base = -1 + } + fset.AddFile(test.filename, base, test.size) j := 0 fset.Iterate(func(f *File) bool { if f.Name() != tests[j].filename { |