cuelang.org/go@v0.10.1/internal/golangorgx/tools/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  	type tokenFile118 struct {
    38  		_ *token.FileSet // deleted in go1.19
    39  		tokenFile119
    40  	}
    41  
    42  	type uP = unsafe.Pointer
    43  	switch unsafe.Sizeof(*file) {
    44  	case unsafe.Sizeof(tokenFile118{}):
    45  		var ptr *tokenFile118
    46  		*(*uP)(uP(&ptr)) = uP(file)
    47  		ptr.mu.Lock()
    48  		defer ptr.mu.Unlock()
    49  		return ptr.lines
    50  
    51  	case unsafe.Sizeof(tokenFile119{}):
    52  		var ptr *tokenFile119
    53  		*(*uP)(uP(&ptr)) = uP(file)
    54  		ptr.mu.Lock()
    55  		defer ptr.mu.Unlock()
    56  		return ptr.lines
    57  
    58  	default:
    59  		panic("unexpected token.File size")
    60  	}
    61  }
    62  
    63  // AddExistingFiles adds the specified files to the FileSet if they
    64  // are not already present. It panics if any pair of files in the
    65  // resulting FileSet would overlap.
    66  func AddExistingFiles(fset *token.FileSet, files []*token.File) {
    67  	// Punch through the FileSet encapsulation.
    68  	type tokenFileSet struct {
    69  		// This type remained essentially consistent from go1.16 to go1.21.
    70  		mutex sync.RWMutex
    71  		base  int
    72  		files []*token.File
    73  		_     *token.File // changed to atomic.Pointer[token.File] in go1.19
    74  	}
    75  
    76  	// If the size of token.FileSet changes, this will fail to compile.
    77  	const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{}))
    78  	var _ [-delta * delta]int
    79  
    80  	type uP = unsafe.Pointer
    81  	var ptr *tokenFileSet
    82  	*(*uP)(uP(&ptr)) = uP(fset)
    83  	ptr.mutex.Lock()
    84  	defer ptr.mutex.Unlock()
    85  
    86  	// Merge and sort.
    87  	newFiles := append(ptr.files, files...)
    88  	sort.Slice(newFiles, func(i, j int) bool {
    89  		return newFiles[i].Base() < newFiles[j].Base()
    90  	})
    91  
    92  	// Reject overlapping files.
    93  	// Discard adjacent identical files.
    94  	out := newFiles[:0]
    95  	for i, file := range newFiles {
    96  		if i > 0 {
    97  			prev := newFiles[i-1]
    98  			if file == prev {
    99  				continue
   100  			}
   101  			if prev.Base()+prev.Size()+1 > file.Base() {
   102  				panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)",
   103  					prev.Name(), prev.Base(), prev.Base()+prev.Size(),
   104  					file.Name(), file.Base(), file.Base()+file.Size()))
   105  			}
   106  		}
   107  		out = append(out, file)
   108  	}
   109  	newFiles = out
   110  
   111  	ptr.files = newFiles
   112  
   113  	// Advance FileSet.Base().
   114  	if len(newFiles) > 0 {
   115  		last := newFiles[len(newFiles)-1]
   116  		newBase := last.Base() + last.Size() + 1
   117  		if ptr.base < newBase {
   118  			ptr.base = newBase
   119  		}
   120  	}
   121  }
   122  
   123  // FileSetFor returns a new FileSet containing a sequence of new Files with
   124  // the same base, size, and line as the input files, for use in APIs that
   125  // require a FileSet.
   126  //
   127  // Precondition: the input files must be non-overlapping, and sorted in order
   128  // of their Base.
   129  func FileSetFor(files ...*token.File) *token.FileSet {
   130  	fset := token.NewFileSet()
   131  	for _, f := range files {
   132  		f2 := fset.AddFile(f.Name(), f.Base(), f.Size())
   133  		lines := GetLines(f)
   134  		f2.SetLines(lines)
   135  	}
   136  	return fset
   137  }
   138  
   139  // CloneFileSet creates a new FileSet holding all files in fset. It does not
   140  // create copies of the token.Files in fset: they are added to the resulting
   141  // FileSet unmodified.
   142  func CloneFileSet(fset *token.FileSet) *token.FileSet {
   143  	var files []*token.File
   144  	fset.Iterate(func(f *token.File) bool {
   145  		files = append(files, f)
   146  		return true
   147  	})
   148  	newFileSet := token.NewFileSet()
   149  	AddExistingFiles(newFileSet, files)
   150  	return newFileSet
   151  }