]> Cypherpunks repositories - gostls13.git/commitdiff
go/token: add FileSet.AddExistingFiles
authorAlan Donovan <adonovan@google.com>
Wed, 14 May 2025 19:22:36 +0000 (15:22 -0400)
committerAlan Donovan <adonovan@google.com>
Mon, 19 May 2025 18:26:48 +0000 (11:26 -0700)
+ 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>
api/next/73205.txt [new file with mode: 0644]
doc/next/6-stdlib/99-minor/go/token/73205.md [new file with mode: 0644]
src/go/token/position.go
src/go/token/position_test.go

diff --git a/api/next/73205.txt b/api/next/73205.txt
new file mode 100644 (file)
index 0000000..3cc2c09
--- /dev/null
@@ -0,0 +1 @@
+pkg go/token, method (*FileSet) AddExistingFiles(...*File) #73205
diff --git a/doc/next/6-stdlib/99-minor/go/token/73205.md b/doc/next/6-stdlib/99-minor/go/token/73205.md
new file mode 100644 (file)
index 0000000..d743663
--- /dev/null
@@ -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.
index 35ef14da8773da230f7584bb880663a4a94e0ef9..f5a43aecef4b2f244525eb14acd9e12fc224ae8c 100644 (file)
@@ -98,6 +98,9 @@ func (p Pos) IsValid() bool {
 
 // A File is a handle for a file belonging to a [FileSet].
 // 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 {
        name string // file name as provided to AddFile
        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
 }
 
+// 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
 // queries for its [Pos] interval yield a negative result.
 // This reduces the memory usage of a long-lived [FileSet] that
index 677a0a251d6e8cac42c865c41446e75eaef13bc1..51516b6ddd65a8f1f10c8068b7295665222c14f6 100644 (file)
@@ -8,6 +8,7 @@ import (
        "fmt"
        "math/rand"
        "slices"
+       "strings"
        "sync"
        "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()
+}