golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/tokeninternal/tokeninternal.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // package tokeninternal provides access to some internal features of the token
     6  // package.
     7  package tokeninternal
     8  
     9  import (
    10  	"fmt"
    11  	"go/token"
    12  	"sort"
    13  	"sync"
    14  	"unsafe"
    15  )
    16  
    17  // GetLines returns the table of line-start offsets from a token.File.
    18  func GetLines(file *token.File) []int {
    19  	// token.File has a Lines method on Go 1.21 and later.
    20  	if file, ok := (interface{})(file).(interface{ Lines() []int }); ok {
    21  		return file.Lines()
    22  	}
    23  
    24  	// This declaration must match that of token.File.
    25  	// This creates a risk of dependency skew.
    26  	// For now we check that the size of the two
    27  	// declarations is the same, on the (fragile) assumption
    28  	// that future changes would add fields.
    29  	type tokenFile119 struct {
    30  		_     string
    31  		_     int
    32  		_     int
    33  		mu    sync.Mutex // we're not complete monsters
    34  		lines []int
    35  		_     []struct{}
    36  	}
    37  
    38  	if unsafe.Sizeof(*file) != unsafe.Sizeof(tokenFile119{}) {
    39  		panic("unexpected token.File size")
    40  	}
    41  	var ptr *tokenFile119
    42  	type uP = unsafe.Pointer
    43  	*(*uP)(uP(&ptr)) = uP(file)
    44  	ptr.mu.Lock()
    45  	defer ptr.mu.Unlock()
    46  	return ptr.lines
    47  }
    48  
    49  // AddExistingFiles adds the specified files to the FileSet if they
    50  // are not already present. It panics if any pair of files in the
    51  // resulting FileSet would overlap.
    52  func AddExistingFiles(fset *token.FileSet, files []*token.File) {
    53  	// Punch through the FileSet encapsulation.
    54  	type tokenFileSet struct {
    55  		// This type remained essentially consistent from go1.16 to go1.21.
    56  		mutex sync.RWMutex
    57  		base  int
    58  		files []*token.File
    59  		_     *token.File // changed to atomic.Pointer[token.File] in go1.19
    60  	}
    61  
    62  	// If the size of token.FileSet changes, this will fail to compile.
    63  	const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{}))
    64  	var _ [-delta * delta]int
    65  
    66  	type uP = unsafe.Pointer
    67  	var ptr *tokenFileSet
    68  	*(*uP)(uP(&ptr)) = uP(fset)
    69  	ptr.mutex.Lock()
    70  	defer ptr.mutex.Unlock()
    71  
    72  	// Merge and sort.
    73  	newFiles := append(ptr.files, files...)
    74  	sort.Slice(newFiles, func(i, j int) bool {
    75  		return newFiles[i].Base() < newFiles[j].Base()
    76  	})
    77  
    78  	// Reject overlapping files.
    79  	// Discard adjacent identical files.
    80  	out := newFiles[:0]
    81  	for i, file := range newFiles {
    82  		if i > 0 {
    83  			prev := newFiles[i-1]
    84  			if file == prev {
    85  				continue
    86  			}
    87  			if prev.Base()+prev.Size()+1 > file.Base() {
    88  				panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)",
    89  					prev.Name(), prev.Base(), prev.Base()+prev.Size(),
    90  					file.Name(), file.Base(), file.Base()+file.Size()))
    91  			}
    92  		}
    93  		out = append(out, file)
    94  	}
    95  	newFiles = out
    96  
    97  	ptr.files = newFiles
    98  
    99  	// Advance FileSet.Base().
   100  	if len(newFiles) > 0 {
   101  		last := newFiles[len(newFiles)-1]
   102  		newBase := last.Base() + last.Size() + 1
   103  		if ptr.base < newBase {
   104  			ptr.base = newBase
   105  		}
   106  	}
   107  }
   108  
   109  // FileSetFor returns a new FileSet containing a sequence of new Files with
   110  // the same base, size, and line as the input files, for use in APIs that
   111  // require a FileSet.
   112  //
   113  // Precondition: the input files must be non-overlapping, and sorted in order
   114  // of their Base.
   115  func FileSetFor(files ...*token.File) *token.FileSet {
   116  	fset := token.NewFileSet()
   117  	for _, f := range files {
   118  		f2 := fset.AddFile(f.Name(), f.Base(), f.Size())
   119  		lines := GetLines(f)
   120  		f2.SetLines(lines)
   121  	}
   122  	return fset
   123  }
   124  
   125  // CloneFileSet creates a new FileSet holding all files in fset. It does not
   126  // create copies of the token.Files in fset: they are added to the resulting
   127  // FileSet unmodified.
   128  func CloneFileSet(fset *token.FileSet) *token.FileSet {
   129  	var files []*token.File
   130  	fset.Iterate(func(f *token.File) bool {
   131  		files = append(files, f)
   132  		return true
   133  	})
   134  	newFileSet := token.NewFileSet()
   135  	AddExistingFiles(newFileSet, files)
   136  	return newFileSet
   137  }