go/token: add FileSet.AddExistingFiles
+ test, doc, relnote Fixes #73205 Change-Id: Id3a4cc6290c55ffa518ad174a02ccca85e8636f7 Reviewed-on: https://go-review.googlesource.com/c/go/+/672875 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
11c86ddcb8
commit
972639fc4c
4 changed files with 113 additions and 0 deletions
1
api/next/73205.txt
Normal file
1
api/next/73205.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pkg go/token, method (*FileSet) AddExistingFiles(...*File) #73205
|
4
doc/next/6-stdlib/99-minor/go/token/73205.md
Normal file
4
doc/next/6-stdlib/99-minor/go/token/73205.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
The new [FileSet.AddExistingFiles] method enables existing Files to be
|
||||||
|
added to a FileSet, or a FileSet to be constructed for an arbitrary
|
||||||
|
set of Files, alleviating the problems associated with a single global
|
||||||
|
FileSet in long-lived applications.
|
|
@ -98,6 +98,9 @@ func (p Pos) IsValid() bool {
|
||||||
|
|
||||||
// A File is a handle for a file belonging to a [FileSet].
|
// A File is a handle for a file belonging to a [FileSet].
|
||||||
// A File has a name, size, and line offset table.
|
// A File has a name, size, and line offset table.
|
||||||
|
//
|
||||||
|
// Use [FileSet.AddFile] to create a File.
|
||||||
|
// A File may belong to more than one FileSet; see [FileSet.AddExistingFiles].
|
||||||
type File struct {
|
type File struct {
|
||||||
name string // file name as provided to AddFile
|
name string // file name as provided to AddFile
|
||||||
base int // Pos value range for this file is [base...base+size]
|
base int // Pos value range for this file is [base...base+size]
|
||||||
|
@ -489,6 +492,69 @@ func (s *FileSet) AddFile(filename string, base, size int) *File {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddExistingFiles adds the specified files to the
|
||||||
|
// FileSet if they are not already present.
|
||||||
|
// The caller must ensure that no pair of Files that
|
||||||
|
// would appear in the resulting FileSet overlap.
|
||||||
|
func (s *FileSet) AddExistingFiles(files ...*File) {
|
||||||
|
// This function cannot be implemented as:
|
||||||
|
//
|
||||||
|
// for _, file := range files {
|
||||||
|
// if prev := fset.File(token.Pos(file.Base())); prev != nil {
|
||||||
|
// if prev != file {
|
||||||
|
// panic("FileSet contains a different file at the same base")
|
||||||
|
// }
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// file2 := fset.AddFile(file.Name(), file.Base(), file.Size())
|
||||||
|
// file2.SetLines(file.Lines())
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// because all calls to AddFile must be in increasing order.
|
||||||
|
// AddExistingFilesFiles lets us augment an existing FileSet
|
||||||
|
// sequentially, so long as all sets of files have disjoint ranges.
|
||||||
|
// This approach also does not preserve line directives.
|
||||||
|
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
// Merge and sort.
|
||||||
|
newFiles := append(s.files, files...)
|
||||||
|
slices.SortFunc(newFiles, func(x, y *File) int {
|
||||||
|
return cmp.Compare(x.Base(), y.Base())
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reject overlapping files.
|
||||||
|
// Discard adjacent identical files.
|
||||||
|
out := newFiles[:0]
|
||||||
|
for i, file := range newFiles {
|
||||||
|
if i > 0 {
|
||||||
|
prev := newFiles[i-1]
|
||||||
|
if file == prev {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if prev.Base()+prev.Size()+1 > file.Base() {
|
||||||
|
panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)",
|
||||||
|
prev.Name(), prev.Base(), prev.Base()+prev.Size(),
|
||||||
|
file.Name(), file.Base(), file.Base()+file.Size()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out = append(out, file)
|
||||||
|
}
|
||||||
|
newFiles = out
|
||||||
|
|
||||||
|
s.files = newFiles
|
||||||
|
|
||||||
|
// Advance base.
|
||||||
|
if len(newFiles) > 0 {
|
||||||
|
last := newFiles[len(newFiles)-1]
|
||||||
|
newBase := last.Base() + last.Size() + 1
|
||||||
|
if s.base < newBase {
|
||||||
|
s.base = newBase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveFile removes a file from the [FileSet] so that subsequent
|
// RemoveFile removes a file from the [FileSet] so that subsequent
|
||||||
// queries for its [Pos] interval yield a negative result.
|
// queries for its [Pos] interval yield a negative result.
|
||||||
// This reduces the memory usage of a long-lived [FileSet] that
|
// This reduces the memory usage of a long-lived [FileSet] that
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -537,3 +538,44 @@ func TestIssue57490(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFileSet_AddExistingFiles(t *testing.T) {
|
||||||
|
fset := NewFileSet()
|
||||||
|
|
||||||
|
check := func(descr, want string) {
|
||||||
|
t.Helper()
|
||||||
|
if got := fsetString(fset); got != want {
|
||||||
|
t.Errorf("%s: got %s, want %s", descr, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileA := fset.AddFile("A", -1, 3)
|
||||||
|
fileB := fset.AddFile("B", -1, 5)
|
||||||
|
_ = fileB
|
||||||
|
check("after AddFile [AB]", "{A:1-4 B:5-10}")
|
||||||
|
|
||||||
|
fset.AddExistingFiles() // noop
|
||||||
|
check("after AddExistingFiles []", "{A:1-4 B:5-10}")
|
||||||
|
|
||||||
|
fileC := NewFileSet().AddFile("C", 100, 5)
|
||||||
|
fileD := NewFileSet().AddFile("D", 200, 5)
|
||||||
|
fset.AddExistingFiles(fileC, fileA, fileD, fileC)
|
||||||
|
check("after AddExistingFiles [CADC]", "{A:1-4 B:5-10 C:100-105 D:200-205}")
|
||||||
|
|
||||||
|
fileE := fset.AddFile("E", -1, 3)
|
||||||
|
_ = fileE
|
||||||
|
check("after AddFile [E]", "{A:1-4 B:5-10 C:100-105 D:200-205 E:206-209}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func fsetString(fset *FileSet) string {
|
||||||
|
var buf strings.Builder
|
||||||
|
buf.WriteRune('{')
|
||||||
|
sep := ""
|
||||||
|
fset.Iterate(func(f *File) bool {
|
||||||
|
fmt.Fprintf(&buf, "%s%s:%d-%d", sep, f.Name(), f.Base(), f.Base()+f.Size())
|
||||||
|
sep = " "
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
buf.WriteRune('}')
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue