diff --git a/BUILD.bazel b/BUILD.bazel
index 0036873cc5..aeb3e553b9 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -197,6 +197,25 @@ nogo(
"//tools/analyzers/logcapitalization:go_default_library",
"//tools/analyzers/logruswitherror:go_default_library",
"//tools/analyzers/maligned:go_default_library",
+ "//tools/analyzers/modernize/any:go_default_library",
+ "//tools/analyzers/modernize/appendclipped:go_default_library",
+ "//tools/analyzers/modernize/bloop:go_default_library",
+ "//tools/analyzers/modernize/fmtappendf:go_default_library",
+ "//tools/analyzers/modernize/forvar:go_default_library",
+ "//tools/analyzers/modernize/mapsloop:go_default_library",
+ "//tools/analyzers/modernize/minmax:go_default_library",
+ #"//tools/analyzers/modernize/newexpr:go_default_library", # Disabled until go 1.26.
+ "//tools/analyzers/modernize/omitzero:go_default_library",
+ "//tools/analyzers/modernize/rangeint:go_default_library",
+ "//tools/analyzers/modernize/reflecttypefor:go_default_library",
+ "//tools/analyzers/modernize/slicescontains:go_default_library",
+ #"//tools/analyzers/modernize/slicesdelete:go_default_library", # Disabled, see https://go.dev/issue/73686
+ "//tools/analyzers/modernize/slicessort:go_default_library",
+ "//tools/analyzers/modernize/stringsbuilder:go_default_library",
+ "//tools/analyzers/modernize/stringscutprefix:go_default_library",
+ "//tools/analyzers/modernize/stringsseq:go_default_library",
+ "//tools/analyzers/modernize/testingcontext:go_default_library",
+ "//tools/analyzers/modernize/waitgroup:go_default_library",
"//tools/analyzers/nop:go_default_library",
"//tools/analyzers/nopanic:go_default_library",
"//tools/analyzers/properpermissions:go_default_library",
diff --git a/WORKSPACE b/WORKSPACE
index 9c774b7358..db1aa9daa9 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -205,6 +205,26 @@ prysm_image_deps()
load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
+# Override golang.org/x/tools to use v0.38.0 instead of v0.30.0
+# This is necessary as this dependency is required by rules_go and they do not accept dependency
+# update PRs. Instead, they ask downstream projects to override the dependency. To generate the
+# patches or update this dependency again, check out the rules_go repo then run the releaser tool.
+# bazel run //go/tools/releaser -- upgrade-dep -mirror=false org_golang_x_tools
+# Copy the patches and http_archive updates from rules_go here.
+http_archive(
+ name = "org_golang_x_tools",
+ patch_args = ["-p1"],
+ patches = [
+ "//third_party:org_golang_x_tools-deletegopls.patch",
+ "//third_party:org_golang_x_tools-gazelle.patch",
+ ],
+ sha256 = "8509908cd7fc35aa09ff49d8494e4fd25bab9e6239fbf57e0d8344f6bec5802b",
+ strip_prefix = "tools-0.38.0",
+ urls = [
+ "https://github.com/golang/tools/archive/refs/tags/v0.38.0.zip",
+ ],
+)
+
go_rules_dependencies()
go_register_toolchains(
diff --git a/api/apiutil/header.go b/api/apiutil/header.go
index 4ef6ca3cb2..37fdf988e0 100644
--- a/api/apiutil/header.go
+++ b/api/apiutil/header.go
@@ -56,7 +56,7 @@ func ParseAccept(header string) []mediaRange {
}
var out []mediaRange
- for _, field := range strings.Split(header, ",") {
+ for field := range strings.SplitSeq(header, ",") {
if r, ok := parseMediaRange(field); ok {
out = append(out, r)
}
diff --git a/api/client/builder/client.go b/api/client/builder/client.go
index ca32e11bb6..6139a95629 100644
--- a/api/client/builder/client.go
+++ b/api/client/builder/client.go
@@ -421,7 +421,7 @@ func (c *Client) RegisterValidator(ctx context.Context, svr []*ethpb.SignedValid
func jsonValidatorRegisterRequest(svr []*ethpb.SignedValidatorRegistrationV1) ([]byte, error) {
vs := make([]*structs.SignedValidatorRegistration, len(svr))
- for i := 0; i < len(svr); i++ {
+ for i := range svr {
vs[i] = structs.SignedValidatorRegistrationFromConsensus(svr[i])
}
body, err := json.Marshal(vs)
diff --git a/api/client/builder/types.go b/api/client/builder/types.go
index e0d6316298..d0175e48ff 100644
--- a/api/client/builder/types.go
+++ b/api/client/builder/types.go
@@ -121,7 +121,7 @@ func (s *Uint64String) UnmarshalText(t []byte) error {
// MarshalText returns a byte representation of the text from Uint64String.
func (s Uint64String) MarshalText() ([]byte, error) {
- return []byte(fmt.Sprintf("%d", s)), nil
+ return fmt.Appendf(nil, "%d", s), nil
}
// VersionResponse is a JSON representation of a field in the builder API header response.
diff --git a/api/grpc/grpcutils.go b/api/grpc/grpcutils.go
index d0dd8be5e4..54ac97958c 100644
--- a/api/grpc/grpcutils.go
+++ b/api/grpc/grpcutils.go
@@ -15,7 +15,7 @@ import (
func LogRequests(
ctx context.Context,
method string, req,
- reply interface{},
+ reply any,
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
diff --git a/api/server/structs/endpoints_config.go b/api/server/structs/endpoints_config.go
index 12e9adf650..1b40522a71 100644
--- a/api/server/structs/endpoints_config.go
+++ b/api/server/structs/endpoints_config.go
@@ -14,5 +14,5 @@ type GetForkScheduleResponse struct {
}
type GetSpecResponse struct {
- Data interface{} `json:"data"`
+ Data any `json:"data"`
}
diff --git a/async/abool/abool_test.go b/async/abool/abool_test.go
index f870206abd..8447ed5e51 100644
--- a/async/abool/abool_test.go
+++ b/async/abool/abool_test.go
@@ -93,9 +93,9 @@ func TestToggleMultipleTimes(t *testing.T) {
v := New()
pre := !v.IsSet()
- for i := 0; i < 100; i++ {
+ for i := range 100 {
v.SetTo(false)
- for j := 0; j < i; j++ {
+ for range i {
pre = v.Toggle()
}
@@ -149,7 +149,7 @@ func TestRace(t *testing.T) {
// Writer
go func() {
- for i := 0; i < repeat; i++ {
+ for range repeat {
v.Set()
wg.Done()
}
@@ -157,7 +157,7 @@ func TestRace(t *testing.T) {
// Reader
go func() {
- for i := 0; i < repeat; i++ {
+ for range repeat {
v.IsSet()
wg.Done()
}
@@ -165,7 +165,7 @@ func TestRace(t *testing.T) {
// Writer
go func() {
- for i := 0; i < repeat; i++ {
+ for range repeat {
v.UnSet()
wg.Done()
}
@@ -173,7 +173,7 @@ func TestRace(t *testing.T) {
// Reader And Writer
go func() {
- for i := 0; i < repeat; i++ {
+ for range repeat {
v.Toggle()
wg.Done()
}
@@ -198,8 +198,8 @@ func ExampleAtomicBool() {
func BenchmarkMutexRead(b *testing.B) {
var m sync.RWMutex
var v bool
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
m.RLock()
_ = v
m.RUnlock()
@@ -208,16 +208,16 @@ func BenchmarkMutexRead(b *testing.B) {
func BenchmarkAtomicValueRead(b *testing.B) {
var v atomic.Value
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_ = v.Load() != nil
}
}
func BenchmarkAtomicBoolRead(b *testing.B) {
v := New()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_ = v.IsSet()
}
}
@@ -227,8 +227,8 @@ func BenchmarkAtomicBoolRead(b *testing.B) {
func BenchmarkMutexWrite(b *testing.B) {
var m sync.RWMutex
var v bool
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
m.RLock()
v = true
m.RUnlock()
@@ -239,16 +239,16 @@ func BenchmarkMutexWrite(b *testing.B) {
func BenchmarkAtomicValueWrite(b *testing.B) {
var v atomic.Value
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
v.Store(true)
}
}
func BenchmarkAtomicBoolWrite(b *testing.B) {
v := New()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
v.Set()
}
}
@@ -258,8 +258,8 @@ func BenchmarkAtomicBoolWrite(b *testing.B) {
func BenchmarkMutexCAS(b *testing.B) {
var m sync.RWMutex
var v bool
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
m.Lock()
if !v {
v = true
@@ -270,8 +270,8 @@ func BenchmarkMutexCAS(b *testing.B) {
func BenchmarkAtomicBoolCAS(b *testing.B) {
v := New()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
v.SetToIf(false, true)
}
}
@@ -281,8 +281,8 @@ func BenchmarkAtomicBoolCAS(b *testing.B) {
func BenchmarkMutexToggle(b *testing.B) {
var m sync.RWMutex
var v bool
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
m.Lock()
v = !v
m.Unlock()
@@ -291,8 +291,8 @@ func BenchmarkMutexToggle(b *testing.B) {
func BenchmarkAtomicBoolToggle(b *testing.B) {
v := New()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
v.Toggle()
}
}
diff --git a/async/benchmark_test.go b/async/benchmark_test.go
index 83530091b8..6ce1589b4e 100644
--- a/async/benchmark_test.go
+++ b/async/benchmark_test.go
@@ -21,7 +21,7 @@ const (
func init() {
input = make([][]byte, benchmarkElements)
- for i := 0; i < benchmarkElements; i++ {
+ for i := range benchmarkElements {
input[i] = make([]byte, benchmarkElementSize)
_, err := rand.Read(input[i])
if err != nil {
@@ -35,7 +35,7 @@ func hash(input [][]byte) [][]byte {
output := make([][]byte, len(input))
for i := range input {
copy(output, input)
- for j := 0; j < benchmarkHashRuns; j++ {
+ for range benchmarkHashRuns {
hash := sha256.Sum256(output[i])
output[i] = hash[:]
}
@@ -44,15 +44,15 @@ func hash(input [][]byte) [][]byte {
}
func BenchmarkHash(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
hash(input)
}
}
func BenchmarkHashMP(b *testing.B) {
output := make([][]byte, len(input))
- for i := 0; i < b.N; i++ {
- workerResults, err := async.Scatter(len(input), func(offset int, entries int, _ *sync.RWMutex) (interface{}, error) {
+ for b.Loop() {
+ workerResults, err := async.Scatter(len(input), func(offset int, entries int, _ *sync.RWMutex) (any, error) {
return hash(input[offset : offset+entries]), nil
})
require.NoError(b, err)
diff --git a/async/debounce.go b/async/debounce.go
index 338fbb3eac..4d27acb972 100644
--- a/async/debounce.go
+++ b/async/debounce.go
@@ -7,7 +7,7 @@ import (
// Debounce events fired over a channel by a specified duration, ensuring no events
// are handled until a certain interval of time has passed.
-func Debounce(ctx context.Context, interval time.Duration, eventsChan <-chan interface{}, handler func(interface{})) {
+func Debounce(ctx context.Context, interval time.Duration, eventsChan <-chan any, handler func(any)) {
var timer *time.Timer
defer func() {
if timer != nil {
diff --git a/async/debounce_test.go b/async/debounce_test.go
index f9272d646a..2e3738bf72 100644
--- a/async/debounce_test.go
+++ b/async/debounce_test.go
@@ -14,7 +14,7 @@ import (
)
func TestDebounce_NoEvents(t *testing.T) {
- eventsChan := make(chan interface{}, 100)
+ eventsChan := make(chan any, 100)
ctx, cancel := context.WithCancel(t.Context())
interval := time.Second
timesHandled := int32(0)
@@ -26,7 +26,7 @@ func TestDebounce_NoEvents(t *testing.T) {
})
}()
go func() {
- async.Debounce(ctx, interval, eventsChan, func(event interface{}) {
+ async.Debounce(ctx, interval, eventsChan, func(event any) {
atomic.AddInt32(×Handled, 1)
})
wg.Done()
@@ -38,7 +38,7 @@ func TestDebounce_NoEvents(t *testing.T) {
}
func TestDebounce_CtxClosing(t *testing.T) {
- eventsChan := make(chan interface{}, 100)
+ eventsChan := make(chan any, 100)
ctx, cancel := context.WithCancel(t.Context())
interval := time.Second
timesHandled := int32(0)
@@ -62,7 +62,7 @@ func TestDebounce_CtxClosing(t *testing.T) {
})
}()
go func() {
- async.Debounce(ctx, interval, eventsChan, func(event interface{}) {
+ async.Debounce(ctx, interval, eventsChan, func(event any) {
atomic.AddInt32(×Handled, 1)
})
wg.Done()
@@ -74,14 +74,14 @@ func TestDebounce_CtxClosing(t *testing.T) {
}
func TestDebounce_SingleHandlerInvocation(t *testing.T) {
- eventsChan := make(chan interface{}, 100)
+ eventsChan := make(chan any, 100)
ctx, cancel := context.WithCancel(t.Context())
interval := time.Second
timesHandled := int32(0)
- go async.Debounce(ctx, interval, eventsChan, func(event interface{}) {
+ go async.Debounce(ctx, interval, eventsChan, func(event any) {
atomic.AddInt32(×Handled, 1)
})
- for i := 0; i < 100; i++ {
+ for range 100 {
eventsChan <- struct{}{}
}
// We should expect 100 rapid fire changes to only have caused
@@ -92,14 +92,14 @@ func TestDebounce_SingleHandlerInvocation(t *testing.T) {
}
func TestDebounce_MultipleHandlerInvocation(t *testing.T) {
- eventsChan := make(chan interface{}, 100)
+ eventsChan := make(chan any, 100)
ctx, cancel := context.WithCancel(t.Context())
interval := time.Second
timesHandled := int32(0)
- go async.Debounce(ctx, interval, eventsChan, func(event interface{}) {
+ go async.Debounce(ctx, interval, eventsChan, func(event any) {
atomic.AddInt32(×Handled, 1)
})
- for i := 0; i < 100; i++ {
+ for range 100 {
eventsChan <- struct{}{}
}
require.Equal(t, int32(0), atomic.LoadInt32(×Handled), "Events must prevent from handler execution")
diff --git a/async/event/example_scope_test.go b/async/event/example_scope_test.go
index 510da227be..6df4a09065 100644
--- a/async/event/example_scope_test.go
+++ b/async/event/example_scope_test.go
@@ -93,9 +93,7 @@ func ExampleSubscriptionScope() {
// Run a subscriber in the background.
divsub := app.SubscribeResults('/', divs)
mulsub := app.SubscribeResults('*', muls)
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
defer fmt.Println("subscriber exited")
defer divsub.Unsubscribe()
defer mulsub.Unsubscribe()
@@ -111,7 +109,7 @@ func ExampleSubscriptionScope() {
return
}
}
- }()
+ })
// Interact with the app.
app.Calc('/', 22, 11)
diff --git a/async/event/example_subscription_test.go b/async/event/example_subscription_test.go
index fe8c65eb68..ed04c1c169 100644
--- a/async/event/example_subscription_test.go
+++ b/async/event/example_subscription_test.go
@@ -26,7 +26,7 @@ func ExampleNewSubscription() {
// Create a subscription that sends 10 integers on ch.
ch := make(chan int)
sub := event.NewSubscription(func(quit <-chan struct{}) error {
- for i := 0; i < 10; i++ {
+ for i := range 10 {
select {
case ch <- i:
case <-quit:
diff --git a/async/event/interface.go b/async/event/interface.go
index d54f9fd321..ed482544eb 100644
--- a/async/event/interface.go
+++ b/async/event/interface.go
@@ -3,6 +3,6 @@ package event
// SubscriberSender is an abstract representation of an *event.Feed
// to use in describing types that accept or return an *event.Feed.
type SubscriberSender interface {
- Subscribe(channel interface{}) Subscription
- Send(value interface{}) (nsent int)
+ Subscribe(channel any) Subscription
+ Send(value any) (nsent int)
}
diff --git a/async/event/subscription_test.go b/async/event/subscription_test.go
index e3f04d1661..58da7ada87 100644
--- a/async/event/subscription_test.go
+++ b/async/event/subscription_test.go
@@ -30,7 +30,7 @@ var errInts = errors.New("error in subscribeInts")
func subscribeInts(max, fail int, c chan<- int) Subscription {
return NewSubscription(func(quit <-chan struct{}) error {
- for i := 0; i < max; i++ {
+ for i := range max {
if i >= fail {
return errInts
}
@@ -50,7 +50,7 @@ func TestNewSubscriptionError(t *testing.T) {
channel := make(chan int)
sub := subscribeInts(10, 2, channel)
loop:
- for want := 0; want < 10; want++ {
+ for want := range 10 {
select {
case got := <-channel:
require.Equal(t, want, got)
diff --git a/async/multilock_test.go b/async/multilock_test.go
index 571460a373..6a64d6fa70 100644
--- a/async/multilock_test.go
+++ b/async/multilock_test.go
@@ -107,15 +107,13 @@ func TestLockUnlock(_ *testing.T) {
func TestLockUnlock_CleansUnused(t *testing.T) {
var wg sync.WaitGroup
- wg.Add(1)
- go func() {
+ wg.Go(func() {
lock := NewMultilock("dog", "cat", "owl")
lock.Lock()
assert.Equal(t, 3, len(locks.list))
lock.Unlock()
- wg.Done()
- }()
+ })
wg.Wait()
// We expect that unlocking completely cleared the locks list
// given all 3 lock keys were unused at time of unlock.
diff --git a/async/scatter.go b/async/scatter.go
index ac3b743d80..8293965a16 100644
--- a/async/scatter.go
+++ b/async/scatter.go
@@ -9,14 +9,14 @@ import (
// WorkerResults are the results of a scatter worker.
type WorkerResults struct {
Offset int
- Extent interface{}
+ Extent any
}
// Scatter scatters a computation across multiple goroutines.
// This breaks the task in to a number of chunks and executes those chunks in parallel with the function provided.
// Results returned are collected and presented as a set of WorkerResults, which can be reassembled by the calling function.
// Any error that occurs in the workers will be passed back to the calling function.
-func Scatter(inputLen int, sFunc func(int, int, *sync.RWMutex) (interface{}, error)) ([]*WorkerResults, error) {
+func Scatter(inputLen int, sFunc func(int, int, *sync.RWMutex) (any, error)) ([]*WorkerResults, error) {
if inputLen <= 0 {
return nil, errors.New("input length must be greater than 0")
}
diff --git a/async/scatter_test.go b/async/scatter_test.go
index 72d8eb2d50..76f0d3e356 100644
--- a/async/scatter_test.go
+++ b/async/scatter_test.go
@@ -46,9 +46,9 @@ func TestDouble(t *testing.T) {
inValues[i] = i
}
outValues := make([]int, test.inValues)
- workerResults, err := async.Scatter(len(inValues), func(offset int, entries int, _ *sync.RWMutex) (interface{}, error) {
+ workerResults, err := async.Scatter(len(inValues), func(offset int, entries int, _ *sync.RWMutex) (any, error) {
extent := make([]int, entries)
- for i := 0; i < entries; i++ {
+ for i := range entries {
extent[i] = inValues[offset+i] * 2
}
return extent, nil
@@ -72,8 +72,8 @@ func TestDouble(t *testing.T) {
func TestMutex(t *testing.T) {
totalRuns := 1048576
val := 0
- _, err := async.Scatter(totalRuns, func(offset int, entries int, mu *sync.RWMutex) (interface{}, error) {
- for i := 0; i < entries; i++ {
+ _, err := async.Scatter(totalRuns, func(offset int, entries int, mu *sync.RWMutex) (any, error) {
+ for range entries {
mu.Lock()
val++
mu.Unlock()
@@ -90,8 +90,8 @@ func TestMutex(t *testing.T) {
func TestError(t *testing.T) {
totalRuns := 1024
val := 0
- _, err := async.Scatter(totalRuns, func(offset int, entries int, mu *sync.RWMutex) (interface{}, error) {
- for i := 0; i < entries; i++ {
+ _, err := async.Scatter(totalRuns, func(offset int, entries int, mu *sync.RWMutex) (any, error) {
+ for range entries {
mu.Lock()
val++
if val == 1011 {
diff --git a/beacon-chain/blockchain/kzg/validation_test.go b/beacon-chain/blockchain/kzg/validation_test.go
index 734e056121..6f91f7bb87 100644
--- a/beacon-chain/blockchain/kzg/validation_test.go
+++ b/beacon-chain/blockchain/kzg/validation_test.go
@@ -70,7 +70,7 @@ func TestVerifyBlobKZGProofBatch(t *testing.T) {
commitments := make([][]byte, blobCount)
proofs := make([][]byte, blobCount)
- for i := 0; i < blobCount; i++ {
+ for i := range blobCount {
blob := random.GetRandBlob(int64(i))
commitment, proof, err := GenerateCommitmentAndProof(blob)
require.NoError(t, err)
@@ -432,8 +432,8 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) {
commitments[1] = make([]byte, 32) // Wrong size
// Add cell proofs for both blobs
- for i := 0; i < blobCount; i++ {
- for j := uint64(0); j < numberOfColumns; j++ {
+ for range blobCount {
+ for range numberOfColumns {
allCellProofs = append(allCellProofs, make([]byte, 48))
}
}
@@ -450,7 +450,7 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) {
commitments := make([][]byte, blobCount)
var allCellProofs [][]byte
- for i := 0; i < blobCount; i++ {
+ for i := range blobCount {
randBlob := random.GetRandBlob(int64(i))
var blob Blob
copy(blob[:], randBlob[:])
@@ -461,7 +461,7 @@ func TestVerifyCellKZGProofBatchFromBlobData(t *testing.T) {
commitments[i] = commitment[:]
// Add cell proofs - make some invalid in the second blob
- for j := uint64(0); j < numberOfColumns; j++ {
+ for j := range numberOfColumns {
if i == 1 && j == 64 {
// Invalid proof size in middle of second blob's proofs
allCellProofs = append(allCellProofs, make([]byte, 20))
diff --git a/beacon-chain/blockchain/process_attestation_test.go b/beacon-chain/blockchain/process_attestation_test.go
index dbccecf0ec..1d78b10e37 100644
--- a/beacon-chain/blockchain/process_attestation_test.go
+++ b/beacon-chain/blockchain/process_attestation_test.go
@@ -209,16 +209,14 @@ func TestService_GetAttPreState_Concurrency(t *testing.T) {
var wg sync.WaitGroup
errChan := make(chan error, 1000)
- for i := 0; i < 1000; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
+ for range 1000 {
+ wg.Go(func() {
cp1 := ðpb.Checkpoint{Epoch: 1, Root: ckRoot}
_, err := service.getAttPreState(ctx, cp1)
if err != nil {
errChan <- err
}
- }()
+ })
}
go func() {
diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go
index 44d098a8c2..01aae56f94 100644
--- a/beacon-chain/blockchain/process_block.go
+++ b/beacon-chain/blockchain/process_block.go
@@ -817,7 +817,7 @@ func (s *Service) areDataColumnsAvailable(
}
case <-ctx.Done():
- var missingIndices interface{} = "all"
+ var missingIndices any = "all"
numberOfColumns := params.BeaconConfig().NumberOfColumns
missingIndicesCount := uint64(len(missing))
diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go
index 0df990b01e..4306d16c8b 100644
--- a/beacon-chain/blockchain/process_block_test.go
+++ b/beacon-chain/blockchain/process_block_test.go
@@ -147,7 +147,7 @@ func TestStore_OnBlockBatch(t *testing.T) {
bState := st.Copy()
var blks []consensusblocks.ROBlock
- for i := 0; i < 97; i++ {
+ for i := range 97 {
b, err := util.GenerateFullBlock(bState, keys, util.DefaultBlockGenConfig(), primitives.Slot(i))
require.NoError(t, err)
wsb, err := consensusblocks.NewSignedBeaconBlock(b)
@@ -1323,7 +1323,7 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
require.NoError(t, err)
logHook := logTest.NewGlobal()
- for i := 0; i < 10; i++ {
+ for range 10 {
fc := ðpb.Checkpoint{}
st, blkRoot, err := prepareForkchoiceState(ctx, 0, wsb1.Block().ParentRoot(), [32]byte{}, [32]byte{}, fc, fc)
require.NoError(t, err)
@@ -1949,7 +1949,7 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
require.Equal(t, true, optimistic)
// Check that the invalid blocks are not in database
- for i := 0; i < 19-13; i++ {
+ for i := range 19 - 13 {
require.Equal(t, false, service.cfg.BeaconDB.HasBlock(ctx, invalidRoots[i]))
}
@@ -2879,7 +2879,7 @@ func TestProcessLightClientUpdate(t *testing.T) {
// set a better sync aggregate
scb := make([]byte, 64)
- for i := 0; i < 5; i++ {
+ for i := range 5 {
scb[i] = 0x01
}
oldUpdate.SetSyncAggregate(ðpb.SyncAggregate{
diff --git a/beacon-chain/blockchain/receive_block_test.go b/beacon-chain/blockchain/receive_block_test.go
index daf9d97a47..95577054f2 100644
--- a/beacon-chain/blockchain/receive_block_test.go
+++ b/beacon-chain/blockchain/receive_block_test.go
@@ -216,13 +216,11 @@ func TestService_ReceiveBlockUpdateHead(t *testing.T) {
root, err := b.Block.HashTreeRoot()
require.NoError(t, err)
wg := sync.WaitGroup{}
- wg.Add(1)
- go func() {
+ wg.Go(func() {
wsb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
require.NoError(t, s.ReceiveBlock(ctx, wsb, root, nil))
- wg.Done()
- }()
+ })
wg.Wait()
time.Sleep(100 * time.Millisecond)
if recvd := len(s.cfg.StateNotifier.(*blockchainTesting.MockStateNotifier).ReceivedEvents()); recvd < 1 {
diff --git a/beacon-chain/blockchain/service_test.go b/beacon-chain/blockchain/service_test.go
index 03148af5d6..f1df2e9474 100644
--- a/beacon-chain/blockchain/service_test.go
+++ b/beacon-chain/blockchain/service_test.go
@@ -412,8 +412,7 @@ func BenchmarkHasBlockDB(b *testing.B) {
r, err := blk.Block.HashTreeRoot()
require.NoError(b, err)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.Equal(b, true, s.cfg.BeaconDB.HasBlock(ctx, r), "Block is not in DB")
}
}
@@ -432,8 +431,7 @@ func BenchmarkHasBlockForkChoiceStore_DoublyLinkedTree(b *testing.B) {
require.NoError(b, err)
require.NoError(b, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, roblock))
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.Equal(b, true, s.cfg.ForkChoiceStore.HasNode(r), "Block is not in fork choice store")
}
}
diff --git a/beacon-chain/blockchain/testing/mock.go b/beacon-chain/blockchain/testing/mock.go
index 2d2d8904e5..f9a406bb1d 100644
--- a/beacon-chain/blockchain/testing/mock.go
+++ b/beacon-chain/blockchain/testing/mock.go
@@ -106,7 +106,7 @@ type EventFeedWrapper struct {
subscribed chan struct{} // this channel is closed once a subscription is made
}
-func (w *EventFeedWrapper) Subscribe(channel interface{}) event.Subscription {
+func (w *EventFeedWrapper) Subscribe(channel any) event.Subscription {
select {
case <-w.subscribed:
break // already closed
@@ -116,7 +116,7 @@ func (w *EventFeedWrapper) Subscribe(channel interface{}) event.Subscription {
return w.feed.Subscribe(channel)
}
-func (w *EventFeedWrapper) Send(value interface{}) int {
+func (w *EventFeedWrapper) Send(value any) int {
return w.feed.Send(value)
}
diff --git a/beacon-chain/builder/service.go b/beacon-chain/builder/service.go
index 70d5001fce..8d6aa8a34e 100644
--- a/beacon-chain/builder/service.go
+++ b/beacon-chain/builder/service.go
@@ -166,7 +166,7 @@ func (s *Service) RegisterValidator(ctx context.Context, reg []*ethpb.SignedVali
indexToRegistration := make(map[primitives.ValidatorIndex]*ethpb.ValidatorRegistrationV1)
valid := make([]*ethpb.SignedValidatorRegistrationV1, 0)
- for i := 0; i < len(reg); i++ {
+ for i := range reg {
r := reg[i]
nx, exists := s.cfg.headFetcher.HeadPublicKeyToValidatorIndex(bytesutil.ToBytes48(r.Message.Pubkey))
if !exists {
diff --git a/beacon-chain/cache/active_balance_test.go b/beacon-chain/cache/active_balance_test.go
index 591dfea4be..857ff01916 100644
--- a/beacon-chain/cache/active_balance_test.go
+++ b/beacon-chain/cache/active_balance_test.go
@@ -17,7 +17,7 @@ import (
func TestBalanceCache_AddGetBalance(t *testing.T) {
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
blockRoots[i] = b
@@ -61,7 +61,7 @@ func TestBalanceCache_AddGetBalance(t *testing.T) {
func TestBalanceCache_BalanceKey(t *testing.T) {
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
blockRoots[i] = b
diff --git a/beacon-chain/cache/committee.go b/beacon-chain/cache/committee.go
index e3db8b6f22..3f0430833c 100644
--- a/beacon-chain/cache/committee.go
+++ b/beacon-chain/cache/committee.go
@@ -51,7 +51,7 @@ type CommitteeCache struct {
}
// committeeKeyFn takes the seed as the key to retrieve shuffled indices of a committee in a given epoch.
-func committeeKeyFn(obj interface{}) (string, error) {
+func committeeKeyFn(obj any) (string, error) {
info, ok := obj.(*Committees)
if !ok {
return "", ErrNotCommittee
diff --git a/beacon-chain/cache/committee_fuzz_test.go b/beacon-chain/cache/committee_fuzz_test.go
index e2a79a0b46..fb3e7280ad 100644
--- a/beacon-chain/cache/committee_fuzz_test.go
+++ b/beacon-chain/cache/committee_fuzz_test.go
@@ -14,7 +14,7 @@ func TestCommitteeKeyFuzz_OK(t *testing.T) {
fuzzer := fuzz.NewWithSeed(0)
c := &Committees{}
- for i := 0; i < 100000; i++ {
+ for range 100000 {
fuzzer.Fuzz(c)
k, err := committeeKeyFn(c)
require.NoError(t, err)
@@ -27,7 +27,7 @@ func TestCommitteeCache_FuzzCommitteesByEpoch(t *testing.T) {
fuzzer := fuzz.NewWithSeed(0)
c := &Committees{}
- for i := 0; i < 100000; i++ {
+ for range 100000 {
fuzzer.Fuzz(c)
require.NoError(t, cache.AddCommitteeShuffledList(t.Context(), c))
_, err := cache.Committee(t.Context(), 0, c.Seed, 0)
@@ -42,7 +42,7 @@ func TestCommitteeCache_FuzzActiveIndices(t *testing.T) {
fuzzer := fuzz.NewWithSeed(0)
c := &Committees{}
- for i := 0; i < 100000; i++ {
+ for range 100000 {
fuzzer.Fuzz(c)
require.NoError(t, cache.AddCommitteeShuffledList(t.Context(), c))
diff --git a/beacon-chain/cache/common.go b/beacon-chain/cache/common.go
index 5eff08f49e..ad26f77bff 100644
--- a/beacon-chain/cache/common.go
+++ b/beacon-chain/cache/common.go
@@ -17,6 +17,6 @@ func trim(queue *cache.FIFO, maxSize uint64) {
}
// popProcessNoopFunc is a no-op function that never returns an error.
-func popProcessNoopFunc(_ interface{}, _ bool) error {
+func popProcessNoopFunc(_ any, _ bool) error {
return nil
}
diff --git a/beacon-chain/cache/depositsnapshot/deposit_cache_test.go b/beacon-chain/cache/depositsnapshot/deposit_cache_test.go
index fbf486ed93..6ef00f2beb 100644
--- a/beacon-chain/cache/depositsnapshot/deposit_cache_test.go
+++ b/beacon-chain/cache/depositsnapshot/deposit_cache_test.go
@@ -769,7 +769,7 @@ func TestFinalizedDeposits_ReturnsTrieCorrectly(t *testing.T) {
}
var ctrs []*ethpb.DepositContainer
- for i := 0; i < 2000; i++ {
+ for i := range 2000 {
ctrs = append(ctrs, generateCtr(uint64(10+(i/2)), int64(i)))
}
@@ -948,9 +948,9 @@ func rootCreator(rn byte) []byte {
func BenchmarkDepositTree_InsertNewImplementation(b *testing.B) {
totalDeposits := 10000
input := bytesutil.ToBytes32([]byte("foo"))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
dt := NewDepositTree()
- for j := 0; j < totalDeposits; j++ {
+ for range totalDeposits {
err := dt.Insert(input[:], 0)
require.NoError(b, err)
}
@@ -959,10 +959,10 @@ func BenchmarkDepositTree_InsertNewImplementation(b *testing.B) {
func BenchmarkDepositTree_InsertOldImplementation(b *testing.B) {
totalDeposits := 10000
input := bytesutil.ToBytes32([]byte("foo"))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
dt, err := trie.NewTrie(33)
require.NoError(b, err)
- for j := 0; j < totalDeposits; j++ {
+ for range totalDeposits {
err := dt.Insert(input[:], 0)
require.NoError(b, err)
}
@@ -980,8 +980,8 @@ func BenchmarkDepositTree_HashTreeRootNewImplementation(b *testing.B) {
}
b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err = tr.HashTreeRoot()
require.NoError(b, err)
}
@@ -999,8 +999,8 @@ func BenchmarkDepositTree_HashTreeRootOldImplementation(b *testing.B) {
}
b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err = dt.HashTreeRoot()
require.NoError(b, err)
}
diff --git a/beacon-chain/cache/depositsnapshot/deposit_tree_snapshot.go b/beacon-chain/cache/depositsnapshot/deposit_tree_snapshot.go
index eefb0330cf..a9bba4b7d2 100644
--- a/beacon-chain/cache/depositsnapshot/deposit_tree_snapshot.go
+++ b/beacon-chain/cache/depositsnapshot/deposit_tree_snapshot.go
@@ -20,7 +20,7 @@ func (ds *DepositTreeSnapshot) CalculateRoot() ([32]byte, error) {
size := ds.depositCount
index := len(ds.finalized)
root := trie.ZeroHashes[0]
- for i := 0; i < DepositContractDepth; i++ {
+ for i := range DepositContractDepth {
if (size & 1) == 1 {
if index == 0 {
break
diff --git a/beacon-chain/cache/skip_slot_cache_test.go b/beacon-chain/cache/skip_slot_cache_test.go
index 6e364d1116..f8cf9c680d 100644
--- a/beacon-chain/cache/skip_slot_cache_test.go
+++ b/beacon-chain/cache/skip_slot_cache_test.go
@@ -47,15 +47,13 @@ func TestSkipSlotCache_DisabledAndEnabled(t *testing.T) {
c.Enable()
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
// Get call will only terminate when
// it is not longer in progress.
obj, err := c.Get(ctx, r)
require.NoError(t, err)
require.IsNil(t, obj)
- wg.Done()
- }()
+ })
c.MarkNotInProgress(r)
wg.Wait()
diff --git a/beacon-chain/cache/sync_committee.go b/beacon-chain/cache/sync_committee.go
index 8991a0022e..4ba9f5730e 100644
--- a/beacon-chain/cache/sync_committee.go
+++ b/beacon-chain/cache/sync_committee.go
@@ -236,7 +236,7 @@ func (s *SyncCommitteeCache) UpdatePositionsInCommittee(syncCommitteeBoundaryRoo
// Given the `syncCommitteeIndexPosition` object, this returns the key of the object.
// The key is the `currentSyncCommitteeRoot` within the field.
// Error gets returned if input does not comply with `currentSyncCommitteeRoot` object.
-func keyFn(obj interface{}) (string, error) {
+func keyFn(obj any) (string, error) {
info, ok := obj.(*syncCommitteeIndexPosition)
if !ok {
return "", errNotSyncCommitteeIndexPosition
diff --git a/beacon-chain/cache/sync_subnet_ids_test.go b/beacon-chain/cache/sync_subnet_ids_test.go
index 07122a4bef..bace4a20fb 100644
--- a/beacon-chain/cache/sync_subnet_ids_test.go
+++ b/beacon-chain/cache/sync_subnet_ids_test.go
@@ -12,12 +12,12 @@ import (
func TestSyncSubnetIDsCache_Roundtrip(t *testing.T) {
c := newSyncSubnetIDs()
- for i := 0; i < 20; i++ {
+ for i := range 20 {
pubkey := [fieldparams.BLSPubkeyLength]byte{byte(i)}
c.AddSyncCommitteeSubnets(pubkey[:], 100, []uint64{uint64(i)}, 0)
}
- for i := uint64(0); i < 20; i++ {
+ for i := range uint64(20) {
pubkey := [fieldparams.BLSPubkeyLength]byte{byte(i)}
idxs, _, ok, _ := c.GetSyncCommitteeSubnets(pubkey[:], 100)
@@ -34,7 +34,7 @@ func TestSyncSubnetIDsCache_Roundtrip(t *testing.T) {
func TestSyncSubnetIDsCache_ValidateCurrentEpoch(t *testing.T) {
c := newSyncSubnetIDs()
- for i := 0; i < 20; i++ {
+ for i := range 20 {
pubkey := [fieldparams.BLSPubkeyLength]byte{byte(i)}
c.AddSyncCommitteeSubnets(pubkey[:], 100, []uint64{uint64(i)}, 0)
}
@@ -42,7 +42,7 @@ func TestSyncSubnetIDsCache_ValidateCurrentEpoch(t *testing.T) {
coms := c.GetAllSubnets(50)
assert.Equal(t, 0, len(coms))
- for i := uint64(0); i < 20; i++ {
+ for i := range uint64(20) {
pubkey := [fieldparams.BLSPubkeyLength]byte{byte(i)}
_, jEpoch, ok, _ := c.GetSyncCommitteeSubnets(pubkey[:], 100)
diff --git a/beacon-chain/core/altair/attestation_test.go b/beacon-chain/core/altair/attestation_test.go
index 4b3f4916e7..8ceb34810f 100644
--- a/beacon-chain/core/altair/attestation_test.go
+++ b/beacon-chain/core/altair/attestation_test.go
@@ -461,7 +461,7 @@ func TestFuzzProcessAttestationsNoVerify_10000(t *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
st := ðpb.BeaconStateAltair{}
b := ðpb.SignedBeaconBlockAltair{Block: ðpb.BeaconBlockAltair{}}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(st)
fuzzer.Fuzz(b)
if b.Block == nil {
diff --git a/beacon-chain/core/altair/block_test.go b/beacon-chain/core/altair/block_test.go
index 51867fefd9..188a9dbb22 100644
--- a/beacon-chain/core/altair/block_test.go
+++ b/beacon-chain/core/altair/block_test.go
@@ -240,7 +240,7 @@ func TestProcessSyncCommittee_processSyncAggregate(t *testing.T) {
proposerIndex, err := helpers.BeaconProposerIndex(t.Context(), beaconState)
require.NoError(t, err)
- for i := 0; i < len(syncBits); i++ {
+ for i := range syncBits {
if syncBits.BitAt(uint64(i)) {
pk := bytesutil.ToBytes48(committeeKeys[i])
require.DeepEqual(t, true, votedMap[pk])
diff --git a/beacon-chain/core/altair/deposit.go b/beacon-chain/core/altair/deposit.go
index 44fa4e7ff7..c23c879b8c 100644
--- a/beacon-chain/core/altair/deposit.go
+++ b/beacon-chain/core/altair/deposit.go
@@ -195,10 +195,7 @@ func AddValidatorToRegistry(beaconState state.BeaconState, pubKey []byte, withdr
// withdrawable_epoch=FAR_FUTURE_EPOCH,
// )
func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount uint64) *ethpb.Validator {
- effectiveBalance := amount - (amount % params.BeaconConfig().EffectiveBalanceIncrement)
- if params.BeaconConfig().MaxEffectiveBalance < effectiveBalance {
- effectiveBalance = params.BeaconConfig().MaxEffectiveBalance
- }
+ effectiveBalance := min(params.BeaconConfig().MaxEffectiveBalance, amount-(amount%params.BeaconConfig().EffectiveBalanceIncrement))
return ðpb.Validator{
PublicKey: pubKey,
diff --git a/beacon-chain/core/altair/deposit_fuzz_test.go b/beacon-chain/core/altair/deposit_fuzz_test.go
index b45b3347ba..588b5a6f7c 100644
--- a/beacon-chain/core/altair/deposit_fuzz_test.go
+++ b/beacon-chain/core/altair/deposit_fuzz_test.go
@@ -16,7 +16,7 @@ func TestFuzzProcessDeposits_10000(t *testing.T) {
state := ðpb.BeaconStateAltair{}
deposits := make([]*ethpb.Deposit, 100)
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
for i := range deposits {
fuzzer.Fuzz(deposits[i])
@@ -37,7 +37,7 @@ func TestFuzzProcessPreGenesisDeposit_10000(t *testing.T) {
deposit := ðpb.Deposit{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(deposit)
s, err := state_native.InitializeFromProtoUnsafeAltair(state)
@@ -56,7 +56,7 @@ func TestFuzzProcessPreGenesisDeposit_Phase0_10000(t *testing.T) {
deposit := ðpb.Deposit{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(deposit)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -74,7 +74,7 @@ func TestFuzzProcessDeposit_Phase0_10000(t *testing.T) {
state := ðpb.BeaconState{}
deposit := ðpb.Deposit{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(deposit)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -92,7 +92,7 @@ func TestFuzzProcessDeposit_10000(t *testing.T) {
state := ðpb.BeaconStateAltair{}
deposit := ðpb.Deposit{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(deposit)
s, err := state_native.InitializeFromProtoUnsafeAltair(state)
diff --git a/beacon-chain/core/altair/epoch_precompute.go b/beacon-chain/core/altair/epoch_precompute.go
index 55ce28bbd1..aa70b8f8a8 100644
--- a/beacon-chain/core/altair/epoch_precompute.go
+++ b/beacon-chain/core/altair/epoch_precompute.go
@@ -122,11 +122,8 @@ func ProcessInactivityScores(
}
if !helpers.IsInInactivityLeak(prevEpoch, finalizedEpoch) {
- score := recoveryRate
// Prevents underflow below 0.
- if score > v.InactivityScore {
- score = v.InactivityScore
- }
+ score := min(recoveryRate, v.InactivityScore)
v.InactivityScore -= score
}
inactivityScores[i] = v.InactivityScore
@@ -242,7 +239,7 @@ func ProcessRewardsAndPenaltiesPrecompute(
}
balances := beaconState.Balances()
- for i := 0; i < numOfVals; i++ {
+ for i := range numOfVals {
vals[i].BeforeEpochTransitionBalance = balances[i]
// Compute the post balance of the validator after accounting for the
diff --git a/beacon-chain/core/altair/sync_committee_test.go b/beacon-chain/core/altair/sync_committee_test.go
index 096f4712b6..71b5e3646c 100644
--- a/beacon-chain/core/altair/sync_committee_test.go
+++ b/beacon-chain/core/altair/sync_committee_test.go
@@ -21,7 +21,7 @@ import (
func TestSyncCommitteeIndices_CanGet(t *testing.T) {
getState := func(t *testing.T, count uint64, vers int) state.BeaconState {
validators := make([]*ethpb.Validator, count)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MinDepositAmount,
@@ -113,7 +113,7 @@ func TestSyncCommitteeIndices_DifferentPeriods(t *testing.T) {
helpers.ClearCache()
getState := func(t *testing.T, count uint64) state.BeaconState {
validators := make([]*ethpb.Validator, count)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MinDepositAmount,
@@ -147,7 +147,7 @@ func TestSyncCommitteeIndices_DifferentPeriods(t *testing.T) {
func TestSyncCommittee_CanGet(t *testing.T) {
getState := func(t *testing.T, count uint64) state.BeaconState {
validators := make([]*ethpb.Validator, count)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
blsKey, err := bls.RandKey()
require.NoError(t, err)
validators[i] = ðpb.Validator{
@@ -394,7 +394,7 @@ func Test_ValidateSyncMessageTime(t *testing.T) {
func getState(t *testing.T, count uint64) state.BeaconState {
validators := make([]*ethpb.Validator, count)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
blsKey, err := bls.RandKey()
require.NoError(t, err)
validators[i] = ðpb.Validator{
diff --git a/beacon-chain/core/altair/upgrade_test.go b/beacon-chain/core/altair/upgrade_test.go
index cb9e3e9cd9..836f777145 100644
--- a/beacon-chain/core/altair/upgrade_test.go
+++ b/beacon-chain/core/altair/upgrade_test.go
@@ -33,7 +33,7 @@ func TestTranslateParticipation(t *testing.T) {
r, err := helpers.BlockRootAtSlot(s, 0)
require.NoError(t, err)
var pendingAtts []*ethpb.PendingAttestation
- for i := 0; i < 3; i++ {
+ for i := range 3 {
pendingAtts = append(pendingAtts, ðpb.PendingAttestation{
Data: ðpb.AttestationData{
CommitteeIndex: primitives.CommitteeIndex(i),
diff --git a/beacon-chain/core/blocks/attestation.go b/beacon-chain/core/blocks/attestation.go
index 6ad6a0f965..858464603d 100644
--- a/beacon-chain/core/blocks/attestation.go
+++ b/beacon-chain/core/blocks/attestation.go
@@ -257,7 +257,7 @@ func VerifyIndexedAttestation(ctx context.Context, beaconState state.ReadOnlyBea
}
indices := indexedAtt.GetAttestingIndices()
var pubkeys []bls.PublicKey
- for i := 0; i < len(indices); i++ {
+ for i := range indices {
pubkeyAtIdx := beaconState.PubkeyAtIndex(primitives.ValidatorIndex(indices[i]))
pk, err := bls.PublicKeyFromBytes(pubkeyAtIdx[:])
if err != nil {
diff --git a/beacon-chain/core/blocks/attestation_test.go b/beacon-chain/core/blocks/attestation_test.go
index 7d3af6f02e..24a068ef91 100644
--- a/beacon-chain/core/blocks/attestation_test.go
+++ b/beacon-chain/core/blocks/attestation_test.go
@@ -317,7 +317,7 @@ func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) {
func TestConvertToIndexed_OK(t *testing.T) {
helpers.ClearCache()
validators := make([]*ethpb.Validator, 2*params.BeaconConfig().SlotsPerEpoch)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -373,7 +373,7 @@ func TestVerifyIndexedAttestation_OK(t *testing.T) {
validators := make([]*ethpb.Validator, numOfValidators)
_, keys, err := util.DeterministicDepositsAndKeys(numOfValidators)
require.NoError(t, err)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[i].PublicKey().Marshal(),
@@ -481,7 +481,7 @@ func TestValidateIndexedAttestation_BadAttestationsSignatureSet(t *testing.T) {
sig := keys[0].Sign([]byte{'t', 'e', 's', 't'})
list := bitfield.Bitlist{0b11111}
var atts []ethpb.Att
- for i := uint64(0); i < 1000; i++ {
+ for range uint64(1000) {
atts = append(atts, ðpb.Attestation{
Data: ðpb.AttestationData{
CommitteeIndex: 1,
@@ -498,7 +498,7 @@ func TestValidateIndexedAttestation_BadAttestationsSignatureSet(t *testing.T) {
atts = []ethpb.Att{}
list = bitfield.Bitlist{0b10000}
- for i := uint64(0); i < 1000; i++ {
+ for range uint64(1000) {
atts = append(atts, ðpb.Attestation{
Data: ðpb.AttestationData{
CommitteeIndex: 1,
@@ -524,7 +524,7 @@ func TestVerifyAttestations_HandlesPlannedFork(t *testing.T) {
validators := make([]*ethpb.Validator, numOfValidators)
_, keys, err := util.DeterministicDepositsAndKeys(numOfValidators)
require.NoError(t, err)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[i].PublicKey().Marshal(),
@@ -588,7 +588,7 @@ func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing
validators := make([]*ethpb.Validator, numOfValidators)
_, keys, err := util.DeterministicDepositsAndKeys(numOfValidators)
require.NoError(t, err)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[i].PublicKey().Marshal(),
@@ -707,7 +707,7 @@ func TestRetrieveAttestationSignatureSet_AcrossFork(t *testing.T) {
validators := make([]*ethpb.Validator, numOfValidators)
_, keys, err := util.DeterministicDepositsAndKeys(numOfValidators)
require.NoError(t, err)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: keys[i].PublicKey().Marshal(),
diff --git a/beacon-chain/core/blocks/block_operations_fuzz_test.go b/beacon-chain/core/blocks/block_operations_fuzz_test.go
index d65fb4c3a0..e12b1217e1 100644
--- a/beacon-chain/core/blocks/block_operations_fuzz_test.go
+++ b/beacon-chain/core/blocks/block_operations_fuzz_test.go
@@ -21,7 +21,7 @@ func TestFuzzProcessAttestationNoVerify_10000(t *testing.T) {
state := ðpb.BeaconState{}
att := ðpb.Attestation{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(att)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -37,7 +37,7 @@ func TestFuzzProcessBlockHeader_10000(t *testing.T) {
state := ðpb.BeaconState{}
block := ðpb.SignedBeaconBlock{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(block)
@@ -63,7 +63,7 @@ func TestFuzzverifyDepositDataSigningRoot_10000(_ *testing.T) {
var p []byte
var s []byte
var d []byte
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(&ba)
fuzzer.Fuzz(&pubkey)
fuzzer.Fuzz(&sig)
@@ -83,7 +83,7 @@ func TestFuzzProcessEth1DataInBlock_10000(t *testing.T) {
e := ðpb.Eth1Data{}
state, err := state_native.InitializeFromProtoUnsafePhase0(ðpb.BeaconState{})
require.NoError(t, err)
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(e)
s, err := ProcessEth1DataInBlock(t.Context(), state, e)
@@ -98,7 +98,7 @@ func TestFuzzareEth1DataEqual_10000(_ *testing.T) {
eth1data := ðpb.Eth1Data{}
eth1data2 := ðpb.Eth1Data{}
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(eth1data)
fuzzer.Fuzz(eth1data2)
AreEth1DataEqual(eth1data, eth1data2)
@@ -110,7 +110,7 @@ func TestFuzzEth1DataHasEnoughSupport_10000(t *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
eth1data := ðpb.Eth1Data{}
var stateVotes []*ethpb.Eth1Data
- for i := 0; i < 100000; i++ {
+ for i := range 100000 {
fuzzer.Fuzz(eth1data)
fuzzer.Fuzz(&stateVotes)
s, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{
@@ -129,7 +129,7 @@ func TestFuzzProcessBlockHeaderNoVerify_10000(t *testing.T) {
state := ðpb.BeaconState{}
block := ðpb.BeaconBlock{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(block)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -145,7 +145,7 @@ func TestFuzzProcessRandao_10000(t *testing.T) {
state := ðpb.BeaconState{}
b := ðpb.SignedBeaconBlock{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(b)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -168,7 +168,7 @@ func TestFuzzProcessRandaoNoVerify_10000(t *testing.T) {
state := ðpb.BeaconState{}
blockBody := ðpb.BeaconBlockBody{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(blockBody)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -186,7 +186,7 @@ func TestFuzzProcessProposerSlashings_10000(t *testing.T) {
state := ðpb.BeaconState{}
p := ðpb.ProposerSlashing{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(p)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -203,7 +203,7 @@ func TestFuzzVerifyProposerSlashing_10000(t *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
state := ðpb.BeaconState{}
proposerSlashing := ðpb.ProposerSlashing{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(proposerSlashing)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -219,7 +219,7 @@ func TestFuzzProcessAttesterSlashings_10000(t *testing.T) {
state := ðpb.BeaconState{}
a := ðpb.AttesterSlashing{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(a)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -237,7 +237,7 @@ func TestFuzzVerifyAttesterSlashing_10000(t *testing.T) {
state := ðpb.BeaconState{}
attesterSlashing := ðpb.AttesterSlashing{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(attesterSlashing)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -253,7 +253,7 @@ func TestFuzzIsSlashableAttestationData_10000(_ *testing.T) {
attestationData := ðpb.AttestationData{}
attestationData2 := ðpb.AttestationData{}
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(attestationData)
fuzzer.Fuzz(attestationData2)
IsSlashableAttestationData(attestationData, attestationData2)
@@ -264,7 +264,7 @@ func TestFuzzslashableAttesterIndices_10000(_ *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
attesterSlashing := ðpb.AttesterSlashing{}
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(attesterSlashing)
SlashableAttesterIndices(attesterSlashing)
}
@@ -275,7 +275,7 @@ func TestFuzzProcessAttestationsNoVerify_10000(t *testing.T) {
state := ðpb.BeaconState{}
b := ðpb.SignedBeaconBlock{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(b)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -298,7 +298,7 @@ func TestFuzzVerifyIndexedAttestationn_10000(t *testing.T) {
state := ðpb.BeaconState{}
idxAttestation := ðpb.IndexedAttestation{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(idxAttestation)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -313,7 +313,7 @@ func TestFuzzverifyDeposit_10000(t *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
state := ðpb.BeaconState{}
deposit := ðpb.Deposit{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(deposit)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -329,7 +329,7 @@ func TestFuzzProcessVoluntaryExits_10000(t *testing.T) {
state := ðpb.BeaconState{}
e := ðpb.SignedVoluntaryExit{}
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(e)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -346,7 +346,7 @@ func TestFuzzProcessVoluntaryExitsNoVerify_10000(t *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
state := ðpb.BeaconState{}
e := ðpb.SignedVoluntaryExit{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(e)
s, err := state_native.InitializeFromProtoUnsafePhase0(state)
@@ -366,7 +366,7 @@ func TestFuzzVerifyExit_10000(t *testing.T) {
fork := ðpb.Fork{}
var slot primitives.Slot
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(ve)
fuzzer.Fuzz(rawVal)
fuzzer.Fuzz(fork)
diff --git a/beacon-chain/core/blocks/eth1_data_test.go b/beacon-chain/core/blocks/eth1_data_test.go
index b2ef805225..0cafe3cd46 100644
--- a/beacon-chain/core/blocks/eth1_data_test.go
+++ b/beacon-chain/core/blocks/eth1_data_test.go
@@ -19,7 +19,7 @@ import (
func FakeDeposits(n uint64) []*ethpb.Eth1Data {
deposits := make([]*ethpb.Eth1Data, n)
- for i := uint64(0); i < n; i++ {
+ for i := range n {
deposits[i] = ðpb.Eth1Data{
DepositCount: 1,
DepositRoot: bytesutil.PadTo([]byte("root"), 32),
@@ -175,7 +175,7 @@ func TestProcessEth1Data_SetsCorrectly(t *testing.T) {
}
period := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(params.BeaconConfig().EpochsPerEth1VotingPeriod)))
- for i := uint64(0); i < period; i++ {
+ for range period {
processedState, err := blocks.ProcessEth1DataInBlock(t.Context(), beaconState, b.Block.Body.Eth1Data)
require.NoError(t, err)
require.Equal(t, true, processedState.Version() == version.Phase0)
diff --git a/beacon-chain/core/blocks/header_test.go b/beacon-chain/core/blocks/header_test.go
index ac70b853d9..cf4480bcf6 100644
--- a/beacon-chain/core/blocks/header_test.go
+++ b/beacon-chain/core/blocks/header_test.go
@@ -27,7 +27,7 @@ func init() {
func TestProcessBlockHeader_ImproperBlockSlot(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 32),
WithdrawalCredentials: make([]byte, 32),
@@ -104,7 +104,7 @@ func TestProcessBlockHeader_WrongProposerSig(t *testing.T) {
func TestProcessBlockHeader_DifferentSlots(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 32),
WithdrawalCredentials: make([]byte, 32),
@@ -148,7 +148,7 @@ func TestProcessBlockHeader_DifferentSlots(t *testing.T) {
func TestProcessBlockHeader_PreviousBlockRootNotSignedRoot(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 48),
WithdrawalCredentials: make([]byte, 32),
@@ -189,7 +189,7 @@ func TestProcessBlockHeader_PreviousBlockRootNotSignedRoot(t *testing.T) {
func TestProcessBlockHeader_SlashedProposer(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 48),
WithdrawalCredentials: make([]byte, 32),
@@ -233,7 +233,7 @@ func TestProcessBlockHeader_SlashedProposer(t *testing.T) {
func TestProcessBlockHeader_OK(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 32),
WithdrawalCredentials: make([]byte, 32),
@@ -293,7 +293,7 @@ func TestProcessBlockHeader_OK(t *testing.T) {
func TestBlockSignatureSet_OK(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 32),
WithdrawalCredentials: make([]byte, 32),
diff --git a/beacon-chain/core/blocks/payload_test.go b/beacon-chain/core/blocks/payload_test.go
index 0e9cb067f6..d5119e1299 100644
--- a/beacon-chain/core/blocks/payload_test.go
+++ b/beacon-chain/core/blocks/payload_test.go
@@ -851,8 +851,7 @@ func BenchmarkBellatrixComplete(b *testing.B) {
require.NoError(b, err)
require.NoError(b, st.SetLatestExecutionPayloadHeader(h))
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := blocks.IsMergeTransitionComplete(st)
require.NoError(b, err)
}
diff --git a/beacon-chain/core/electra/churn_test.go b/beacon-chain/core/electra/churn_test.go
index 2c28c11726..def4464ffd 100644
--- a/beacon-chain/core/electra/churn_test.go
+++ b/beacon-chain/core/electra/churn_test.go
@@ -28,7 +28,7 @@ func createValidatorsWithTotalActiveBalance(totalBal primitives.Gwei) []*eth.Val
ActivationEpoch: primitives.Epoch(0),
EffectiveBalance: params.BeaconConfig().MinActivationBalance,
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
- PublicKey: []byte(fmt.Sprintf("val_%d", i)),
+ PublicKey: fmt.Appendf(nil, "val_%d", i),
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
WithdrawalCredentials: wd,
}
diff --git a/beacon-chain/core/electra/deposit_fuzz_test.go b/beacon-chain/core/electra/deposit_fuzz_test.go
index 04a7ac2dd1..59343e3675 100644
--- a/beacon-chain/core/electra/deposit_fuzz_test.go
+++ b/beacon-chain/core/electra/deposit_fuzz_test.go
@@ -16,7 +16,7 @@ func TestFuzzProcessDeposits_10000(t *testing.T) {
state := ðpb.BeaconStateElectra{}
deposits := make([]*ethpb.Deposit, 100)
ctx := t.Context()
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
for i := range deposits {
fuzzer.Fuzz(deposits[i])
@@ -36,7 +36,7 @@ func TestFuzzProcessDeposit_10000(t *testing.T) {
state := ðpb.BeaconStateElectra{}
deposit := ðpb.Deposit{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(deposit)
s, err := state_native.InitializeFromProtoUnsafeElectra(state)
diff --git a/beacon-chain/core/electra/deposits_test.go b/beacon-chain/core/electra/deposits_test.go
index 0c4d4658df..2c4a856452 100644
--- a/beacon-chain/core/electra/deposits_test.go
+++ b/beacon-chain/core/electra/deposits_test.go
@@ -95,7 +95,7 @@ func TestProcessPendingDeposits(t *testing.T) {
require.NoError(t, err)
require.Equal(t, primitives.Gwei(100), res)
// Validators 0..9 should have their balance increased
- for i := primitives.ValidatorIndex(0); i < 10; i++ {
+ for i := range primitives.ValidatorIndex(10) {
b, err := st.BalanceAtIndex(i)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().MinActivationBalance+uint64(amountAvailForProcessing)/10, b)
@@ -122,7 +122,7 @@ func TestProcessPendingDeposits(t *testing.T) {
check: func(t *testing.T, st state.BeaconState) {
amountAvailForProcessing := helpers.ActivationExitChurnLimit(1_000 * 1e9)
// Validators 0..9 should have their balance increased
- for i := primitives.ValidatorIndex(0); i < 2; i++ {
+ for i := range primitives.ValidatorIndex(2) {
b, err := st.BalanceAtIndex(i)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().MinActivationBalance+uint64(amountAvailForProcessing), b)
@@ -149,7 +149,7 @@ func TestProcessPendingDeposits(t *testing.T) {
require.NoError(t, err)
require.Equal(t, primitives.Gwei(0), res)
// Validators 0..4 should have their balance increased
- for i := primitives.ValidatorIndex(0); i < 4; i++ {
+ for i := range primitives.ValidatorIndex(4) {
b, err := st.BalanceAtIndex(i)
require.NoError(t, err)
require.Equal(t, params.BeaconConfig().MinActivationBalance+uint64(amountAvailForProcessing)/5, b)
@@ -528,7 +528,7 @@ func stateWithActiveBalanceETH(t *testing.T, balETH uint64) state.BeaconState {
vals := make([]*eth.Validator, numVals)
bals := make([]uint64, numVals)
- for i := uint64(0); i < numVals; i++ {
+ for i := range numVals {
wc := make([]byte, 32)
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
wc[31] = byte(i)
diff --git a/beacon-chain/core/electra/registry_updates_test.go b/beacon-chain/core/electra/registry_updates_test.go
index af393feac0..03883a072d 100644
--- a/beacon-chain/core/electra/registry_updates_test.go
+++ b/beacon-chain/core/electra/registry_updates_test.go
@@ -56,7 +56,7 @@ func TestProcessRegistryUpdates(t *testing.T) {
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
FinalizedCheckpoint: ð.Checkpoint{Epoch: finalizedEpoch, Root: make([]byte, fieldparams.RootLength)},
}
- for i := uint64(0); i < 10; i++ {
+ for range uint64(10) {
base.Validators = append(base.Validators, ð.Validator{
ActivationEligibilityEpoch: finalizedEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
@@ -82,7 +82,7 @@ func TestProcessRegistryUpdates(t *testing.T) {
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
FinalizedCheckpoint: ð.Checkpoint{Epoch: finalizedEpoch, Root: make([]byte, fieldparams.RootLength)},
}
- for i := uint64(0); i < 10; i++ {
+ for range uint64(10) {
base.Validators = append(base.Validators, ð.Validator{
EffectiveBalance: params.BeaconConfig().EjectionBalance - 1,
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
@@ -108,7 +108,7 @@ func TestProcessRegistryUpdates(t *testing.T) {
Slot: 5 * params.BeaconConfig().SlotsPerEpoch,
FinalizedCheckpoint: ð.Checkpoint{Epoch: finalizedEpoch, Root: make([]byte, fieldparams.RootLength)},
}
- for i := uint64(0); i < 10; i++ {
+ for range uint64(10) {
base.Validators = append(base.Validators, ð.Validator{
EffectiveBalance: params.BeaconConfig().EjectionBalance - 1,
ExitEpoch: 10,
@@ -157,7 +157,7 @@ func Benchmark_ProcessRegistryUpdates_MassEjection(b *testing.B) {
st, err := util.NewBeaconStateElectra()
require.NoError(b, err)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
b.StopTimer()
if err := st.SetValidators(genValidators(100000)); err != nil {
panic(err)
diff --git a/beacon-chain/core/epoch/epoch_processing.go b/beacon-chain/core/epoch/epoch_processing.go
index a5b097e5c4..dc246242c0 100644
--- a/beacon-chain/core/epoch/epoch_processing.go
+++ b/beacon-chain/core/epoch/epoch_processing.go
@@ -329,10 +329,7 @@ func ProcessEffectiveBalanceUpdates(st state.BeaconState) (state.BeaconState, er
balance := bals[idx]
if balance+downwardThreshold < val.EffectiveBalance() || val.EffectiveBalance()+upwardThreshold < balance {
- effectiveBal := maxEffBalance
- if effectiveBal > balance-balance%effBalanceInc {
- effectiveBal = balance - balance%effBalanceInc
- }
+ effectiveBal := min(maxEffBalance, balance-balance%effBalanceInc)
if effectiveBal != val.EffectiveBalance() {
newVal = val.Copy()
newVal.EffectiveBalance = effectiveBal
diff --git a/beacon-chain/core/epoch/epoch_processing_fuzz_test.go b/beacon-chain/core/epoch/epoch_processing_fuzz_test.go
index de84b7b14e..a4ceb91f2d 100644
--- a/beacon-chain/core/epoch/epoch_processing_fuzz_test.go
+++ b/beacon-chain/core/epoch/epoch_processing_fuzz_test.go
@@ -14,7 +14,7 @@ func TestFuzzFinalUpdates_10000(t *testing.T) {
fuzzer := gofuzz.NewWithSeed(0)
base := ðpb.BeaconState{}
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
fuzzer.Fuzz(base)
s, err := state_native.InitializeFromProtoUnsafePhase0(base)
require.NoError(t, err)
diff --git a/beacon-chain/core/epoch/epoch_processing_test.go b/beacon-chain/core/epoch/epoch_processing_test.go
index dd2739a421..0c82afc087 100644
--- a/beacon-chain/core/epoch/epoch_processing_test.go
+++ b/beacon-chain/core/epoch/epoch_processing_test.go
@@ -218,7 +218,7 @@ func TestProcessRegistryUpdates_EligibleToActivate_Cancun(t *testing.T) {
cfg.ChurnLimitQuotient = 1
params.OverrideBeaconConfig(cfg)
- for i := uint64(0); i < 10; i++ {
+ for range uint64(10) {
base.Validators = append(base.Validators, ðpb.Validator{
ActivationEligibilityEpoch: finalizedEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
@@ -314,28 +314,28 @@ func TestProcessRegistryUpdates_CanExits(t *testing.T) {
func buildState(t testing.TB, slot primitives.Slot, validatorCount uint64) state.BeaconState {
validators := make([]*ethpb.Validator, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
}
}
validatorBalances := make([]uint64, len(validators))
- for i := 0; i < len(validatorBalances); i++ {
+ for i := range validatorBalances {
validatorBalances[i] = params.BeaconConfig().MaxEffectiveBalance
}
latestActiveIndexRoots := make(
[][]byte,
params.BeaconConfig().EpochsPerHistoricalVector,
)
- for i := 0; i < len(latestActiveIndexRoots); i++ {
+ for i := range latestActiveIndexRoots {
latestActiveIndexRoots[i] = params.BeaconConfig().ZeroHash[:]
}
latestRandaoMixes := make(
[][]byte,
params.BeaconConfig().EpochsPerHistoricalVector,
)
- for i := 0; i < len(latestRandaoMixes); i++ {
+ for i := range latestRandaoMixes {
latestRandaoMixes[i] = params.BeaconConfig().ZeroHash[:]
}
s, err := util.NewBeaconState()
diff --git a/beacon-chain/core/epoch/precompute/justification_finalization_test.go b/beacon-chain/core/epoch/precompute/justification_finalization_test.go
index a0222d7f31..0c2cece56b 100644
--- a/beacon-chain/core/epoch/precompute/justification_finalization_test.go
+++ b/beacon-chain/core/epoch/precompute/justification_finalization_test.go
@@ -19,7 +19,7 @@ func TestProcessJustificationAndFinalizationPreCompute_ConsecutiveEpochs(t *test
e := params.BeaconConfig().FarFutureEpoch
a := params.BeaconConfig().MaxEffectiveBalance
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerEpoch*2+1)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = []byte{byte(i)}
}
base := ðpb.BeaconState{
@@ -56,7 +56,7 @@ func TestProcessJustificationAndFinalizationPreCompute_JustifyCurrentEpoch(t *te
e := params.BeaconConfig().FarFutureEpoch
a := params.BeaconConfig().MaxEffectiveBalance
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerEpoch*2+1)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = []byte{byte(i)}
}
base := ðpb.BeaconState{
@@ -93,7 +93,7 @@ func TestProcessJustificationAndFinalizationPreCompute_JustifyPrevEpoch(t *testi
e := params.BeaconConfig().FarFutureEpoch
a := params.BeaconConfig().MaxEffectiveBalance
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerEpoch*2+1)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = []byte{byte(i)}
}
base := ðpb.BeaconState{
@@ -128,7 +128,7 @@ func TestProcessJustificationAndFinalizationPreCompute_JustifyPrevEpoch(t *testi
func TestUnrealizedCheckpoints(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
balances := make([]uint64, len(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
diff --git a/beacon-chain/core/epoch/precompute/reward_penalty.go b/beacon-chain/core/epoch/precompute/reward_penalty.go
index 6643719a04..7c674e5ed4 100644
--- a/beacon-chain/core/epoch/precompute/reward_penalty.go
+++ b/beacon-chain/core/epoch/precompute/reward_penalty.go
@@ -42,7 +42,7 @@ func ProcessRewardsAndPenaltiesPrecompute(
return nil, errors.Wrap(err, "could not get proposer attestation delta")
}
validatorBals := state.Balances()
- for i := 0; i < numOfVals; i++ {
+ for i := range numOfVals {
vp[i].BeforeEpochTransitionBalance = validatorBals[i]
// Compute the post balance of the validator after accounting for the
diff --git a/beacon-chain/core/epoch/precompute/reward_penalty_test.go b/beacon-chain/core/epoch/precompute/reward_penalty_test.go
index 83f600ff5d..7cc0762ace 100644
--- a/beacon-chain/core/epoch/precompute/reward_penalty_test.go
+++ b/beacon-chain/core/epoch/precompute/reward_penalty_test.go
@@ -24,7 +24,7 @@ func TestProcessRewardsAndPenaltiesPrecompute(t *testing.T) {
validatorCount := uint64(2048)
base := buildState(e+3, validatorCount)
atts := make([]*ethpb.PendingAttestation, 3)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = ðpb.PendingAttestation{
Data: ðpb.AttestationData{
Target: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)},
@@ -63,7 +63,7 @@ func TestAttestationDeltas_ZeroEpoch(t *testing.T) {
base := buildState(e+2, validatorCount)
atts := make([]*ethpb.PendingAttestation, 3)
var emptyRoot [32]byte
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = ðpb.PendingAttestation{
Data: ðpb.AttestationData{
Target: ðpb.Checkpoint{
@@ -99,7 +99,7 @@ func TestAttestationDeltas_ZeroInclusionDelay(t *testing.T) {
base := buildState(e+2, validatorCount)
atts := make([]*ethpb.PendingAttestation, 3)
var emptyRoot [32]byte
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = ðpb.PendingAttestation{
Data: ðpb.AttestationData{
Target: ðpb.Checkpoint{
@@ -131,7 +131,7 @@ func TestProcessRewardsAndPenaltiesPrecompute_SlashedInactivePenalty(t *testing.
validatorCount := uint64(2048)
base := buildState(e+3, validatorCount)
atts := make([]*ethpb.PendingAttestation, 3)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = ðpb.PendingAttestation{
Data: ðpb.AttestationData{
Target: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)},
@@ -176,28 +176,28 @@ func TestProcessRewardsAndPenaltiesPrecompute_SlashedInactivePenalty(t *testing.
func buildState(slot primitives.Slot, validatorCount uint64) *ethpb.BeaconState {
validators := make([]*ethpb.Validator, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
}
}
validatorBalances := make([]uint64, len(validators))
- for i := 0; i < len(validatorBalances); i++ {
+ for i := range validatorBalances {
validatorBalances[i] = params.BeaconConfig().MaxEffectiveBalance
}
latestActiveIndexRoots := make(
[][]byte,
params.BeaconConfig().EpochsPerHistoricalVector,
)
- for i := 0; i < len(latestActiveIndexRoots); i++ {
+ for i := range latestActiveIndexRoots {
latestActiveIndexRoots[i] = params.BeaconConfig().ZeroHash[:]
}
latestRandaoMixes := make(
[][]byte,
params.BeaconConfig().EpochsPerHistoricalVector,
)
- for i := 0; i < len(latestRandaoMixes); i++ {
+ for i := range latestRandaoMixes {
latestRandaoMixes[i] = params.BeaconConfig().ZeroHash[:]
}
return ðpb.BeaconState{
diff --git a/beacon-chain/core/feed/event.go b/beacon-chain/core/feed/event.go
index 871a3d562e..eadaa8c756 100644
--- a/beacon-chain/core/feed/event.go
+++ b/beacon-chain/core/feed/event.go
@@ -17,5 +17,5 @@ type Event struct {
// Type is the type of event.
Type EventType
// Data is event-specific data.
- Data interface{}
+ Data any
}
diff --git a/beacon-chain/core/helpers/attestation_test.go b/beacon-chain/core/helpers/attestation_test.go
index 16d86f8e1e..8d861f2e5c 100644
--- a/beacon-chain/core/helpers/attestation_test.go
+++ b/beacon-chain/core/helpers/attestation_test.go
@@ -54,7 +54,7 @@ func TestAttestation_ComputeSubnetForAttestation(t *testing.T) {
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize
validators := make([]*ethpb.Validator, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
diff --git a/beacon-chain/core/helpers/beacon_committee.go b/beacon-chain/core/helpers/beacon_committee.go
index fd04aef284..83565aa570 100644
--- a/beacon-chain/core/helpers/beacon_committee.go
+++ b/beacon-chain/core/helpers/beacon_committee.go
@@ -5,7 +5,7 @@ package helpers
import (
"context"
"fmt"
- "sort"
+ "slices"
"github.com/OffchainLabs/go-bitfield"
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
@@ -515,9 +515,7 @@ func UpdateCommitteeCache(ctx context.Context, state state.ReadOnlyBeaconState,
// used for failing verify signature fallback.
sortedIndices := make([]primitives.ValidatorIndex, len(shuffledIndices))
copy(sortedIndices, shuffledIndices)
- sort.Slice(sortedIndices, func(i, j int) bool {
- return sortedIndices[i] < sortedIndices[j]
- })
+ slices.Sort(sortedIndices)
if err := committeeCache.AddCommitteeShuffledList(ctx, &cache.Committees{
ShuffledIndices: shuffledIndices,
diff --git a/beacon-chain/core/helpers/beacon_committee_test.go b/beacon-chain/core/helpers/beacon_committee_test.go
index b805b63a56..2d83d19d53 100644
--- a/beacon-chain/core/helpers/beacon_committee_test.go
+++ b/beacon-chain/core/helpers/beacon_committee_test.go
@@ -29,7 +29,7 @@ func TestComputeCommittee_WithoutCache(t *testing.T) {
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize
validators := make([]*ethpb.Validator, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -122,7 +122,7 @@ func TestCommitteeAssignments_NoProposerForSlot0(t *testing.T) {
helpers.ClearCache()
validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
var activationEpoch primitives.Epoch
if i >= len(validators)/2 {
activationEpoch = 3
@@ -151,7 +151,7 @@ func TestCommitteeAssignments_CanRetrieve(t *testing.T) {
// Initialize test with 256 validators, each slot and each index gets 4 validators.
validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch)
validatorIndices := make([]primitives.ValidatorIndex, len(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
// First 2 epochs only half validators are activated.
var activationEpoch primitives.Epoch
if i >= len(validators)/2 {
@@ -234,7 +234,7 @@ func TestCommitteeAssignments_CannotRetrieveFuture(t *testing.T) {
// Initialize test with 256 validators, each slot and each index gets 4 validators.
validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
// First 2 epochs only half validators are activated.
var activationEpoch primitives.Epoch
if i >= len(validators)/2 {
@@ -266,7 +266,7 @@ func TestCommitteeAssignments_CannotRetrieveOlderThanSlotsPerHistoricalRoot(t *t
// Initialize test with 256 validators, each slot and each index gets 4 validators.
validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -287,7 +287,7 @@ func TestCommitteeAssignments_EverySlotHasMin1Proposer(t *testing.T) {
// Initialize test with 256 validators, each slot and each index gets 4 validators.
validators := make([]*ethpb.Validator, 4*params.BeaconConfig().SlotsPerEpoch)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ActivationEpoch: 0,
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
@@ -323,7 +323,7 @@ func TestCommitteeAssignments_EverySlotHasMin1Proposer(t *testing.T) {
func TestVerifyAttestationBitfieldLengths_OK(t *testing.T) {
validators := make([]*ethpb.Validator, 2*params.BeaconConfig().SlotsPerEpoch)
activeRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -489,7 +489,7 @@ func TestUpdateCommitteeCache_CanUpdateAcrossEpochs(t *testing.T) {
func BenchmarkComputeCommittee300000_WithPreCache(b *testing.B) {
validators := make([]*ethpb.Validator, 300000)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -512,8 +512,7 @@ func BenchmarkComputeCommittee300000_WithPreCache(b *testing.B) {
panic(err)
}
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for b.Loop() {
_, err := helpers.ComputeCommittee(indices, seed, index, params.BeaconConfig().MaxCommitteesPerSlot)
if err != nil {
panic(err)
@@ -523,7 +522,7 @@ func BenchmarkComputeCommittee300000_WithPreCache(b *testing.B) {
func BenchmarkComputeCommittee3000000_WithPreCache(b *testing.B) {
validators := make([]*ethpb.Validator, 3000000)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -546,8 +545,7 @@ func BenchmarkComputeCommittee3000000_WithPreCache(b *testing.B) {
panic(err)
}
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
+ for b.Loop() {
_, err := helpers.ComputeCommittee(indices, seed, index, params.BeaconConfig().MaxCommitteesPerSlot)
if err != nil {
panic(err)
@@ -557,7 +555,7 @@ func BenchmarkComputeCommittee3000000_WithPreCache(b *testing.B) {
func BenchmarkComputeCommittee128000_WithOutPreCache(b *testing.B) {
validators := make([]*ethpb.Validator, 128000)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -576,8 +574,8 @@ func BenchmarkComputeCommittee128000_WithOutPreCache(b *testing.B) {
i := uint64(0)
index := uint64(0)
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
+
+ for b.Loop() {
i++
_, err := helpers.ComputeCommittee(indices, seed, index, params.BeaconConfig().MaxCommitteesPerSlot)
if err != nil {
@@ -592,7 +590,7 @@ func BenchmarkComputeCommittee128000_WithOutPreCache(b *testing.B) {
func BenchmarkComputeCommittee1000000_WithOutCache(b *testing.B) {
validators := make([]*ethpb.Validator, 1000000)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -611,8 +609,8 @@ func BenchmarkComputeCommittee1000000_WithOutCache(b *testing.B) {
i := uint64(0)
index := uint64(0)
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
+
+ for b.Loop() {
i++
_, err := helpers.ComputeCommittee(indices, seed, index, params.BeaconConfig().MaxCommitteesPerSlot)
if err != nil {
@@ -627,7 +625,7 @@ func BenchmarkComputeCommittee1000000_WithOutCache(b *testing.B) {
func BenchmarkComputeCommittee4000000_WithOutCache(b *testing.B) {
validators := make([]*ethpb.Validator, 4000000)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -646,8 +644,8 @@ func BenchmarkComputeCommittee4000000_WithOutCache(b *testing.B) {
i := uint64(0)
index := uint64(0)
- b.ResetTimer()
- for n := 0; n < b.N; n++ {
+
+ for b.Loop() {
i++
_, err := helpers.ComputeCommittee(indices, seed, index, params.BeaconConfig().MaxCommitteesPerSlot)
if err != nil {
@@ -663,7 +661,7 @@ func BenchmarkComputeCommittee4000000_WithOutCache(b *testing.B) {
func TestBeaconCommitteeFromState_UpdateCacheForPreviousEpoch(t *testing.T) {
committeeSize := uint64(16)
validators := make([]*ethpb.Validator, params.BeaconConfig().SlotsPerEpoch.Mul(committeeSize))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -688,7 +686,7 @@ func TestBeaconCommitteeFromState_UpdateCacheForPreviousEpoch(t *testing.T) {
func TestPrecomputeProposerIndices_Ok(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -732,7 +730,7 @@ func TestAttestationCommitteesFromState(t *testing.T) {
ctx := t.Context()
validators := make([]*ethpb.Validator, params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().TargetCommitteeSize))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -768,7 +766,7 @@ func TestAttestationCommitteesFromCache(t *testing.T) {
ctx := t.Context()
validators := make([]*ethpb.Validator, params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().TargetCommitteeSize))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -934,7 +932,7 @@ func TestInitializeProposerLookahead_RegressionTest(t *testing.T) {
proposerLookahead, err := helpers.InitializeProposerLookahead(ctx, state, epoch)
require.NoError(t, err)
slotsPerEpoch := int(params.BeaconConfig().SlotsPerEpoch)
- for epochOffset := primitives.Epoch(0); epochOffset < 2; epochOffset++ {
+ for epochOffset := range primitives.Epoch(2) {
targetEpoch := epoch + epochOffset
activeIndices, err := helpers.ActiveValidatorIndices(ctx, state, targetEpoch)
diff --git a/beacon-chain/core/helpers/randao_test.go b/beacon-chain/core/helpers/randao_test.go
index d9476ff49e..88cfaec82c 100644
--- a/beacon-chain/core/helpers/randao_test.go
+++ b/beacon-chain/core/helpers/randao_test.go
@@ -16,7 +16,7 @@ import (
func TestRandaoMix_OK(t *testing.T) {
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
intInBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(intInBytes, uint64(i))
randaoMixes[i] = intInBytes
@@ -52,7 +52,7 @@ func TestRandaoMix_OK(t *testing.T) {
func TestRandaoMix_CopyOK(t *testing.T) {
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
intInBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(intInBytes, uint64(i))
randaoMixes[i] = intInBytes
@@ -96,7 +96,7 @@ func TestGenerateSeed_OK(t *testing.T) {
helpers.ClearCache()
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
intInBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(intInBytes, uint64(i))
randaoMixes[i] = intInBytes
diff --git a/beacon-chain/core/helpers/rewards_penalties_test.go b/beacon-chain/core/helpers/rewards_penalties_test.go
index 5cedac6ef2..7d17cefd79 100644
--- a/beacon-chain/core/helpers/rewards_penalties_test.go
+++ b/beacon-chain/core/helpers/rewards_penalties_test.go
@@ -239,28 +239,28 @@ func TestIsInInactivityLeak(t *testing.T) {
func buildState(slot primitives.Slot, validatorCount uint64) *ethpb.BeaconState {
validators := make([]*ethpb.Validator, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
}
}
validatorBalances := make([]uint64, len(validators))
- for i := 0; i < len(validatorBalances); i++ {
+ for i := range validatorBalances {
validatorBalances[i] = params.BeaconConfig().MaxEffectiveBalance
}
latestActiveIndexRoots := make(
[][]byte,
params.BeaconConfig().EpochsPerHistoricalVector,
)
- for i := 0; i < len(latestActiveIndexRoots); i++ {
+ for i := range latestActiveIndexRoots {
latestActiveIndexRoots[i] = params.BeaconConfig().ZeroHash[:]
}
latestRandaoMixes := make(
[][]byte,
params.BeaconConfig().EpochsPerHistoricalVector,
)
- for i := 0; i < len(latestRandaoMixes); i++ {
+ for i := range latestRandaoMixes {
latestRandaoMixes[i] = params.BeaconConfig().ZeroHash[:]
}
return ðpb.BeaconState{
diff --git a/beacon-chain/core/helpers/shuffle.go b/beacon-chain/core/helpers/shuffle.go
index e10053bdbe..2b3df4f0e1 100644
--- a/beacon-chain/core/helpers/shuffle.go
+++ b/beacon-chain/core/helpers/shuffle.go
@@ -23,7 +23,7 @@ var maxShuffleListSize uint64 = 1 << 40
func SplitIndices(l []uint64, n uint64) [][]uint64 {
var divided [][]uint64
var lSize = uint64(len(l))
- for i := uint64(0); i < n; i++ {
+ for i := range n {
start := slice.SplitOffset(lSize, n, i)
end := slice.SplitOffset(lSize, n, i+1)
divided = append(divided, l[start:end])
@@ -103,10 +103,7 @@ func ComputeShuffledIndex(index primitives.ValidatorIndex, indexCount uint64, se
pivot := hash8Int % indexCount
flip := (pivot + indexCount - uint64(index)) % indexCount
// Consider every pair only once by picking the highest pair index to retrieve randomness.
- position := uint64(index)
- if flip > position {
- position = flip
- }
+ position := max(flip, uint64(index))
// Add position except its last byte to []buf for randomness,
// it will be used later to select a bit from the resulting hash.
binary.LittleEndian.PutUint64(posBuffer[:8], position>>8)
diff --git a/beacon-chain/core/helpers/shuffle_test.go b/beacon-chain/core/helpers/shuffle_test.go
index d1e985b38f..7e3c1103ef 100644
--- a/beacon-chain/core/helpers/shuffle_test.go
+++ b/beacon-chain/core/helpers/shuffle_test.go
@@ -30,7 +30,7 @@ func TestShuffleList_OK(t *testing.T) {
var list1 []primitives.ValidatorIndex
seed1 := [32]byte{1, 128, 12}
seed2 := [32]byte{2, 128, 12}
- for i := 0; i < 10; i++ {
+ for i := range 10 {
list1 = append(list1, primitives.ValidatorIndex(i))
}
@@ -55,7 +55,7 @@ func TestSplitIndices_OK(t *testing.T) {
var l []uint64
numValidators := uint64(64000)
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
l = append(l, i)
}
split := SplitIndices(l, uint64(params.BeaconConfig().SlotsPerEpoch))
@@ -104,7 +104,7 @@ func BenchmarkIndexComparison(b *testing.B) {
seed := [32]byte{123, 42}
for _, listSize := range listSizes {
b.Run(fmt.Sprintf("Indexwise_ShuffleList_%d", listSize), func(ib *testing.B) {
- for i := 0; i < ib.N; i++ {
+ for ib.Loop() {
// Simulate a list-shuffle by running shuffle-index listSize times.
for j := primitives.ValidatorIndex(0); uint64(j) < listSize; j++ {
_, err := ShuffledIndex(j, listSize, seed)
@@ -120,11 +120,11 @@ func BenchmarkShuffleList(b *testing.B) {
seed := [32]byte{123, 42}
for _, listSize := range listSizes {
testIndices := make([]primitives.ValidatorIndex, listSize)
- for i := uint64(0); i < listSize; i++ {
+ for i := range listSize {
testIndices[i] = primitives.ValidatorIndex(i)
}
b.Run(fmt.Sprintf("ShuffleList_%d", listSize), func(ib *testing.B) {
- for i := 0; i < ib.N; i++ {
+ for ib.Loop() {
_, err := ShuffleList(testIndices, seed)
assert.NoError(b, err)
}
@@ -161,12 +161,12 @@ func TestSplitIndicesAndOffset_OK(t *testing.T) {
var l []uint64
validators := uint64(64000)
- for i := uint64(0); i < validators; i++ {
+ for i := range validators {
l = append(l, i)
}
chunks := uint64(6)
split := SplitIndices(l, chunks)
- for i := uint64(0); i < chunks; i++ {
+ for i := range chunks {
if !reflect.DeepEqual(split[i], l[slice.SplitOffset(uint64(len(l)), chunks, i):slice.SplitOffset(uint64(len(l)), chunks, i+1)]) {
t.Errorf("Want: %v got: %v", l[slice.SplitOffset(uint64(len(l)), chunks, i):slice.SplitOffset(uint64(len(l)), chunks, i+1)], split[i])
break
diff --git a/beacon-chain/core/helpers/sync_committee_test.go b/beacon-chain/core/helpers/sync_committee_test.go
index 8b328d90c1..0d51eb1c56 100644
--- a/beacon-chain/core/helpers/sync_committee_test.go
+++ b/beacon-chain/core/helpers/sync_committee_test.go
@@ -24,7 +24,7 @@ func TestCurrentPeriodPositions(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
Pubkeys: make([][]byte, params.BeaconConfig().SyncCommitteeSize),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -56,7 +56,7 @@ func TestIsCurrentEpochSyncCommittee_UsingCache(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -87,7 +87,7 @@ func TestIsCurrentEpochSyncCommittee_UsingCommittee(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -116,7 +116,7 @@ func TestIsCurrentEpochSyncCommittee_DoesNotExist(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -144,7 +144,7 @@ func TestIsNextEpochSyncCommittee_UsingCache(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -175,7 +175,7 @@ func TestIsNextEpochSyncCommittee_UsingCommittee(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -203,7 +203,7 @@ func TestIsNextEpochSyncCommittee_DoesNotExist(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -231,7 +231,7 @@ func TestCurrentEpochSyncSubcommitteeIndices_UsingCache(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -262,7 +262,7 @@ func TestCurrentEpochSyncSubcommitteeIndices_UsingCommittee(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -304,7 +304,7 @@ func TestCurrentEpochSyncSubcommitteeIndices_DoesNotExist(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -332,7 +332,7 @@ func TestNextEpochSyncSubcommitteeIndices_UsingCache(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -363,7 +363,7 @@ func TestNextEpochSyncSubcommitteeIndices_UsingCommittee(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -391,7 +391,7 @@ func TestNextEpochSyncSubcommitteeIndices_DoesNotExist(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
@@ -449,7 +449,7 @@ func TestIsCurrentEpochSyncCommittee_SameBlockRoot(t *testing.T) {
syncCommittee := ðpb.SyncCommittee{
AggregatePubkey: bytesutil.PadTo([]byte{}, params.BeaconConfig().BLSPubkeyLength),
}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = ðpb.Validator{
diff --git a/beacon-chain/core/helpers/validators_test.go b/beacon-chain/core/helpers/validators_test.go
index a449bb2a42..9a18899c53 100644
--- a/beacon-chain/core/helpers/validators_test.go
+++ b/beacon-chain/core/helpers/validators_test.go
@@ -184,7 +184,7 @@ func TestBeaconProposerIndex_OK(t *testing.T) {
c.MinGenesisActiveValidatorCount = 16384
params.OverrideBeaconConfig(c)
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount/8)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -241,7 +241,7 @@ func TestBeaconProposerIndex_BadState(t *testing.T) {
c.MinGenesisActiveValidatorCount = 16384
params.OverrideBeaconConfig(c)
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount/8)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -270,7 +270,7 @@ func TestComputeProposerIndex_Compatibility(t *testing.T) {
helpers.ClearCache()
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -322,7 +322,7 @@ func TestActiveValidatorCount_Genesis(t *testing.T) {
c := 1000
validators := make([]*ethpb.Validator, c)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -357,7 +357,7 @@ func TestChurnLimit_OK(t *testing.T) {
helpers.ClearCache()
validators := make([]*ethpb.Validator, test.validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
@@ -861,7 +861,7 @@ func TestLastActivatedValidatorIndex_OK(t *testing.T) {
validators := make([]*ethpb.Validator, 4)
balances := make([]uint64, len(validators))
- for i := uint64(0); i < 4; i++ {
+ for i := range uint64(4) {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, params.BeaconConfig().BLSPubkeyLength),
WithdrawalCredentials: make([]byte, 32),
diff --git a/beacon-chain/core/helpers/weak_subjectivity_test.go b/beacon-chain/core/helpers/weak_subjectivity_test.go
index 582271f328..cb11a6e8b8 100644
--- a/beacon-chain/core/helpers/weak_subjectivity_test.go
+++ b/beacon-chain/core/helpers/weak_subjectivity_test.go
@@ -270,7 +270,7 @@ func genState(t *testing.T, valCount, avgBalance uint64) state.BeaconState {
validators := make([]*ethpb.Validator, valCount)
balances := make([]uint64, len(validators))
- for i := uint64(0); i < valCount; i++ {
+ for i := range valCount {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, params.BeaconConfig().BLSPubkeyLength),
WithdrawalCredentials: make([]byte, 32),
diff --git a/beacon-chain/core/peerdas/p2p_interface_test.go b/beacon-chain/core/peerdas/p2p_interface_test.go
index b47a07e0fc..38ec58f778 100644
--- a/beacon-chain/core/peerdas/p2p_interface_test.go
+++ b/beacon-chain/core/peerdas/p2p_interface_test.go
@@ -100,7 +100,7 @@ func Test_VerifyKZGInclusionProofColumn(t *testing.T) {
// Generate random KZG commitments `blobCount` blobs.
kzgCommitments := make([][]byte, blobCount)
- for i := 0; i < blobCount; i++ {
+ for i := range blobCount {
kzgCommitments[i] = make([]byte, 48)
_, err := rand.Read(kzgCommitments[i])
require.NoError(t, err)
diff --git a/beacon-chain/core/peerdas/validator.go b/beacon-chain/core/peerdas/validator.go
index d5331524d4..aa679db806 100644
--- a/beacon-chain/core/peerdas/validator.go
+++ b/beacon-chain/core/peerdas/validator.go
@@ -216,7 +216,7 @@ func rotateRowsToCols(cellsPerBlob [][]kzg.Cell, proofsPerBlob [][]kzg.Proof, nu
if len(cells) != len(proofs) {
return nil, nil, errors.Wrap(ErrNotEnoughDataColumnSidecars, "not enough proofs")
}
- for j := uint64(0); j < numCols; j++ {
+ for j := range numCols {
if i == 0 {
cellCols[j] = make([][]byte, len(cellsPerBlob))
proofCols[j] = make([][]byte, len(cellsPerBlob))
diff --git a/beacon-chain/core/signing/signing_root_test.go b/beacon-chain/core/signing/signing_root_test.go
index ba42b1f777..b3b93d733a 100644
--- a/beacon-chain/core/signing/signing_root_test.go
+++ b/beacon-chain/core/signing/signing_root_test.go
@@ -119,7 +119,7 @@ func TestFuzzverifySigningRoot_10000(_ *testing.T) {
var p []byte
var s []byte
var d []byte
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(st)
fuzzer.Fuzz(&pubkey)
fuzzer.Fuzz(&sig)
diff --git a/beacon-chain/core/transition/benchmarks_test.go b/beacon-chain/core/transition/benchmarks_test.go
index 0422390963..d78b6c39f7 100644
--- a/beacon-chain/core/transition/benchmarks_test.go
+++ b/beacon-chain/core/transition/benchmarks_test.go
@@ -28,8 +28,7 @@ func BenchmarkExecuteStateTransition_FullBlock(b *testing.B) {
block, err := benchmark.PreGenFullBlock()
require.NoError(b, err)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
wsb, err := blocks.NewSignedBeaconBlock(block)
require.NoError(b, err)
_, err = coreState.ExecuteStateTransition(b.Context(), cleanStates[i], wsb)
@@ -60,8 +59,7 @@ func BenchmarkExecuteStateTransition_WithCache(b *testing.B) {
_, err = coreState.ExecuteStateTransition(b.Context(), beaconState, wsb)
require.NoError(b, err, "Failed to process block, benchmarks will fail")
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
wsb, err := blocks.NewSignedBeaconBlock(block)
require.NoError(b, err)
_, err = coreState.ExecuteStateTransition(b.Context(), cleanStates[i], wsb)
@@ -83,8 +81,7 @@ func BenchmarkProcessEpoch_2FullEpochs(b *testing.B) {
require.NoError(b, helpers.UpdateCommitteeCache(b.Context(), beaconState, time.CurrentEpoch(beaconState)))
require.NoError(b, beaconState.SetSlot(currentSlot))
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
// ProcessEpochPrecompute is the optimized version of process epoch. It's enabled by default
// at run time.
_, err := coreState.ProcessEpochPrecompute(b.Context(), beaconState.Copy())
@@ -96,8 +93,7 @@ func BenchmarkHashTreeRoot_FullState(b *testing.B) {
beaconState, err := benchmark.PreGenstateFullEpochs()
require.NoError(b, err)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := beaconState.HashTreeRoot(b.Context())
require.NoError(b, err)
}
@@ -113,8 +109,7 @@ func BenchmarkHashTreeRootState_FullState(b *testing.B) {
_, err = beaconState.HashTreeRoot(ctx)
require.NoError(b, err)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := beaconState.HashTreeRoot(ctx)
require.NoError(b, err)
}
@@ -128,7 +123,7 @@ func BenchmarkMarshalState_FullState(b *testing.B) {
b.Run("Proto_Marshal", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := proto.Marshal(natState)
require.NoError(b, err)
}
@@ -137,7 +132,7 @@ func BenchmarkMarshalState_FullState(b *testing.B) {
b.Run("Fast_SSZ_Marshal", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := natState.MarshalSSZ()
require.NoError(b, err)
}
@@ -157,7 +152,7 @@ func BenchmarkUnmarshalState_FullState(b *testing.B) {
b.Run("Proto_Unmarshal", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.NoError(b, proto.Unmarshal(protoObject, ðpb.BeaconState{}))
}
})
@@ -165,7 +160,7 @@ func BenchmarkUnmarshalState_FullState(b *testing.B) {
b.Run("Fast_SSZ_Unmarshal", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
sszState := ðpb.BeaconState{}
require.NoError(b, sszState.UnmarshalSSZ(sszObject))
}
@@ -174,7 +169,7 @@ func BenchmarkUnmarshalState_FullState(b *testing.B) {
func clonedStates(beaconState state.BeaconState) []state.BeaconState {
clonedStates := make([]state.BeaconState, runAmount)
- for i := 0; i < runAmount; i++ {
+ for i := range runAmount {
clonedStates[i] = beaconState.Copy()
}
return clonedStates
diff --git a/beacon-chain/core/transition/skip_slot_cache_test.go b/beacon-chain/core/transition/skip_slot_cache_test.go
index f4458eba88..4ddf30d194 100644
--- a/beacon-chain/core/transition/skip_slot_cache_test.go
+++ b/beacon-chain/core/transition/skip_slot_cache_test.go
@@ -108,7 +108,7 @@ func TestSkipSlotCache_ConcurrentMixup(t *testing.T) {
// prepare copies for both states
var setups []state.BeaconState
- for i := uint64(0); i < 300; i++ {
+ for i := range uint64(300) {
var st state.BeaconState
if i%2 == 0 {
st = s1
diff --git a/beacon-chain/core/transition/state-bellatrix.go b/beacon-chain/core/transition/state-bellatrix.go
index 55b106449a..ea444bb489 100644
--- a/beacon-chain/core/transition/state-bellatrix.go
+++ b/beacon-chain/core/transition/state-bellatrix.go
@@ -95,7 +95,7 @@ func OptimizedGenesisBeaconStateBellatrix(genesisTime uint64, preState state.Bea
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -104,17 +104,17 @@ func OptimizedGenesisBeaconStateBellatrix(genesisTime uint64, preState state.Bea
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
@@ -131,7 +131,7 @@ func OptimizedGenesisBeaconStateBellatrix(genesisTime uint64, preState state.Bea
}
scoresMissing := len(preState.Validators()) - len(scores)
if scoresMissing > 0 {
- for i := 0; i < scoresMissing; i++ {
+ for range scoresMissing {
scores = append(scores, 0)
}
}
diff --git a/beacon-chain/core/transition/state.go b/beacon-chain/core/transition/state.go
index bad69cd22e..8248515087 100644
--- a/beacon-chain/core/transition/state.go
+++ b/beacon-chain/core/transition/state.go
@@ -122,7 +122,7 @@ func OptimizedGenesisBeaconState(genesisTime uint64, preState state.BeaconState,
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -131,17 +131,17 @@ func OptimizedGenesisBeaconState(genesisTime uint64, preState state.BeaconState,
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
diff --git a/beacon-chain/core/transition/state_fuzz_test.go b/beacon-chain/core/transition/state_fuzz_test.go
index da29755479..6bc1aba27c 100644
--- a/beacon-chain/core/transition/state_fuzz_test.go
+++ b/beacon-chain/core/transition/state_fuzz_test.go
@@ -17,7 +17,7 @@ func TestGenesisBeaconState_1000(t *testing.T) {
deposits := make([]*ethpb.Deposit, 300000)
var genesisTime uint64
eth1Data := ðpb.Eth1Data{}
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(&deposits)
fuzzer.Fuzz(&genesisTime)
fuzzer.Fuzz(eth1Data)
@@ -40,7 +40,7 @@ func TestOptimizedGenesisBeaconState_1000(t *testing.T) {
preState, err := state_native.InitializeFromProtoUnsafePhase0(ðpb.BeaconState{})
require.NoError(t, err)
eth1Data := ðpb.Eth1Data{}
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(&genesisTime)
fuzzer.Fuzz(eth1Data)
fuzzer.Fuzz(preState)
@@ -60,7 +60,7 @@ func TestIsValidGenesisState_100000(_ *testing.T) {
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
var chainStartDepositCount, currentTime uint64
- for i := 0; i < 100000; i++ {
+ for range 100000 {
fuzzer.Fuzz(&chainStartDepositCount)
fuzzer.Fuzz(¤tTime)
IsValidGenesisState(chainStartDepositCount, currentTime)
diff --git a/beacon-chain/core/transition/transition_fuzz_test.go b/beacon-chain/core/transition/transition_fuzz_test.go
index 78a688c6d6..652c90e01b 100644
--- a/beacon-chain/core/transition/transition_fuzz_test.go
+++ b/beacon-chain/core/transition/transition_fuzz_test.go
@@ -21,7 +21,7 @@ func TestFuzzExecuteStateTransition_1000(t *testing.T) {
sb := ðpb.SignedBeaconBlock{}
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(sb)
if sb.Block == nil || sb.Block.Body == nil {
@@ -45,7 +45,7 @@ func TestFuzzCalculateStateRoot_1000(t *testing.T) {
sb := ðpb.SignedBeaconBlock{}
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(sb)
if sb.Block == nil || sb.Block.Body == nil {
@@ -68,7 +68,7 @@ func TestFuzzProcessSlot_1000(t *testing.T) {
require.NoError(t, err)
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
s, err := ProcessSlot(ctx, state)
if err != nil && s != nil {
@@ -86,7 +86,7 @@ func TestFuzzProcessSlots_1000(t *testing.T) {
slot := primitives.Slot(0)
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(&slot)
s, err := ProcessSlots(ctx, state, slot)
@@ -105,7 +105,7 @@ func TestFuzzprocessOperationsNoVerify_1000(t *testing.T) {
bb := ðpb.BeaconBlock{}
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(bb)
if bb.Body == nil {
@@ -128,7 +128,7 @@ func TestFuzzverifyOperationLengths_10000(t *testing.T) {
bb := ðpb.BeaconBlock{}
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(bb)
if bb.Body == nil {
@@ -148,7 +148,7 @@ func TestFuzzCanProcessEpoch_10000(t *testing.T) {
require.NoError(t, err)
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 10000; i++ {
+ for range 10000 {
fuzzer.Fuzz(state)
time.CanProcessEpoch(state)
}
@@ -162,7 +162,7 @@ func TestFuzzProcessEpochPrecompute_1000(t *testing.T) {
require.NoError(t, err)
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
s, err := ProcessEpochPrecompute(ctx, state)
if err != nil && s != nil {
@@ -180,7 +180,7 @@ func TestFuzzProcessBlockForStateRoot_1000(t *testing.T) {
sb := ðpb.SignedBeaconBlock{}
fuzzer := fuzz.NewWithSeed(0)
fuzzer.NilChance(0.1)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
fuzzer.Fuzz(state)
fuzzer.Fuzz(sb)
if sb.Block == nil || sb.Block.Body == nil || sb.Block.Body.Eth1Data == nil {
diff --git a/beacon-chain/core/transition/transition_test.go b/beacon-chain/core/transition/transition_test.go
index 908e392465..04f8263d0d 100644
--- a/beacon-chain/core/transition/transition_test.go
+++ b/beacon-chain/core/transition/transition_test.go
@@ -754,8 +754,7 @@ func BenchmarkProcessSlots_Capella(b *testing.B) {
var err error
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
st, err = transition.ProcessSlots(b.Context(), st, st.Slot()+1)
if err != nil {
b.Fatalf("Failed to process slot %v", err)
@@ -768,8 +767,7 @@ func BenchmarkProcessSlots_Deneb(b *testing.B) {
var err error
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
st, err = transition.ProcessSlots(b.Context(), st, st.Slot()+1)
if err != nil {
b.Fatalf("Failed to process slot %v", err)
@@ -782,8 +780,7 @@ func BenchmarkProcessSlots_Electra(b *testing.B) {
var err error
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
st, err = transition.ProcessSlots(b.Context(), st, st.Slot()+1)
if err != nil {
b.Fatalf("Failed to process slot %v", err)
diff --git a/beacon-chain/core/validators/validator.go b/beacon-chain/core/validators/validator.go
index 9e1079eaf1..9673400d75 100644
--- a/beacon-chain/core/validators/validator.go
+++ b/beacon-chain/core/validators/validator.go
@@ -307,7 +307,7 @@ func SlashValidator(
// ActivatedValidatorIndices determines the indices activated during the given epoch.
func ActivatedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validator) []primitives.ValidatorIndex {
activations := make([]primitives.ValidatorIndex, 0)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
val := validators[i]
if val.ActivationEpoch <= epoch && epoch < val.ExitEpoch {
activations = append(activations, primitives.ValidatorIndex(i))
@@ -319,7 +319,7 @@ func ActivatedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Valid
// SlashedValidatorIndices determines the indices slashed during the given epoch.
func SlashedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validator) []primitives.ValidatorIndex {
slashed := make([]primitives.ValidatorIndex, 0)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
val := validators[i]
maxWithdrawableEpoch := primitives.MaxEpoch(val.WithdrawableEpoch, epoch+params.BeaconConfig().EpochsPerSlashingsVector)
if val.WithdrawableEpoch == maxWithdrawableEpoch && val.Slashed {
diff --git a/beacon-chain/core/validators/validator_test.go b/beacon-chain/core/validators/validator_test.go
index 8e2edde42a..e2e5488ce3 100644
--- a/beacon-chain/core/validators/validator_test.go
+++ b/beacon-chain/core/validators/validator_test.go
@@ -172,7 +172,7 @@ func TestSlashValidator_OK(t *testing.T) {
validatorCount := 100
registry := make([]*ethpb.Validator, 0, validatorCount)
balances := make([]uint64, 0, validatorCount)
- for i := 0; i < validatorCount; i++ {
+ for range validatorCount {
registry = append(registry, ðpb.Validator{
ActivationEpoch: 0,
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
@@ -226,7 +226,7 @@ func TestSlashValidator_Electra(t *testing.T) {
validatorCount := 100
registry := make([]*ethpb.Validator, 0, validatorCount)
balances := make([]uint64, 0, validatorCount)
- for i := 0; i < validatorCount; i++ {
+ for range validatorCount {
registry = append(registry, ðpb.Validator{
ActivationEpoch: 0,
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
diff --git a/beacon-chain/das/availability_blobs_test.go b/beacon-chain/das/availability_blobs_test.go
index 1f41e96aba..c5d6cc03f2 100644
--- a/beacon-chain/das/availability_blobs_test.go
+++ b/beacon-chain/das/availability_blobs_test.go
@@ -26,7 +26,7 @@ func Test_commitmentsToCheck(t *testing.T) {
windowSlots = windowSlots + primitives.Slot(params.BeaconConfig().FuluForkEpoch)
maxBlobs := params.LastNetworkScheduleEntry().MaxBlobsPerBlock
commits := make([][]byte, maxBlobs+1)
- for i := 0; i < len(commits); i++ {
+ for i := range commits {
commits[i] = bytesutil.PadTo([]byte{byte(i)}, 48)
}
cases := []struct {
diff --git a/beacon-chain/das/blob_cache_test.go b/beacon-chain/das/blob_cache_test.go
index 8b8e41d434..e21b56f853 100644
--- a/beacon-chain/das/blob_cache_test.go
+++ b/beacon-chain/das/blob_cache_test.go
@@ -44,7 +44,7 @@ func filterTestCaseSetup(slot primitives.Slot, nBlobs int, onDisk []int, numExpe
entry.setDiskSummary(sum)
}
expected := make([]blocks.ROBlob, 0, nBlobs)
- for i := 0; i < len(commits); i++ {
+ for i := range commits {
if entry.diskSummary.HasIndex(uint64(i)) {
continue
}
diff --git a/beacon-chain/db/filesystem/blob_test.go b/beacon-chain/db/filesystem/blob_test.go
index b8356cd9cd..f1ebcff356 100644
--- a/beacon-chain/db/filesystem/blob_test.go
+++ b/beacon-chain/db/filesystem/blob_test.go
@@ -112,12 +112,10 @@ func TestBlobStorage_SaveBlobData(t *testing.T) {
blob := testSidecars[0]
var wg sync.WaitGroup
- for i := 0; i < 100; i++ {
- wg.Add(1)
- go func() {
- defer wg.Done()
+ for range 100 {
+ wg.Go(func() {
require.NoError(t, b.Save(blob))
- }()
+ })
}
wg.Wait()
diff --git a/beacon-chain/db/filesystem/cache.go b/beacon-chain/db/filesystem/cache.go
index fa5f56f03d..5c238b91a1 100644
--- a/beacon-chain/db/filesystem/cache.go
+++ b/beacon-chain/db/filesystem/cache.go
@@ -32,7 +32,7 @@ func (s BlobStorageSummary) AllAvailable(count int) bool {
if count > len(s.mask) {
return false
}
- for i := 0; i < count; i++ {
+ for i := range count {
if !s.mask[i] {
return false
}
diff --git a/beacon-chain/db/filters/filter.go b/beacon-chain/db/filters/filter.go
index 132a702603..c072c326ca 100644
--- a/beacon-chain/db/filters/filter.go
+++ b/beacon-chain/db/filters/filter.go
@@ -74,7 +74,7 @@ func (aq AncestryQuery) Span() primitives.Slot {
// QueryFilter defines a generic interface for type-asserting
// specific filters to use in querying DB objects.
type QueryFilter struct {
- queries map[FilterType]interface{}
+ queries map[FilterType]any
ancestry AncestryQuery
}
@@ -82,14 +82,14 @@ type QueryFilter struct {
// certain Ethereum data types by attribute.
func NewFilter() *QueryFilter {
return &QueryFilter{
- queries: make(map[FilterType]interface{}),
+ queries: make(map[FilterType]any),
}
}
// Filters returns and underlying map of FilterType to interface{}, giving us
// a copy of the currently set filters which can then be iterated over and type
// asserted for use anywhere.
-func (q *QueryFilter) Filters() map[FilterType]interface{} {
+func (q *QueryFilter) Filters() map[FilterType]any {
return q.queries
}
diff --git a/beacon-chain/db/kv/blocks.go b/beacon-chain/db/kv/blocks.go
index d10c3aa5c5..818ad0571f 100644
--- a/beacon-chain/db/kv/blocks.go
+++ b/beacon-chain/db/kv/blocks.go
@@ -215,7 +215,7 @@ func (s *Store) Blocks(ctx context.Context, f *filters.QueryFilter) ([]interface
return err
}
- for i := 0; i < len(keys); i++ {
+ for i := range keys {
encoded := bkt.Get(keys[i])
blk, err := unmarshalBlock(ctx, encoded)
if err != nil {
@@ -307,7 +307,7 @@ func (s *Store) BlockRoots(ctx context.Context, f *filters.QueryFilter) ([][32]b
return err
}
- for i := 0; i < len(keys); i++ {
+ for i := range keys {
blockRoots = append(blockRoots, bytesutil.ToBytes32(keys[i]))
}
return nil
@@ -1063,7 +1063,7 @@ func blockRootsByFilter(ctx context.Context, tx *bolt.Tx, f *filters.QueryFilter
func blockRootsBySlotRange(
ctx context.Context,
bkt *bolt.Bucket,
- startSlotEncoded, endSlotEncoded, startEpochEncoded, endEpochEncoded, slotStepEncoded interface{},
+ startSlotEncoded, endSlotEncoded, startEpochEncoded, endEpochEncoded, slotStepEncoded any,
) ([][]byte, error) {
_, span := trace.StartSpan(ctx, "BeaconDB.blockRootsBySlotRange")
defer span.End()
diff --git a/beacon-chain/db/kv/blocks_test.go b/beacon-chain/db/kv/blocks_test.go
index 94f1ceafb5..88511c10f4 100644
--- a/beacon-chain/db/kv/blocks_test.go
+++ b/beacon-chain/db/kv/blocks_test.go
@@ -172,7 +172,7 @@ func TestStore_SaveBlock_NoDuplicates(t *testing.T) {
// Even with a full cache, saving new blocks should not cause
// duplicated blocks in the DB.
- for i := 0; i < 100; i++ {
+ for range 100 {
require.NoError(t, db.SaveBlock(ctx, blk))
}
@@ -255,7 +255,7 @@ func TestStore_BlocksHandleZeroCase(t *testing.T) {
ctx := t.Context()
numBlocks := 10
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, numBlocks)
- for i := 0; i < len(totalBlocks); i++ {
+ for i := range totalBlocks {
b, err := tt.newBlock(primitives.Slot(i), bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
totalBlocks[i] = b
@@ -279,7 +279,7 @@ func TestStore_BlocksHandleInvalidEndSlot(t *testing.T) {
numBlocks := 10
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, numBlocks)
// Save blocks from slot 1 onwards.
- for i := 0; i < len(totalBlocks); i++ {
+ for i := range totalBlocks {
b, err := tt.newBlock(primitives.Slot(i+1), bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
totalBlocks[i] = b
@@ -927,7 +927,7 @@ func TestStore_Blocks_Retrieve_SlotRange(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
db := setupDB(t)
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, 500)
- for i := 0; i < 500; i++ {
+ for i := range 500 {
b, err := tt.newBlock(primitives.Slot(i), bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
totalBlocks[i] = b
@@ -947,7 +947,7 @@ func TestStore_Blocks_Retrieve_Epoch(t *testing.T) {
db := setupDB(t)
slots := params.BeaconConfig().SlotsPerEpoch.Mul(7)
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, slots)
- for i := primitives.Slot(0); i < slots; i++ {
+ for i := range slots {
b, err := tt.newBlock(i, bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
totalBlocks[i] = b
@@ -971,7 +971,7 @@ func TestStore_Blocks_Retrieve_SlotRangeWithStep(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
db := setupDB(t)
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, 500)
- for i := 0; i < 500; i++ {
+ for i := range 500 {
b, err := tt.newBlock(primitives.Slot(i), bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
totalBlocks[i] = b
@@ -1140,7 +1140,7 @@ func TestStore_SaveBlocks_HasCachedBlocks(t *testing.T) {
ctx := t.Context()
b := make([]interfaces.ReadOnlySignedBeaconBlock, 500)
- for i := 0; i < 500; i++ {
+ for i := range 500 {
blk, err := tt.newBlock(primitives.Slot(i), bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
b[i] = blk
@@ -1164,7 +1164,7 @@ func TestStore_SaveBlocks_HasRootsMatched(t *testing.T) {
ctx := t.Context()
b := make([]interfaces.ReadOnlySignedBeaconBlock, 500)
- for i := 0; i < 500; i++ {
+ for i := range 500 {
blk, err := tt.newBlock(primitives.Slot(i), bytesutil.PadTo([]byte("parent"), 32))
require.NoError(t, err)
b[i] = blk
diff --git a/beacon-chain/db/kv/encoding.go b/beacon-chain/db/kv/encoding.go
index d1a5c9d5b9..d894913ba0 100644
--- a/beacon-chain/db/kv/encoding.go
+++ b/beacon-chain/db/kv/encoding.go
@@ -58,7 +58,7 @@ func encode(ctx context.Context, msg proto.Message) ([]byte, error) {
}
// isSSZStorageFormat returns true if the object type should be saved in SSZ encoded format.
-func isSSZStorageFormat(obj interface{}) bool {
+func isSSZStorageFormat(obj any) bool {
switch obj.(type) {
case *ethpb.BeaconState:
return true
diff --git a/beacon-chain/db/kv/finalized_block_roots_test.go b/beacon-chain/db/kv/finalized_block_roots_test.go
index 91e2c057a6..01d3dff1cd 100644
--- a/beacon-chain/db/kv/finalized_block_roots_test.go
+++ b/beacon-chain/db/kv/finalized_block_roots_test.go
@@ -161,7 +161,7 @@ func TestStore_IsFinalizedChildBlock(t *testing.T) {
require.NoError(t, db.SaveFinalizedCheckpoint(ctx, cp))
// All blocks up to slotsPerEpoch should have a finalized child block.
- for i := uint64(0); i < slotsPerEpoch; i++ {
+ for i := range slotsPerEpoch {
root, err := blks[i].Block().HashTreeRoot()
require.NoError(t, err)
assert.Equal(t, true, db.IsFinalizedBlock(ctx, root), "Block at index %d was not considered finalized in the index", i)
diff --git a/beacon-chain/db/kv/lightclient_test.go b/beacon-chain/db/kv/lightclient_test.go
index cec59f2cbf..bb49e67b57 100644
--- a/beacon-chain/db/kv/lightclient_test.go
+++ b/beacon-chain/db/kv/lightclient_test.go
@@ -29,14 +29,14 @@ func createUpdate(t *testing.T, v int) (interfaces.LightClientUpdate, error) {
var err error
sampleRoot := make([]byte, 32)
- for i := 0; i < 32; i++ {
+ for i := range 32 {
sampleRoot[i] = byte(i)
}
sampleExecutionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
- for i := 0; i < 4; i++ {
+ for i := range 4 {
sampleExecutionBranch[i] = make([]byte, 32)
- for j := 0; j < 32; j++ {
+ for j := range 32 {
sampleExecutionBranch[i][j] = byte(i + j)
}
}
@@ -460,7 +460,7 @@ func createDefaultLightClientUpdate(currentSlot primitives.Slot, attestedState s
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
pubKeys := make([][]byte, syncCommitteeSize)
- for i := uint64(0); i < syncCommitteeSize; i++ {
+ for i := range syncCommitteeSize {
pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
nextSyncCommittee := &pb.SyncCommittee{
@@ -479,7 +479,7 @@ func createDefaultLightClientUpdate(currentSlot primitives.Slot, attestedState s
}
executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
- for i := 0; i < fieldparams.ExecutionBranchDepth; i++ {
+ for i := range fieldparams.ExecutionBranchDepth {
executionBranch[i] = make([]byte, 32)
}
@@ -731,7 +731,7 @@ func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.
currentEpoch := slots.ToEpoch(currentSlot)
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
pubKeys := make([][]byte, syncCommitteeSize)
- for i := uint64(0); i < syncCommitteeSize; i++ {
+ for i := range syncCommitteeSize {
pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
currentSyncCommittee := &pb.SyncCommittee{
@@ -750,7 +750,7 @@ func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.
}
executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
- for i := 0; i < fieldparams.ExecutionBranchDepth; i++ {
+ for i := range fieldparams.ExecutionBranchDepth {
executionBranch[i] = make([]byte, 32)
}
diff --git a/beacon-chain/db/kv/state.go b/beacon-chain/db/kv/state.go
index 485c5bcadd..a84890fcf6 100644
--- a/beacon-chain/db/kv/state.go
+++ b/beacon-chain/db/kv/state.go
@@ -922,7 +922,7 @@ func createStateIndicesFromStateSlot(ctx context.Context, slot primitives.Slot)
indices := [][]byte{
bytesutil.SlotToBytesBigEndian(slot),
}
- for i := 0; i < len(buckets); i++ {
+ for i := range buckets {
indicesByBucket[string(buckets[i])] = indices[i]
}
return indicesByBucket
diff --git a/beacon-chain/db/kv/state_test.go b/beacon-chain/db/kv/state_test.go
index 38af8b225e..ca4ec52d27 100644
--- a/beacon-chain/db/kv/state_test.go
+++ b/beacon-chain/db/kv/state_test.go
@@ -318,7 +318,7 @@ func TestState_CanSaveRetrieveValidatorEntriesFromCache(t *testing.T) {
assert.Equal(t, true, db.HasState(t.Context(), r))
// check if the state is in cache
- for i := 0; i < len(stateValidators); i++ {
+ for i := range stateValidators {
hash, hashErr := stateValidators[i].HashTreeRoot()
assert.NoError(t, hashErr)
@@ -505,7 +505,7 @@ func TestStore_StatesBatchDelete(t *testing.T) {
totalBlocks := make([]interfaces.ReadOnlySignedBeaconBlock, numBlocks)
blockRoots := make([][32]byte, 0)
evenBlockRoots := make([][32]byte, 0)
- for i := 0; i < len(totalBlocks); i++ {
+ for i := range totalBlocks {
b := util.NewBeaconBlock()
b.Block.Slot = primitives.Slot(i)
var err error
@@ -874,7 +874,7 @@ func TestAltairState_CanDelete(t *testing.T) {
func validators(limit int) []*ethpb.Validator {
var vals []*ethpb.Validator
- for i := 0; i < limit; i++ {
+ for i := range limit {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
binary.LittleEndian.PutUint64(pubKey, mathRand.Uint64())
val := ðpb.Validator{
@@ -893,13 +893,12 @@ func validators(limit int) []*ethpb.Validator {
}
func checkStateSaveTime(b *testing.B, saveCount int) {
- b.StopTimer()
db := setupDB(b)
initialSetOfValidators := validators(100000)
// construct some states and save to randomize benchmark.
- for i := 0; i < saveCount; i++ {
+ for range saveCount {
key := make([]byte, 32)
_, err := rand.Read(key)
require.NoError(b, err)
@@ -925,14 +924,13 @@ func checkStateSaveTime(b *testing.B, saveCount int) {
require.NoError(b, st.SetValidators(initialSetOfValidators))
b.ReportAllocs()
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
require.NoError(b, db.SaveState(b.Context(), st, r))
}
}
func checkStateReadTime(b *testing.B, saveCount int) {
- b.StopTimer()
db := setupDB(b)
initialSetOfValidators := validators(100000)
@@ -945,7 +943,7 @@ func checkStateReadTime(b *testing.B, saveCount int) {
require.NoError(b, db.SaveState(b.Context(), st, r))
// construct some states and save to randomize benchmark.
- for i := 0; i < saveCount; i++ {
+ for range saveCount {
key := make([]byte, 32)
_, err := rand.Read(key)
require.NoError(b, err)
@@ -965,8 +963,8 @@ func checkStateReadTime(b *testing.B, saveCount int) {
}
b.ReportAllocs()
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := db.State(b.Context(), r)
require.NoError(b, err)
}
diff --git a/beacon-chain/db/kv/utils_test.go b/beacon-chain/db/kv/utils_test.go
index cafa836337..2f1542baea 100644
--- a/beacon-chain/db/kv/utils_test.go
+++ b/beacon-chain/db/kv/utils_test.go
@@ -151,7 +151,7 @@ func TestSplitRoots(t *testing.T) {
bt := make([][32]byte, 0)
for _, x := range []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} {
var b [32]byte
- for i := 0; i < 32; i++ {
+ for i := range 32 {
b[i] = x
}
bt = append(bt, b)
diff --git a/beacon-chain/db/kv/validated_checkpoint_test.go b/beacon-chain/db/kv/validated_checkpoint_test.go
index 895ea6bbdf..387da4f8e1 100644
--- a/beacon-chain/db/kv/validated_checkpoint_test.go
+++ b/beacon-chain/db/kv/validated_checkpoint_test.go
@@ -64,8 +64,7 @@ func BenchmarkStore_SaveLastValidatedCheckpoint(b *testing.B) {
require.NoError(b, db.SaveState(ctx, st, root))
db.stateSummaryCache.clear()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.NoError(b, db.SaveLastValidatedCheckpoint(ctx, cp))
}
}
diff --git a/beacon-chain/db/pruner/pruner.go b/beacon-chain/db/pruner/pruner.go
index 7573435214..a078b1aab1 100644
--- a/beacon-chain/db/pruner/pruner.go
+++ b/beacon-chain/db/pruner/pruner.go
@@ -221,7 +221,7 @@ func (p *Service) pruneBatches(pruneUpto primitives.Slot) (int, error) {
case <-ctx.Done():
return numBatches, nil
default:
- for i := 0; i < defaultNumBatchesToPrune; i++ {
+ for range defaultNumBatchesToPrune {
slotsDeleted, err := p.db.DeleteHistoricalDataBeforeSlot(ctx, pruneUpto, defaultPrunableBatchSize)
if err != nil {
return 0, errors.Wrapf(err, "could not delete upto slot %d", pruneUpto)
diff --git a/beacon-chain/db/slasherkv/pruning_test.go b/beacon-chain/db/slasherkv/pruning_test.go
index 3e4912fd9a..cc4eee2e1d 100644
--- a/beacon-chain/db/slasherkv/pruning_test.go
+++ b/beacon-chain/db/slasherkv/pruning_test.go
@@ -66,7 +66,7 @@ func TestStore_PruneProposalsAtEpoch(t *testing.T) {
expectedNumPruned := 2 * uint(pruningLimitEpoch+1) * uint(slotsPerEpoch)
proposals := make([]*slashertypes.SignedBlockHeaderWrapper, 0, uint64(currentEpoch)*uint64(slotsPerEpoch)*2)
- for i := primitives.Epoch(0); i < currentEpoch; i++ {
+ for i := range currentEpoch {
startSlot, err := slots.EpochStart(i)
require.NoError(t, err)
endSlot, err := slots.EpochStart(i + 1)
@@ -86,7 +86,7 @@ func TestStore_PruneProposalsAtEpoch(t *testing.T) {
require.Equal(t, expectedNumPruned, actualNumPruned)
// Everything before epoch 10 should be deleted.
- for i := primitives.Epoch(0); i < pruningLimitEpoch; i++ {
+ for i := range pruningLimitEpoch {
err = beaconDB.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(proposalRecordsBucket)
startSlot, err := slots.EpochStart(i)
@@ -164,7 +164,7 @@ func TestStore_PruneAttestations_OK(t *testing.T) {
expectedNumPruned := 2 * uint(pruningLimitEpoch+1) * uint(slotsPerEpoch)
attestations := make([]*slashertypes.IndexedAttestationWrapper, 0, uint64(currentEpoch)*uint64(slotsPerEpoch)*2)
- for i := primitives.Epoch(0); i < currentEpoch; i++ {
+ for i := range currentEpoch {
startSlot, err := slots.EpochStart(i)
require.NoError(t, err)
endSlot, err := slots.EpochStart(i + 1)
@@ -191,7 +191,7 @@ func TestStore_PruneAttestations_OK(t *testing.T) {
require.Equal(t, expectedNumPruned, actualNumPruned)
// Everything before epoch 10 should be deleted.
- for i := primitives.Epoch(0); i < pruningLimitEpoch; i++ {
+ for i := range pruningLimitEpoch {
err = beaconDB.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(attestationDataRootsBucket)
startSlot, err := slots.EpochStart(i)
diff --git a/beacon-chain/db/slasherkv/slasher.go b/beacon-chain/db/slasherkv/slasher.go
index 355861e746..3246c501bd 100644
--- a/beacon-chain/db/slasherkv/slasher.go
+++ b/beacon-chain/db/slasherkv/slasher.go
@@ -429,7 +429,7 @@ func (s *Store) SaveSlasherChunks(
encodedKeys := make([][]byte, chunksCount)
encodedChunks := make([][]byte, chunksCount)
- for i := 0; i < chunksCount; i++ {
+ for i := range chunksCount {
chunkKey, chunk := chunkKeys[i], chunks[i]
encodedKey := append(encodedKind, chunkKey...)
@@ -452,7 +452,7 @@ func (s *Store) SaveSlasherChunks(
if err := s.db.Update(func(tx *bolt.Tx) error {
bkt := tx.Bucket(slasherChunksBucket)
- for i := 0; i < batchSize; i++ {
+ for i := range batchSize {
if err := bkt.Put(encodedKeysBatch[i], encodedChunksBatch[i]); err != nil {
return err
}
@@ -617,7 +617,7 @@ func (s *Store) HighestAttestations(
err = s.db.View(func(tx *bolt.Tx) error {
signingRootsBkt := tx.Bucket(attestationDataRootsBucket)
attRecordsBkt := tx.Bucket(attestationRecordsBucket)
- for i := 0; i < len(encodedIndices); i++ {
+ for i := range encodedIndices {
c := signingRootsBkt.Cursor()
for k, v := c.Last(); k != nil; k, v = c.Prev() {
if suffixForAttestationRecordsKey(k, encodedIndices[i]) {
@@ -659,7 +659,7 @@ func keyForValidatorProposal(slot primitives.Slot, proposerIndex primitives.Vali
func encodeSlasherChunk(chunk []uint16) ([]byte, error) {
val := make([]byte, 0)
- for i := 0; i < len(chunk); i++ {
+ for i := range chunk {
val = append(val, ssz.MarshalUint16(make([]byte, 0), chunk[i])...)
}
if len(val) == 0 {
diff --git a/beacon-chain/db/slasherkv/slasher_test.go b/beacon-chain/db/slasherkv/slasher_test.go
index a76128e5ee..ced8939beb 100644
--- a/beacon-chain/db/slasherkv/slasher_test.go
+++ b/beacon-chain/db/slasherkv/slasher_test.go
@@ -26,7 +26,7 @@ func TestStore_AttestationRecordForValidator_SaveRetrieve(t *testing.T) {
// Defines attestations to save and retrieve.
attWrappers := make([]*slashertypes.IndexedAttestationWrapper, attestationsCount)
- for i := 0; i < attestationsCount; i++ {
+ for i := range attestationsCount {
var dataRoot [32]byte
binary.LittleEndian.PutUint64(dataRoot[:], uint64(i))
@@ -41,7 +41,7 @@ func TestStore_AttestationRecordForValidator_SaveRetrieve(t *testing.T) {
attWrappers[i] = attWrapper
}
attWrappersElectra := make([]*slashertypes.IndexedAttestationWrapper, attestationsCount)
- for i := 0; i < attestationsCount; i++ {
+ for i := range attestationsCount {
var dataRoot [32]byte
binary.LittleEndian.PutUint64(dataRoot[:], uint64(i))
@@ -107,13 +107,13 @@ func TestStore_LastEpochWrittenForValidators(t *testing.T) {
indices := make([]primitives.ValidatorIndex, validatorsCount)
epochs := make([]primitives.Epoch, validatorsCount)
- for i := 0; i < validatorsCount; i++ {
+ for i := range validatorsCount {
indices[i] = primitives.ValidatorIndex(i)
epochs[i] = primitives.Epoch(i)
}
epochsByValidator := make(map[primitives.ValidatorIndex]primitives.Epoch, validatorsCount)
- for i := 0; i < validatorsCount; i++ {
+ for i := range validatorsCount {
epochsByValidator[indices[i]] = epochs[i]
}
@@ -215,7 +215,7 @@ func TestStore_SlasherChunk_SaveRetrieve(t *testing.T) {
minChunkKeys := make([][]byte, totalChunks)
minChunks := make([][]uint16, totalChunks)
- for i := 0; i < totalChunks; i++ {
+ for i := range totalChunks {
// Create chunk key.
chunkKey := ssz.MarshalUint64(make([]byte, 0), uint64(i))
minChunkKeys[i] = chunkKey
@@ -223,7 +223,7 @@ func TestStore_SlasherChunk_SaveRetrieve(t *testing.T) {
// Create chunk.
chunk := make([]uint16, elemsPerChunk)
- for j := 0; j < len(chunk); j++ {
+ for j := range chunk {
chunk[j] = uint16(i + j)
}
@@ -234,7 +234,7 @@ func TestStore_SlasherChunk_SaveRetrieve(t *testing.T) {
maxChunkKeys := make([][]byte, totalChunks)
maxChunks := make([][]uint16, totalChunks)
- for i := 0; i < totalChunks; i++ {
+ for i := range totalChunks {
// Create chunk key.
chunkKey := ssz.MarshalUint64(make([]byte, 0), uint64(i+1))
maxChunkKeys[i] = chunkKey
@@ -242,7 +242,7 @@ func TestStore_SlasherChunk_SaveRetrieve(t *testing.T) {
// Create chunk.
chunk := make([]uint16, elemsPerChunk)
- for j := 0; j < len(chunk); j++ {
+ for j := range chunk {
chunk[j] = uint16(i + j + 1)
}
@@ -315,7 +315,7 @@ func TestStore_SlasherChunk_PreventsSavingWrongLength(t *testing.T) {
totalChunks := 64
chunkKeys := make([][]byte, totalChunks)
chunks := make([][]uint16, totalChunks)
- for i := 0; i < totalChunks; i++ {
+ for i := range totalChunks {
chunks[i] = []uint16{}
chunkKeys[i] = ssz.MarshalUint64(make([]byte, 0), uint64(i))
}
@@ -566,11 +566,11 @@ func TestStore_HighestAttestations(t *testing.T) {
}
func BenchmarkHighestAttestations(b *testing.B) {
- b.StopTimer()
+
count := 10000
valsPerAtt := 100
indicesPerAtt := make([][]uint64, count)
- for i := 0; i < count; i++ {
+ for i := range count {
indicesForAtt := make([]uint64, valsPerAtt)
for r := i * count; r < valsPerAtt*(i+1); r++ {
indicesForAtt[i] = uint64(r)
@@ -578,7 +578,7 @@ func BenchmarkHighestAttestations(b *testing.B) {
indicesPerAtt[i] = indicesForAtt
}
atts := make([]*slashertypes.IndexedAttestationWrapper, count)
- for i := 0; i < count; i++ {
+ for i := range count {
atts[i] = createAttestationWrapper(version.Phase0, primitives.Epoch(i), primitives.Epoch(i+2), indicesPerAtt[i], []byte{})
}
@@ -587,27 +587,27 @@ func BenchmarkHighestAttestations(b *testing.B) {
require.NoError(b, beaconDB.SaveAttestationRecordsForValidators(ctx, atts))
allIndices := make([]primitives.ValidatorIndex, 0, valsPerAtt*count)
- for i := 0; i < count; i++ {
+ for i := range count {
indicesForAtt := make([]primitives.ValidatorIndex, valsPerAtt)
- for r := 0; r < valsPerAtt; r++ {
+ for r := range valsPerAtt {
indicesForAtt[r] = primitives.ValidatorIndex(atts[i].IndexedAttestation.GetAttestingIndices()[r])
}
allIndices = append(allIndices, indicesForAtt...)
}
b.ReportAllocs()
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := beaconDB.HighestAttestations(ctx, allIndices)
require.NoError(b, err)
}
}
func BenchmarkStore_CheckDoubleBlockProposals(b *testing.B) {
- b.StopTimer()
+
count := 10000
valsPerAtt := 100
indicesPerAtt := make([][]uint64, count)
- for i := 0; i < count; i++ {
+ for i := range count {
indicesForAtt := make([]uint64, valsPerAtt)
for r := i * count; r < valsPerAtt*(i+1); r++ {
indicesForAtt[i] = uint64(r)
@@ -615,7 +615,7 @@ func BenchmarkStore_CheckDoubleBlockProposals(b *testing.B) {
indicesPerAtt[i] = indicesForAtt
}
atts := make([]*slashertypes.IndexedAttestationWrapper, count)
- for i := 0; i < count; i++ {
+ for i := range count {
atts[i] = createAttestationWrapper(version.Phase0, primitives.Epoch(i), primitives.Epoch(i+2), indicesPerAtt[i], []byte{})
}
@@ -627,8 +627,8 @@ func BenchmarkStore_CheckDoubleBlockProposals(b *testing.B) {
rand.Shuffle(count, func(i, j int) { atts[i], atts[j] = atts[j], atts[i] })
b.ReportAllocs()
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := beaconDB.CheckAttesterDoubleVotes(ctx, atts)
require.NoError(b, err)
}
diff --git a/beacon-chain/execution/block_cache.go b/beacon-chain/execution/block_cache.go
index 3762e9acce..7ae86c7d83 100644
--- a/beacon-chain/execution/block_cache.go
+++ b/beacon-chain/execution/block_cache.go
@@ -39,7 +39,7 @@ var (
)
// hashKeyFn takes the hex string representation as the key for a headerInfo.
-func hashKeyFn(obj interface{}) (string, error) {
+func hashKeyFn(obj any) (string, error) {
hInfo, ok := obj.(*types.HeaderInfo)
if !ok {
return "", ErrNotAHeaderInfo
@@ -50,7 +50,7 @@ func hashKeyFn(obj interface{}) (string, error) {
// heightKeyFn takes the string representation of the block header number as the key
// for a headerInfo.
-func heightKeyFn(obj interface{}) (string, error) {
+func heightKeyFn(obj any) (string, error) {
hInfo, ok := obj.(*types.HeaderInfo)
if !ok {
return "", ErrNotAHeaderInfo
@@ -164,6 +164,6 @@ func trim(queue *cache.FIFO, maxSize uint64) {
}
// popProcessNoopFunc is a no-op function that never returns an error.
-func popProcessNoopFunc(_ interface{}, _ bool) error {
+func popProcessNoopFunc(_ any, _ bool) error {
return nil
}
diff --git a/beacon-chain/execution/block_reader.go b/beacon-chain/execution/block_reader.go
index f3a558dd46..9f09f98189 100644
--- a/beacon-chain/execution/block_reader.go
+++ b/beacon-chain/execution/block_reader.go
@@ -118,7 +118,7 @@ func (s *Service) BlockByTimestamp(ctx context.Context, time uint64) (*types.Hea
maxTimeBuffer := searchThreshold * params.BeaconConfig().SecondsPerETH1Block
// Terminate if we can't find an acceptable block after
// repeated searches.
- for i := 0; i < repeatedSearches; i++ {
+ for range repeatedSearches {
if ctx.Err() != nil {
return nil, ctx.Err()
}
diff --git a/beacon-chain/execution/block_reader_test.go b/beacon-chain/execution/block_reader_test.go
index 11cbca4fc8..7d7e8bf3a7 100644
--- a/beacon-chain/execution/block_reader_test.go
+++ b/beacon-chain/execution/block_reader_test.go
@@ -226,7 +226,7 @@ func TestService_BlockNumberByTimestamp(t *testing.T) {
params.OverrideBeaconConfig(conf)
initialHead, err := testAcc.Backend.Client().HeaderByNumber(ctx, nil)
require.NoError(t, err)
- for i := 0; i < 200; i++ {
+ for range 200 {
testAcc.Backend.Commit()
}
@@ -258,7 +258,7 @@ func TestService_BlockNumberByTimestampLessTargetTime(t *testing.T) {
web3Service = setDefaultMocks(web3Service)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
- for i := 0; i < 200; i++ {
+ for range 200 {
testAcc.Backend.Commit()
}
ctx := t.Context()
@@ -296,7 +296,7 @@ func TestService_BlockNumberByTimestampMoreTargetTime(t *testing.T) {
web3Service = setDefaultMocks(web3Service)
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
- for i := 0; i < 200; i++ {
+ for range 200 {
testAcc.Backend.Commit()
}
ctx := t.Context()
diff --git a/beacon-chain/execution/engine_client.go b/beacon-chain/execution/engine_client.go
index 84bce36665..43702a9596 100644
--- a/beacon-chain/execution/engine_client.go
+++ b/beacon-chain/execution/engine_client.go
@@ -475,7 +475,7 @@ func (s *Service) ExecutionBlocksByHashes(ctx context.Context, hashes []common.H
newH := h
elems = append(elems, gethRPC.BatchElem{
Method: BlockByHashMethod,
- Args: []interface{}{newH, withTxs},
+ Args: []any{newH, withTxs},
Result: blk,
Error: error(nil),
})
diff --git a/beacon-chain/execution/engine_client_fuzz_test.go b/beacon-chain/execution/engine_client_fuzz_test.go
index 21e8b6bdb3..64187607ef 100644
--- a/beacon-chain/execution/engine_client_fuzz_test.go
+++ b/beacon-chain/execution/engine_client_fuzz_test.go
@@ -1,6 +1,3 @@
-//go:build go1.18
-// +build go1.18
-
package execution_test
import (
@@ -164,7 +161,7 @@ func FuzzExecutionBlock(f *testing.F) {
f.Add(output)
f.Fuzz(func(t *testing.T, jsonBlob []byte) {
- gethResp := make(map[string]interface{})
+ gethResp := make(map[string]any)
prysmResp := &pb.ExecutionBlock{}
gethErr := json.Unmarshal(jsonBlob, &gethResp)
prysmErr := json.Unmarshal(jsonBlob, prysmResp)
@@ -187,10 +184,10 @@ func FuzzExecutionBlock(f *testing.F) {
gethBlob, gethErr := json.Marshal(gethResp)
prysmBlob, prysmErr := json.Marshal(prysmResp)
assert.Equal(t, gethErr != nil, prysmErr != nil, "geth and prysm unmarshaller return inconsistent errors")
- newGethResp := make(map[string]interface{})
+ newGethResp := make(map[string]any)
newGethErr := json.Unmarshal(prysmBlob, &newGethResp)
assert.NoError(t, newGethErr)
- newGethResp2 := make(map[string]interface{})
+ newGethResp2 := make(map[string]any)
newGethErr = json.Unmarshal(gethBlob, &newGethResp2)
assert.NoError(t, newGethErr)
@@ -199,13 +196,13 @@ func FuzzExecutionBlock(f *testing.F) {
})
}
-func isBogusTransactionHash(blk *pb.ExecutionBlock, jsonMap map[string]interface{}) bool {
+func isBogusTransactionHash(blk *pb.ExecutionBlock, jsonMap map[string]any) bool {
if blk.Transactions == nil {
return false
}
for i, tx := range blk.Transactions {
- jsonTx, ok := jsonMap["transactions"].([]interface{})[i].(map[string]interface{})
+ jsonTx, ok := jsonMap["transactions"].([]any)[i].(map[string]any)
if !ok {
return true
}
@@ -244,13 +241,13 @@ func compareHeaders(t *testing.T, jsonBlob []byte) {
assert.DeepEqual(t, newGethResp, newGethResp2)
}
-func validateBlockConsistency(execBlock *pb.ExecutionBlock, jsonMap map[string]interface{}) error {
+func validateBlockConsistency(execBlock *pb.ExecutionBlock, jsonMap map[string]any) error {
blockVal := reflect.ValueOf(execBlock).Elem()
- bType := reflect.TypeOf(execBlock).Elem()
+ bType := reflect.TypeFor[pb.ExecutionBlock]()
fieldnum := bType.NumField()
- for i := 0; i < fieldnum; i++ {
+ for i := range fieldnum {
field := bType.Field(i)
fName := field.Tag.Get("json")
if field.Name == "Header" {
@@ -278,12 +275,12 @@ func validateBlockConsistency(execBlock *pb.ExecutionBlock, jsonMap map[string]i
return nil
}
-func jsonFieldsAreValid(execBlock *pb.ExecutionBlock, jsonMap map[string]interface{}) (bool, error) {
- bType := reflect.TypeOf(execBlock).Elem()
+func jsonFieldsAreValid(execBlock *pb.ExecutionBlock, jsonMap map[string]any) (bool, error) {
+ bType := reflect.TypeFor[pb.ExecutionBlock]()
fieldnum := bType.NumField()
- for i := 0; i < fieldnum; i++ {
+ for i := range fieldnum {
field := bType.Field(i)
fName := field.Tag.Get("json")
if field.Name == "Header" {
diff --git a/beacon-chain/execution/engine_client_test.go b/beacon-chain/execution/engine_client_test.go
index b7368e81c4..727d345469 100644
--- a/beacon-chain/execution/engine_client_test.go
+++ b/beacon-chain/execution/engine_client_test.go
@@ -56,7 +56,7 @@ func (RPCClientBad) BatchCall([]rpc.BatchElem) error {
return errors.New("rpc client is not initialized")
}
-func (RPCClientBad) CallContext(context.Context, interface{}, string, ...interface{}) error {
+func (RPCClientBad) CallContext(context.Context, any, string, ...any) error {
return ethereum.NotFound
}
@@ -192,7 +192,7 @@ func TestClient_HTTP(t *testing.T) {
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -238,7 +238,7 @@ func TestClient_HTTP(t *testing.T) {
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -291,7 +291,7 @@ func TestClient_HTTP(t *testing.T) {
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -346,7 +346,7 @@ func TestClient_HTTP(t *testing.T) {
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -428,7 +428,7 @@ func TestClient_HTTP(t *testing.T) {
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -939,7 +939,7 @@ func TestClient_HTTP(t *testing.T) {
defer func() {
require.NoError(t, r.Body.Close())
}()
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -977,7 +977,7 @@ func TestClient_HTTP(t *testing.T) {
require.Equal(t, true, strings.Contains(
jsonRequestString, fmt.Sprintf("%#x", arg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": want,
@@ -1039,7 +1039,7 @@ func TestReconstructFullBellatrixBlock(t *testing.T) {
payload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
- jsonPayload := make(map[string]interface{})
+ jsonPayload := make(map[string]any)
tx := gethtypes.NewTransaction(
0,
common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
@@ -1064,10 +1064,10 @@ func TestReconstructFullBellatrixBlock(t *testing.T) {
defer func() {
require.NoError(t, r.Body.Close())
}()
- respJSON := map[string]interface{}{
+ respJSON := map[string]any{
"jsonrpc": "2.0",
"id": 1,
- "result": []map[string]interface{}{jsonPayload},
+ "result": []map[string]any{jsonPayload},
}
require.NoError(t, json.NewEncoder(w).Encode(respJSON))
}))
@@ -1131,7 +1131,7 @@ func TestReconstructFullBellatrixBlockBatch(t *testing.T) {
payload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
- jsonPayload := make(map[string]interface{})
+ jsonPayload := make(map[string]any)
tx := gethtypes.NewTransaction(
0,
common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
@@ -1168,10 +1168,10 @@ func TestReconstructFullBellatrixBlockBatch(t *testing.T) {
require.NoError(t, r.Body.Close())
}()
- respJSON := map[string]interface{}{
+ respJSON := map[string]any{
"jsonrpc": "2.0",
"id": 1,
- "result": []map[string]interface{}{jsonPayload},
+ "result": []map[string]any{jsonPayload},
}
require.NoError(t, json.NewEncoder(w).Encode(respJSON))
@@ -1206,7 +1206,7 @@ func TestReconstructFullBellatrixBlockBatch(t *testing.T) {
payload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
- jsonPayload := make(map[string]interface{})
+ jsonPayload := make(map[string]any)
tx := gethtypes.NewTransaction(
0,
common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
@@ -1241,10 +1241,10 @@ func TestReconstructFullBellatrixBlockBatch(t *testing.T) {
require.NoError(t, r.Body.Close())
}()
- respJSON := map[string]interface{}{
+ respJSON := map[string]any{
"jsonrpc": "2.0",
"id": 1,
- "result": []map[string]interface{}{},
+ "result": []map[string]any{},
}
require.NoError(t, json.NewEncoder(w).Encode(respJSON))
@@ -1473,7 +1473,7 @@ func (c *customError) Timeout() bool {
type dataError struct {
code int
- data interface{}
+ data any
}
func (c *dataError) ErrorCode() int {
@@ -1484,7 +1484,7 @@ func (*dataError) Error() string {
return "something went wrong"
}
-func (c *dataError) ErrorData() interface{} {
+func (c *dataError) ErrorData() any {
return c.data
}
@@ -1576,9 +1576,9 @@ func newTestIPCServer(t *testing.T) *rpc.Server {
return server
}
-func fixtures() map[string]interface{} {
+func fixtures() map[string]any {
s := fixturesStruct()
- return map[string]interface{}{
+ return map[string]any{
"ExecutionBlock": s.ExecutionBlock,
"ExecutionPayloadBody": s.ExecutionPayloadBody,
"ExecutionPayload": s.ExecutionPayload,
@@ -2173,7 +2173,7 @@ func forkchoiceUpdateSetup(t *testing.T, fcs *pb.ForkchoiceState, att *pb.Payloa
require.Equal(t, true, strings.Contains(
jsonRequestString, string(payloadAttrsReq),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": res,
@@ -2212,7 +2212,7 @@ func forkchoiceUpdateSetupV2(t *testing.T, fcs *pb.ForkchoiceState, att *pb.Payl
require.Equal(t, true, strings.Contains(
jsonRequestString, string(payloadAttrsReq),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": res,
@@ -2246,7 +2246,7 @@ func newPayloadSetup(t *testing.T, status *pb.PayloadStatus, payload *pb.Executi
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": status,
@@ -2280,7 +2280,7 @@ func newPayloadV2Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.Execu
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": status,
@@ -2314,7 +2314,7 @@ func newPayloadV3Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.Execu
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": status,
@@ -2362,7 +2362,7 @@ func newPayloadV4Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.Execu
jsonRequestString, string(jsonRequests),
))
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": status,
@@ -2418,7 +2418,7 @@ func Test_ExchangeCapabilities(t *testing.T) {
defer func() {
require.NoError(t, r.Body.Close())
}()
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": []string{},
@@ -2451,7 +2451,7 @@ func Test_ExchangeCapabilities(t *testing.T) {
require.NoError(t, r.Body.Close())
}()
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": []string{"A", "B", "C"},
@@ -2509,7 +2509,7 @@ func TestReconstructBlobSidecars(t *testing.T) {
require.Equal(t, 0, len(verifiedBlobs))
})
- client.capabilityCache = &capabilityCache{capabilities: map[string]interface{}{GetBlobsV1: nil}}
+ client.capabilityCache = &capabilityCache{capabilities: map[string]any{GetBlobsV1: nil}}
t.Run("recovered 6 missing blobs", func(t *testing.T) {
srv := createBlobServer(t, 6)
@@ -2652,10 +2652,10 @@ func createBlobServer(t *testing.T, numBlobs int, callbackFuncs ...func()) *http
blobs := make([]pb.BlobAndProofJson, numBlobs)
for i := range blobs {
- blobs[i] = pb.BlobAndProofJson{Blob: []byte(fmt.Sprintf("blob%d", i+1)), KzgProof: []byte(fmt.Sprintf("proof%d", i+1))}
+ blobs[i] = pb.BlobAndProofJson{Blob: fmt.Appendf(nil, "blob%d", i+1), KzgProof: fmt.Appendf(nil, "proof%d", i+1)}
}
- respJSON := map[string]interface{}{
+ respJSON := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": blobs,
@@ -2689,7 +2689,7 @@ func createBlobServerV2(t *testing.T, numBlobs int, blobMasks []bool) *httptest.
}
}
- respJSON := map[string]interface{}{
+ respJSON := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": blobAndCellProofs,
@@ -2705,7 +2705,7 @@ func setupRpcClient(t *testing.T, url string, client *Service) (*rpc.Client, *Se
require.NoError(t, err)
client.rpcClient = rpcClient
- client.capabilityCache = &capabilityCache{capabilities: map[string]interface{}{GetBlobsV1: nil}}
+ client.capabilityCache = &capabilityCache{capabilities: map[string]any{GetBlobsV1: nil}}
client.blobVerifier = testNewBlobVerifier()
return rpcClient, client
@@ -2713,7 +2713,7 @@ func setupRpcClient(t *testing.T, url string, client *Service) (*rpc.Client, *Se
func setupRpcClientV2(t *testing.T, url string, client *Service) (*rpc.Client, *Service) {
rpcClient, client := setupRpcClient(t, url, client)
- client.capabilityCache = &capabilityCache{capabilities: map[string]interface{}{GetBlobsV2: nil}}
+ client.capabilityCache = &capabilityCache{capabilities: map[string]any{GetBlobsV2: nil}}
return rpcClient, client
}
diff --git a/beacon-chain/execution/log_processing_test.go b/beacon-chain/execution/log_processing_test.go
index a3673bc797..11bd290a8a 100644
--- a/beacon-chain/execution/log_processing_test.go
+++ b/beacon-chain/execution/log_processing_test.go
@@ -254,7 +254,7 @@ func TestProcessETH2GenesisLog_8DuplicatePubkeys(t *testing.T) {
// 64 Validators are used as size required for beacon-chain to start. This number
// is defined in the deposit contract as the number required for the testnet. The actual number
// is 2**14
- for i := 0; i < depositsReqForChainStart; i++ {
+ for range depositsReqForChainStart {
testAcc.TxOpts.Value = mock.Amount32Eth()
_, err = testAcc.Contract.Deposit(testAcc.TxOpts, data.PublicKey, data.WithdrawalCredentials, data.Signature, depositRoots[0])
require.NoError(t, err, "Could not deposit to deposit contract")
@@ -325,7 +325,7 @@ func TestProcessETH2GenesisLog(t *testing.T) {
// 64 Validators are used as size required for beacon-chain to start. This number
// is defined in the deposit contract as the number required for the testnet. The actual number
// is 2**14
- for i := 0; i < depositsReqForChainStart; i++ {
+ for i := range depositsReqForChainStart {
data := deposits[i].Data
testAcc.TxOpts.Value = mock.Amount32Eth()
testAcc.TxOpts.GasLimit = 1000000
@@ -429,7 +429,7 @@ func TestProcessETH2GenesisLog_CorrectNumOfDeposits(t *testing.T) {
// 64 Validators are used as size required for beacon-chain to start. This number
// is defined in the deposit contract as the number required for the testnet. The actual number
// is 2**14
- for i := 0; i < totalNumOfDeposits; i++ {
+ for i := range totalNumOfDeposits {
data := deposits[i].Data
testAcc.TxOpts.Value = mock.Amount32Eth()
testAcc.TxOpts.GasLimit = 1000000
@@ -530,7 +530,7 @@ func TestProcessLogs_DepositRequestsStarted(t *testing.T) {
// 64 Validators are used as size required for beacon-chain to start. This number
// is defined in the deposit contract as the number required for the testnet. The actual number
// is 2**14
- for i := 0; i < totalNumOfDeposits; i++ {
+ for i := range totalNumOfDeposits {
data := deposits[i].Data
testAcc.TxOpts.Value = mock.Amount32Eth()
testAcc.TxOpts.GasLimit = 1000000
@@ -616,7 +616,7 @@ func TestProcessETH2GenesisLog_LargePeriodOfNoLogs(t *testing.T) {
// 64 Validators are used as size required for beacon-chain to start. This number
// is defined in the deposit contract as the number required for the testnet. The actual number
// is 2**14
- for i := 0; i < totalNumOfDeposits; i++ {
+ for i := range totalNumOfDeposits {
data := deposits[i].Data
testAcc.TxOpts.Value = mock.Amount32Eth()
testAcc.TxOpts.GasLimit = 1000000
@@ -629,7 +629,7 @@ func TestProcessETH2GenesisLog_LargePeriodOfNoLogs(t *testing.T) {
}
}
// Forward the chain to 'mine' blocks without logs
- for i := uint64(0); i < 1500; i++ {
+ for range uint64(1500) {
testAcc.Backend.Commit()
}
genesisBlock, err := testAcc.Backend.Client().BlockByNumber(t.Context(), nil)
diff --git a/beacon-chain/execution/mock_test.go b/beacon-chain/execution/mock_test.go
index 500f71c529..c39b7ed979 100644
--- a/beacon-chain/execution/mock_test.go
+++ b/beacon-chain/execution/mock_test.go
@@ -15,9 +15,9 @@ import (
var mockHandlerDefaultName = "__default__"
type jsonError struct {
- Code int `json:"code"`
- Message string `json:"message"`
- Data interface{} `json:"data,omitempty"`
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Data any `json:"data,omitempty"`
}
type jsonrpcMessage struct {
@@ -154,12 +154,12 @@ func TestParseRequest(t *testing.T) {
})
result := make([]*pb.ExecutionPayloadBody, 0)
- var args []interface{}
+ var args []any
if len(c.byteArgs) > 0 {
- args = []interface{}{c.byteArgs}
+ args = []any{c.byteArgs}
}
if len(c.hexArgs) > 0 {
- args = make([]interface{}, len(c.hexArgs))
+ args = make([]any, len(c.hexArgs))
for i := range c.hexArgs {
args[i] = c.hexArgs[i]
}
diff --git a/beacon-chain/execution/payload_body.go b/beacon-chain/execution/payload_body.go
index bda68063f5..fe10bbdd8d 100644
--- a/beacon-chain/execution/payload_body.go
+++ b/beacon-chain/execution/payload_body.go
@@ -142,7 +142,7 @@ func computeRanges(hbns []hashBlockNumber) []byRangeReq {
ranges := make([]byRangeReq, 0)
start := hbns[0].n
count := uint64(0)
- for i := 0; i < len(hbns); i++ {
+ for i := range hbns {
if hbns[i].n == start+count {
count++
continue
diff --git a/beacon-chain/execution/service.go b/beacon-chain/execution/service.go
index f621fd1d3c..f9d35fd7a5 100644
--- a/beacon-chain/execution/service.go
+++ b/beacon-chain/execution/service.go
@@ -103,7 +103,7 @@ type Chain interface {
type RPCClient interface {
Close()
BatchCall(b []gethRPC.BatchElem) error
- CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
+ CallContext(ctx context.Context, result any, method string, args ...any) error
}
type RPCClientEmpty struct {
@@ -114,7 +114,7 @@ func (RPCClientEmpty) BatchCall([]gethRPC.BatchElem) error {
return errors.New("rpc client is not initialized")
}
-func (RPCClientEmpty) CallContext(context.Context, interface{}, string, ...interface{}) error {
+func (RPCClientEmpty) CallContext(context.Context, any, string, ...any) error {
return errors.New("rpc client is not initialized")
}
@@ -426,7 +426,7 @@ func (s *Service) batchRequestHeaders(startBlock, endBlock uint64) ([]*types.Hea
header := &types.HeaderInfo{}
elems = append(elems, gethRPC.BatchElem{
Method: "eth_getBlockByNumber",
- Args: []interface{}{hexutil.EncodeBig(new(big.Int).SetUint64(i)), false},
+ Args: []any{hexutil.EncodeBig(new(big.Int).SetUint64(i)), false},
Result: header,
Error: error(nil),
})
@@ -922,7 +922,7 @@ func newBlobVerifierFromInitializer(ini *verification.Initializer) verification.
}
type capabilityCache struct {
- capabilities map[string]interface{}
+ capabilities map[string]any
capabilitiesLock sync.RWMutex
}
@@ -931,7 +931,7 @@ func (c *capabilityCache) save(cs []string) {
defer c.capabilitiesLock.Unlock()
if c.capabilities == nil {
- c.capabilities = make(map[string]interface{})
+ c.capabilities = make(map[string]any)
}
for _, capability := range cs {
diff --git a/beacon-chain/execution/service_test.go b/beacon-chain/execution/service_test.go
index fe4bc36e81..e7be8bd9cb 100644
--- a/beacon-chain/execution/service_test.go
+++ b/beacon-chain/execution/service_test.go
@@ -61,7 +61,7 @@ func (g *goodLogger) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQ
func (g *goodLogger) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]gethTypes.Log, error) {
if g.backend == nil {
logs := make([]gethTypes.Log, 3)
- for i := 0; i < len(logs); i++ {
+ for i := range logs {
logs[i].Address = common.Address{}
logs[i].Topics = make([]common.Hash, 5)
logs[i].Topics[0] = common.Hash{'a'}
@@ -246,7 +246,7 @@ func TestFollowBlock_OK(t *testing.T) {
numToForward := uint64(2)
expectedHeight := numToForward + baseHeight
// forward 2 blocks
- for i := uint64(0); i < numToForward; i++ {
+ for range numToForward {
testAcc.Backend.Commit()
}
@@ -344,7 +344,7 @@ func TestLogTillGenesis_OK(t *testing.T) {
web3Service.rpcClient = &mockExecution.RPCClient{Backend: testAcc.Backend}
web3Service.httpLogger = testAcc.Backend.Client()
- for i := 0; i < 30; i++ {
+ for range 30 {
testAcc.Backend.Commit()
}
web3Service.latestEth1Data = ðpb.LatestETH1Data{LastRequestedBlock: 0}
@@ -498,7 +498,7 @@ func TestNewService_EarliestVotingBlock(t *testing.T) {
numToForward := 1500
// forward 1500 blocks
- for i := 0; i < numToForward; i++ {
+ for range numToForward {
testAcc.Backend.Commit()
}
currHeader, err := testAcc.Backend.Client().HeaderByNumber(t.Context(), nil)
@@ -695,7 +695,7 @@ func TestService_ValidateDepositContainers(t *testing.T) {
name: "ordered containers",
ctrsFunc: func() []*ethpb.DepositContainer {
ctrs := make([]*ethpb.DepositContainer, 0)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
ctrs = append(ctrs, ðpb.DepositContainer{Index: int64(i), Eth1BlockHeight: uint64(i + 10)})
}
return ctrs
@@ -717,7 +717,7 @@ func TestService_ValidateDepositContainers(t *testing.T) {
name: "skipped containers",
ctrsFunc: func() []*ethpb.DepositContainer {
ctrs := make([]*ethpb.DepositContainer, 0)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
if i == 5 || i == 7 {
continue
}
@@ -833,7 +833,7 @@ func (s *slowRPCClient) BatchCall(b []rpc.BatchElem) error {
return nil
}
-func (s *slowRPCClient) CallContext(_ context.Context, _ interface{}, _ string, _ ...interface{}) error {
+func (s *slowRPCClient) CallContext(_ context.Context, _ any, _ string, _ ...any) error {
panic("implement me")
}
@@ -869,7 +869,7 @@ func TestService_migrateOldDepositTree(t *testing.T) {
dt, err := trie.NewTrie(32)
require.NoError(t, err)
- for i := 0; i < totalDeposits; i++ {
+ for i := range totalDeposits {
err := dt.Insert(input[:], i)
require.NoError(t, err)
}
diff --git a/beacon-chain/execution/testing/mock_execution_chain.go b/beacon-chain/execution/testing/mock_execution_chain.go
index c1eb76d369..bdbc40e88e 100644
--- a/beacon-chain/execution/testing/mock_execution_chain.go
+++ b/beacon-chain/execution/testing/mock_execution_chain.go
@@ -147,7 +147,7 @@ type RPCClient struct {
func (*RPCClient) Close() {}
-func (r *RPCClient) CallContext(ctx context.Context, obj interface{}, methodName string, args ...interface{}) error {
+func (r *RPCClient) CallContext(ctx context.Context, obj any, methodName string, args ...any) error {
if r.BlockNumMap != nil && methodName == "eth_getBlockByNumber" {
val, ok := args[0].(string)
if !ok {
diff --git a/beacon-chain/forkchoice/doubly-linked-tree/proposer_boost_test.go b/beacon-chain/forkchoice/doubly-linked-tree/proposer_boost_test.go
index fe8e12e18c..80f88ac8c6 100644
--- a/beacon-chain/forkchoice/doubly-linked-tree/proposer_boost_test.go
+++ b/beacon-chain/forkchoice/doubly-linked-tree/proposer_boost_test.go
@@ -31,7 +31,7 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
jEpoch, fEpoch := primitives.Epoch(0), primitives.Epoch(0)
zeroHash := params.BeaconConfig().ZeroHash
balances := make([]uint64, 64) // 64 active validators.
- for i := 0; i < len(balances); i++ {
+ for i := range balances {
balances[i] = 10
}
t.Run("back-propagates boost score to ancestors after proposer boosting", func(t *testing.T) {
@@ -482,7 +482,7 @@ func TestForkChoice_missingProposerBoostRoots(t *testing.T) {
ctx := t.Context()
f := setup(1, 1)
balances := make([]uint64, 64) // 64 active validators.
- for i := 0; i < len(balances); i++ {
+ for i := range balances {
balances[i] = 10
}
f.justifiedBalances = balances
diff --git a/beacon-chain/light-client/helpers.go b/beacon-chain/light-client/helpers.go
index ef37bd082a..f23202a5cd 100644
--- a/beacon-chain/light-client/helpers.go
+++ b/beacon-chain/light-client/helpers.go
@@ -23,7 +23,7 @@ func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.
currentEpoch := slots.ToEpoch(currentSlot)
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
pubKeys := make([][]byte, syncCommitteeSize)
- for i := uint64(0); i < syncCommitteeSize; i++ {
+ for i := range syncCommitteeSize {
pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
currentSyncCommittee := &pb.SyncCommittee{
@@ -42,7 +42,7 @@ func createDefaultLightClientBootstrap(currentSlot primitives.Slot) (interfaces.
}
executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
- for i := 0; i < fieldparams.ExecutionBranchDepth; i++ {
+ for i := range fieldparams.ExecutionBranchDepth {
executionBranch[i] = make([]byte, 32)
}
diff --git a/beacon-chain/light-client/lightclient.go b/beacon-chain/light-client/lightclient.go
index 7e6063c5b4..29cdde2cc3 100644
--- a/beacon-chain/light-client/lightclient.go
+++ b/beacon-chain/light-client/lightclient.go
@@ -243,7 +243,7 @@ func NewLightClientUpdateFromBeaconState(
func CreateDefaultLightClientUpdate(attestedBlock interfaces.ReadOnlySignedBeaconBlock) (interfaces.LightClientUpdate, error) {
syncCommitteeSize := params.BeaconConfig().SyncCommitteeSize
pubKeys := make([][]byte, syncCommitteeSize)
- for i := uint64(0); i < syncCommitteeSize; i++ {
+ for i := range syncCommitteeSize {
pubKeys[i] = make([]byte, fieldparams.BLSPubkeyLength)
}
nextSyncCommittee := &pb.SyncCommittee{
@@ -262,7 +262,7 @@ func CreateDefaultLightClientUpdate(attestedBlock interfaces.ReadOnlySignedBeaco
}
executionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
- for i := 0; i < fieldparams.ExecutionBranchDepth; i++ {
+ for i := range fieldparams.ExecutionBranchDepth {
executionBranch[i] = make([]byte, 32)
}
diff --git a/beacon-chain/light-client/lightclient_test.go b/beacon-chain/light-client/lightclient_test.go
index 09255a26af..8c1e5a544c 100644
--- a/beacon-chain/light-client/lightclient_test.go
+++ b/beacon-chain/light-client/lightclient_test.go
@@ -76,7 +76,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
//zeroHash := params.BeaconConfig().ZeroHash[:]
require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil")
- require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderAltair{}), "Finalized header is not Altair")
+ require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeFor[*pb.LightClientHeaderAltair](), "Finalized header is not Altair")
updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon()
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
@@ -109,7 +109,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
finalizedBlockHeader, err := l.FinalizedBlock.Header()
require.NoError(t, err)
require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil")
- require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderCapella{}), "Finalized header is not Capella")
+ require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeFor[*pb.LightClientHeaderCapella](), "Finalized header is not Capella")
updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon()
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
@@ -183,7 +183,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
finalizedBlockHeader, err := l.FinalizedBlock.Header()
require.NoError(t, err)
require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil")
- require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderCapella{}), "Finalized header is not Capella")
+ require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeFor[*pb.LightClientHeaderCapella](), "Finalized header is not Capella")
updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon()
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
@@ -293,7 +293,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil")
updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon()
- require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeOf(&pb.LightClientHeaderDeneb{}), "Finalized header is not Deneb")
+ require.Equal(t, reflect.TypeOf(update.FinalizedHeader().Proto()), reflect.TypeFor[*pb.LightClientHeaderDeneb](), "Finalized header is not Deneb")
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
diff --git a/beacon-chain/light-client/store.go b/beacon-chain/light-client/store.go
index 74da9f343f..d2ce7e77bd 100644
--- a/beacon-chain/light-client/store.go
+++ b/beacon-chain/light-client/store.go
@@ -2,6 +2,7 @@ package light_client
import (
"context"
+ "maps"
"sync"
"github.com/OffchainLabs/prysm/v7/async/event"
@@ -202,9 +203,7 @@ func (s *Store) LightClientUpdates(ctx context.Context, startPeriod, endPeriod u
return nil, errors.Wrapf(err, "failed to get updates from cache")
}
- for period, update := range cacheUpdatesByPeriod {
- updatesMap[period] = update
- }
+ maps.Copy(updatesMap, cacheUpdatesByPeriod)
var updates []interfaces.LightClientUpdate
diff --git a/beacon-chain/light-client/store_test.go b/beacon-chain/light-client/store_test.go
index 06bd48f9d3..2f399ecaa6 100644
--- a/beacon-chain/light-client/store_test.go
+++ b/beacon-chain/light-client/store_test.go
@@ -366,7 +366,7 @@ func TestLightClientStore_MigrateToCold(t *testing.T) {
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
- for i := 0; i < 3; i++ {
+ for i := range 3 {
newBlock := util.NewBeaconBlock()
newBlock.Block.Slot = primitives.Slot(32 + uint64(i))
newBlock.Block.ParentRoot = finalizedBlockRoot[:]
@@ -394,7 +394,7 @@ func TestLightClientStore_MigrateToCold(t *testing.T) {
s := NewLightClientStore(&p2pTesting.FakeP2P{}, new(event.Feed), beaconDB)
require.NotNil(t, s)
- for i := 0; i < 3; i++ {
+ for i := range 3 {
newBlock := util.NewBeaconBlock()
newBlock.Block.Slot = primitives.Slot(32 + uint64(i))
newBlock.Block.ParentRoot = finalizedBlockRoot[:]
@@ -844,7 +844,7 @@ func TestLightClientStore_LightClientUpdatesByRange(t *testing.T) {
updates, err := s.LightClientUpdates(ctx, 1, 5, headBlock)
require.NoError(t, err)
require.Equal(t, 5, len(updates))
- for i := 0; i < 5; i++ {
+ for i := range 5 {
require.DeepEqual(t, update, updates[i], "Expected to find the update in the store")
}
})
@@ -899,7 +899,7 @@ func TestLightClientStore_LightClientUpdatesByRange(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 5, len(updates))
// first two updates should be update1
- for i := 0; i < 2; i++ {
+ for i := range 2 {
require.DeepEqual(t, update1, updates[i], "Expected to find the update in the store")
}
// next three updates should be update2 - as cache overrides db
diff --git a/beacon-chain/monitor/service.go b/beacon-chain/monitor/service.go
index 7674bb2d76..0771f201c0 100644
--- a/beacon-chain/monitor/service.go
+++ b/beacon-chain/monitor/service.go
@@ -3,7 +3,7 @@ package monitor
import (
"context"
"errors"
- "sort"
+ "slices"
"sync"
"github.com/OffchainLabs/prysm/v7/async/event"
@@ -108,7 +108,7 @@ func (s *Service) Start() {
for idx := range s.TrackedValidators {
tracked = append(tracked, idx)
}
- sort.Slice(tracked, func(i, j int) bool { return tracked[i] < tracked[j] })
+ slices.Sort(tracked)
log.WithFields(logrus.Fields{
"validatorIndices": tracked,
diff --git a/beacon-chain/monitor/service_test.go b/beacon-chain/monitor/service_test.go
index e4c4d84243..0e83bd46c2 100644
--- a/beacon-chain/monitor/service_test.go
+++ b/beacon-chain/monitor/service_test.go
@@ -204,12 +204,10 @@ func TestMonitorRoutine(t *testing.T) {
stateSub := s.config.StateNotifier.StateFeed().Subscribe(stateChannel)
wg := &sync.WaitGroup{}
- wg.Add(1)
- go func() {
+ wg.Go(func() {
s.monitorRoutine(stateChannel, stateSub)
- wg.Done()
- }()
+ })
genesis, keys := util.DeterministicGenesisStateAltair(t, 64)
c, err := altair.NextSyncCommittee(ctx, genesis)
diff --git a/beacon-chain/node/config_test.go b/beacon-chain/node/config_test.go
index f03e106a62..0dc9d39c86 100644
--- a/beacon-chain/node/config_test.go
+++ b/beacon-chain/node/config_test.go
@@ -138,9 +138,9 @@ func TestConfigureNetwork_ConfigFile(t *testing.T) {
set := flag.NewFlagSet("test", 0)
context := cli.NewContext(&app, set, nil)
- require.NoError(t, os.WriteFile("flags_test.yaml", []byte(fmt.Sprintf("%s:\n - %s\n - %s\n", cmd.BootstrapNode.Name,
+ require.NoError(t, os.WriteFile("flags_test.yaml", fmt.Appendf(nil, "%s:\n - %s\n - %s\n", cmd.BootstrapNode.Name,
"node1",
- "node2")), 0666))
+ "node2"), 0666))
require.NoError(t, set.Parse([]string{"test-command", "--" + cmd.ConfigFileFlag.Name, "flags_test.yaml"}))
comFlags := cmd.WrapFlags([]cli.Flag{
diff --git a/beacon-chain/operations/attestations/kv/aggregated.go b/beacon-chain/operations/attestations/kv/aggregated.go
index e15e439b11..3339f36d89 100644
--- a/beacon-chain/operations/attestations/kv/aggregated.go
+++ b/beacon-chain/operations/attestations/kv/aggregated.go
@@ -72,7 +72,7 @@ func (c *AttCaches) aggregateParallel(atts map[attestation.Id][]ethpb.Att, leftO
n := runtime.GOMAXPROCS(0) // defaults to the value of runtime.NumCPU
ch := make(chan []ethpb.Att, n)
wg.Add(n)
- for i := 0; i < n; i++ {
+ for range n {
go func() {
defer wg.Done()
for as := range ch {
diff --git a/beacon-chain/operations/blstoexec/pool.go b/beacon-chain/operations/blstoexec/pool.go
index 447b0f9a14..809a6d7647 100644
--- a/beacon-chain/operations/blstoexec/pool.go
+++ b/beacon-chain/operations/blstoexec/pool.go
@@ -1,6 +1,7 @@
package blstoexec
import (
+ "maps"
"sync"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
@@ -54,9 +55,7 @@ func NewPool() *Pool {
// Copies the internal map and returns a new one.
func (p *Pool) cycleMap() {
newMap := make(map[primitives.ValidatorIndex]*doublylinkedlist.Node[*ethpb.SignedBLSToExecutionChange])
- for k, v := range p.m {
- newMap[k] = v
- }
+ maps.Copy(newMap, p.m)
p.m = newMap
}
diff --git a/beacon-chain/operations/blstoexec/pool_test.go b/beacon-chain/operations/blstoexec/pool_test.go
index 76ad873dce..a54867ca42 100644
--- a/beacon-chain/operations/blstoexec/pool_test.go
+++ b/beacon-chain/operations/blstoexec/pool_test.go
@@ -122,7 +122,7 @@ func TestBLSToExecChangesForInclusion(t *testing.T) {
})
t.Run("more than MaxBlsToExecutionChanges in pool", func(t *testing.T) {
pool := NewPool()
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
pool.InsertBLSToExecChange(signedChanges[i])
}
changes, err := pool.BLSToExecChangesForInclusion(st)
@@ -137,7 +137,7 @@ func TestBLSToExecChangesForInclusion(t *testing.T) {
pool := NewPool()
saveByte := signedChanges[1].Message.FromBlsPubkey[5]
signedChanges[1].Message.FromBlsPubkey[5] = 0xff
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
pool.InsertBLSToExecChange(signedChanges[i])
}
changes, err := pool.BLSToExecChangesForInclusion(st)
@@ -149,7 +149,7 @@ func TestBLSToExecChangesForInclusion(t *testing.T) {
t.Run("One Bad Signature", func(t *testing.T) {
pool := NewPool()
copy(signedChanges[30].Signature, signedChanges[31].Signature)
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
pool.InsertBLSToExecChange(signedChanges[i])
}
changes, err := pool.BLSToExecChangesForInclusion(st)
diff --git a/beacon-chain/operations/slashings/service_attester_test.go b/beacon-chain/operations/slashings/service_attester_test.go
index 6f74036cb8..3758d41fc9 100644
--- a/beacon-chain/operations/slashings/service_attester_test.go
+++ b/beacon-chain/operations/slashings/service_attester_test.go
@@ -105,7 +105,7 @@ func TestPool_InsertAttesterSlashing(t *testing.T) {
setupFunc := func(beaconState state.BeaconState, privKeys []bls.SecretKey) []testCase {
pendingSlashings := make([]*PendingAttesterSlashing, 20)
slashings := make([]ethpb.AttSlashing, 20)
- for i := 0; i < len(pendingSlashings); i++ {
+ for i := range pendingSlashings {
generatedSl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
pendingSlashings[i] = &PendingAttesterSlashing{
@@ -341,7 +341,7 @@ func TestPool_InsertAttesterSlashing_SigFailsVerify_ClearPool(t *testing.T) {
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
pendingSlashings := make([]*PendingAttesterSlashing, 2)
slashings := make([]*ethpb.AttesterSlashing, 2)
- for i := 0; i < 2; i++ {
+ for i := range 2 {
generatedSl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
pendingSlashings[i] = &PendingAttesterSlashing{
@@ -522,7 +522,7 @@ func TestPool_PendingAttesterSlashings(t *testing.T) {
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
pendingSlashings := make([]*PendingAttesterSlashing, 20)
slashings := make([]ethpb.AttSlashing, 20)
- for i := 0; i < len(pendingSlashings); i++ {
+ for i := range pendingSlashings {
sl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
pendingSlashings[i] = &PendingAttesterSlashing{
@@ -586,7 +586,7 @@ func TestPool_PendingAttesterSlashings_AfterElectra(t *testing.T) {
pendingSlashings := make([]*PendingAttesterSlashing, 20)
slashings := make([]ethpb.AttSlashing, 20)
- for i := 0; i < len(pendingSlashings); i++ {
+ for i := range pendingSlashings {
sl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
pendingSlashings[i] = &PendingAttesterSlashing{
@@ -661,7 +661,7 @@ func TestPool_PendingAttesterSlashings_Slashed(t *testing.T) {
pendingSlashings := make([]*PendingAttesterSlashing, 20)
pendingSlashings2 := make([]*PendingAttesterSlashing, 20)
slashings := make([]ethpb.AttSlashing, 20)
- for i := 0; i < len(pendingSlashings); i++ {
+ for i := range pendingSlashings {
sl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
pendingSlashings[i] = &PendingAttesterSlashing{
@@ -719,7 +719,7 @@ func TestPool_PendingAttesterSlashings_NoDuplicates(t *testing.T) {
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
pendingSlashings := make([]*PendingAttesterSlashing, 3)
slashings := make([]ethpb.AttSlashing, 3)
- for i := 0; i < 2; i++ {
+ for i := range 2 {
sl, err := util.GenerateAttesterSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
pendingSlashings[i] = &PendingAttesterSlashing{
diff --git a/beacon-chain/operations/slashings/service_proposer_test.go b/beacon-chain/operations/slashings/service_proposer_test.go
index d8e043d740..2c3ffd41cb 100644
--- a/beacon-chain/operations/slashings/service_proposer_test.go
+++ b/beacon-chain/operations/slashings/service_proposer_test.go
@@ -35,7 +35,7 @@ func TestPool_InsertProposerSlashing(t *testing.T) {
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
slashings := make([]*ethpb.ProposerSlashing, 20)
- for i := 0; i < len(slashings); i++ {
+ for i := range slashings {
sl, err := util.GenerateProposerSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
slashings[i] = sl
@@ -185,7 +185,7 @@ func TestPool_InsertProposerSlashing_SigFailsVerify_ClearPool(t *testing.T) {
params.OverrideBeaconConfig(conf)
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
slashings := make([]*ethpb.ProposerSlashing, 2)
- for i := 0; i < 2; i++ {
+ for i := range 2 {
sl, err := util.GenerateProposerSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
slashings[i] = sl
@@ -328,7 +328,7 @@ func TestPool_PendingProposerSlashings(t *testing.T) {
}
beaconState, privKeys := util.DeterministicGenesisState(t, 64)
slashings := make([]*ethpb.ProposerSlashing, 20)
- for i := 0; i < len(slashings); i++ {
+ for i := range slashings {
sl, err := util.GenerateProposerSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
slashings[i] = sl
@@ -395,7 +395,7 @@ func TestPool_PendingProposerSlashings_Slashed(t *testing.T) {
slashings := make([]*ethpb.ProposerSlashing, 32)
slashings2 := make([]*ethpb.ProposerSlashing, 32)
result := make([]*ethpb.ProposerSlashing, 32)
- for i := 0; i < len(slashings); i++ {
+ for i := range slashings {
sl, err := util.GenerateProposerSlashingForValidator(beaconState, privKeys[i], primitives.ValidatorIndex(i))
require.NoError(t, err)
slashings[i] = sl
diff --git a/beacon-chain/operations/voluntaryexits/pool_test.go b/beacon-chain/operations/voluntaryexits/pool_test.go
index 29f89fc034..2eb3318d17 100644
--- a/beacon-chain/operations/voluntaryexits/pool_test.go
+++ b/beacon-chain/operations/voluntaryexits/pool_test.go
@@ -124,7 +124,7 @@ func TestExitsForInclusion(t *testing.T) {
})
t.Run("more than MaxVoluntaryExits in pool", func(t *testing.T) {
pool := NewPool()
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
pool.InsertVoluntaryExit(signedExits[i])
}
exits, err := pool.ExitsForInclusion(st, stateSlot)
diff --git a/beacon-chain/p2p/BUILD.bazel b/beacon-chain/p2p/BUILD.bazel
index 8d27988d82..714af413e5 100644
--- a/beacon-chain/p2p/BUILD.bazel
+++ b/beacon-chain/p2p/BUILD.bazel
@@ -142,6 +142,7 @@ go_test(
"topics_test.go",
"utils_test.go",
],
+ data = glob(["testdata/**"]),
embed = [":go_default_library"],
flaky = True,
tags = ["requires-network"],
diff --git a/beacon-chain/p2p/broadcaster_test.go b/beacon-chain/p2p/broadcaster_test.go
index 81041b0b3a..88b26ca032 100644
--- a/beacon-chain/p2p/broadcaster_test.go
+++ b/beacon-chain/p2p/broadcaster_test.go
@@ -60,7 +60,7 @@ func TestService_Broadcast(t *testing.T) {
topic := "/eth2/%x/testing"
// Set a test gossip mapping for testpb.TestSimpleMessage.
- GossipTypeMapping[reflect.TypeOf(msg)] = topic
+ GossipTypeMapping[reflect.TypeFor[*ethpb.Fork]()] = topic
digest, err := p.currentForkDigest()
require.NoError(t, err)
topic = fmt.Sprintf(topic, digest)
@@ -106,7 +106,7 @@ func TestService_Broadcast_ReturnsErr_TopicNotMapped(t *testing.T) {
}
func TestService_Attestation_Subnet(t *testing.T) {
- if gtm := GossipTypeMapping[reflect.TypeOf(ðpb.Attestation{})]; gtm != AttestationSubnetTopicFormat {
+ if gtm := GossipTypeMapping[reflect.TypeFor[*ethpb.Attestation]()]; gtm != AttestationSubnetTopicFormat {
t.Errorf("Constant is out of date. Wanted %s, got %s", AttestationSubnetTopicFormat, gtm)
}
@@ -174,7 +174,7 @@ func TestService_BroadcastAttestation(t *testing.T) {
subnet := uint64(5)
topic := AttestationSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(msg)] = topic
+ GossipTypeMapping[reflect.TypeFor[*ethpb.Attestation]()] = topic
digest, err := p.currentForkDigest()
require.NoError(t, err)
topic = fmt.Sprintf(topic, digest, subnet)
@@ -354,7 +354,7 @@ func TestService_BroadcastAttestationWithDiscoveryAttempts(t *testing.T) {
msg := util.HydrateAttestation(ðpb.Attestation{AggregationBits: bitfield.NewBitlist(7)})
topic := AttestationSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(msg)] = topic
+ GossipTypeMapping[reflect.TypeFor[*ethpb.Attestation]()] = topic
digest, err := p.currentForkDigest()
require.NoError(t, err)
topic = fmt.Sprintf(topic, digest, subnet)
@@ -432,7 +432,7 @@ func TestService_BroadcastSyncCommittee(t *testing.T) {
subnet := uint64(5)
topic := SyncCommitteeSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(msg)] = topic
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SyncCommitteeMessage]()] = topic
digest, err := p.currentForkDigest()
require.NoError(t, err)
topic = fmt.Sprintf(topic, digest, subnet)
@@ -509,7 +509,7 @@ func TestService_BroadcastBlob(t *testing.T) {
subnet := uint64(0)
topic := BlobSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(blobSidecar)] = topic
+ GossipTypeMapping[reflect.TypeFor[*ethpb.BlobSidecar]()] = topic
digest, err := p.currentForkDigest()
require.NoError(t, err)
topic = fmt.Sprintf(topic, digest, subnet)
diff --git a/beacon-chain/p2p/connection_gater_test.go b/beacon-chain/p2p/connection_gater_test.go
index b40a55e7a4..166796b9ef 100644
--- a/beacon-chain/p2p/connection_gater_test.go
+++ b/beacon-chain/p2p/connection_gater_test.go
@@ -48,7 +48,7 @@ func TestPeer_AtMaxLimit(t *testing.T) {
require.NoError(t, err)
}()
- for i := 0; i < highWatermarkBuffer; i++ {
+ for range highWatermarkBuffer {
addPeer(t, s.peers, peers.Connected, false)
}
@@ -85,7 +85,7 @@ func TestService_InterceptBannedIP(t *testing.T) {
require.NoError(t, err)
s.started = true
- for i := 0; i < ipBurst; i++ {
+ for range ipBurst {
valid := s.validateDial(multiAddress)
if !valid {
t.Errorf("Expected multiaddress with ip %s to not be rejected", ip)
diff --git a/beacon-chain/p2p/discovery_test.go b/beacon-chain/p2p/discovery_test.go
index fc73c6d075..a18a649618 100644
--- a/beacon-chain/p2p/discovery_test.go
+++ b/beacon-chain/p2p/discovery_test.go
@@ -536,14 +536,14 @@ func TestInboundPeerLimit(t *testing.T) {
host: fakePeer.BHost,
}
- for i := 0; i < 30; i++ {
+ for range 30 {
_ = addPeer(t, s.peers, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED), false)
}
require.Equal(t, true, s.isPeerAtLimit(all), "not at limit for outbound peers")
require.Equal(t, false, s.isPeerAtLimit(inbound), "at limit for inbound peers")
- for i := 0; i < highWatermarkBuffer; i++ {
+ for range highWatermarkBuffer {
_ = addPeer(t, s.peers, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED), false)
}
@@ -562,13 +562,13 @@ func TestOutboundPeerThreshold(t *testing.T) {
host: fakePeer.BHost,
}
- for i := 0; i < 2; i++ {
+ for range 2 {
_ = addPeer(t, s.peers, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED), true)
}
require.Equal(t, true, s.isBelowOutboundPeerThreshold(), "not at outbound peer threshold")
- for i := 0; i < 3; i++ {
+ for range 3 {
_ = addPeer(t, s.peers, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED), true)
}
@@ -697,7 +697,7 @@ func addPeer(t *testing.T, p *peers.Status, state peerdata.ConnectionState, outb
func createAndConnectPeer(t *testing.T, p2pService *testp2p.TestP2P, offset int) {
// Create the private key.
privateKeyBytes := make([]byte, 32)
- for i := 0; i < 32; i++ {
+ for i := range 32 {
privateKeyBytes[i] = byte(offset + i)
}
@@ -932,7 +932,7 @@ func TestRefreshPersistentSubnets(t *testing.T) {
// Create the private key.
privateKeyBytes := make([]byte, 32)
- for i := 0; i < 32; i++ {
+ for i := range 32 {
privateKeyBytes[i] = byte(i)
}
@@ -1279,7 +1279,7 @@ func TestFindPeers_received_bad_existing_node(t *testing.T) {
if peerData != nil {
service.peers.Add(node1_seq2.Record(), peerData.ID, nil, network.DirUnknown)
// Mark as bad peer - need enough increments to exceed threshold (6)
- for i := 0; i < 10; i++ {
+ for range 10 {
service.peers.Scorers().BadResponsesScorer().Increment(peerData.ID)
}
}
diff --git a/beacon-chain/p2p/encoder/varint.go b/beacon-chain/p2p/encoder/varint.go
index f1554c83b1..98c24089ff 100644
--- a/beacon-chain/p2p/encoder/varint.go
+++ b/beacon-chain/p2p/encoder/varint.go
@@ -15,7 +15,7 @@ var errExcessMaxLength = errors.Errorf("provided header exceeds the max varint l
// the length of the remaining bytes in the reader.
func readVarint(r io.Reader) (uint64, error) {
b := make([]byte, 0, maxVarintLength)
- for i := 0; i < maxVarintLength; i++ {
+ for i := range maxVarintLength {
b1 := make([]byte, 1)
n, err := r.Read(b1)
if err != nil {
diff --git a/beacon-chain/p2p/encoder/varint_test.go b/beacon-chain/p2p/encoder/varint_test.go
index 65fa6bfbba..ed35d73ff2 100644
--- a/beacon-chain/p2p/encoder/varint_test.go
+++ b/beacon-chain/p2p/encoder/varint_test.go
@@ -23,7 +23,7 @@ func TestReadVarint_ExceedsMaxLength(t *testing.T) {
// Terminating byte.
tByte := byte(1 << 6)
var header []byte
- for i := 0; i < 9; i++ {
+ for range 9 {
header = append(header, fByte)
}
header = append(header, tByte)
diff --git a/beacon-chain/p2p/gossip_scoring_params.go b/beacon-chain/p2p/gossip_scoring_params.go
index d48d578eef..374be0cdb1 100644
--- a/beacon-chain/p2p/gossip_scoring_params.go
+++ b/beacon-chain/p2p/gossip_scoring_params.go
@@ -651,8 +651,8 @@ func logGossipParameters(topic string, params *pubsub.TopicScoreParams) {
numOfFields := rawParams.NumField()
fields := make(logrus.Fields, numOfFields)
- for i := 0; i < numOfFields; i++ {
- fields[reflect.TypeOf(params).Elem().Field(i).Name] = rawParams.Field(i).Interface()
+ for i := range numOfFields {
+ fields[reflect.TypeFor[pubsub.TopicScoreParams]().Field(i).Name] = rawParams.Field(i).Interface()
}
log.WithFields(fields).Debugf("Topic Parameters for %s", topic)
}
diff --git a/beacon-chain/p2p/gossip_scoring_params_test.go b/beacon-chain/p2p/gossip_scoring_params_test.go
index 31a9f4f645..af96ae89c6 100644
--- a/beacon-chain/p2p/gossip_scoring_params_test.go
+++ b/beacon-chain/p2p/gossip_scoring_params_test.go
@@ -26,7 +26,7 @@ func TestCorrect_ActiveValidatorsCount(t *testing.T) {
}
bState, err := util.NewBeaconState(func(state *ethpb.BeaconState) error {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 48),
WithdrawalCredentials: make([]byte, 32),
@@ -43,7 +43,7 @@ func TestCorrect_ActiveValidatorsCount(t *testing.T) {
vals, err := s.retrieveActiveValidators()
assert.NoError(t, err, "genesis state not retrieved")
assert.Equal(t, int(params.BeaconConfig().MinGenesisActiveValidatorCount), int(vals), "mainnet genesis active count isn't accurate")
- for i := 0; i < 100; i++ {
+ for range 100 {
require.NoError(t, bState.AppendValidator(ðpb.Validator{
PublicKey: make([]byte, 48),
WithdrawalCredentials: make([]byte, 32),
diff --git a/beacon-chain/p2p/gossip_topic_mappings.go b/beacon-chain/p2p/gossip_topic_mappings.go
index fbb9fcd605..cbfb7a0cf5 100644
--- a/beacon-chain/p2p/gossip_topic_mappings.go
+++ b/beacon-chain/p2p/gossip_topic_mappings.go
@@ -118,30 +118,30 @@ func init() {
}
// Specially handle Altair objects.
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockAltair{})] = BlockSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateAltair{})] = LightClientFinalityUpdateTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateAltair{})] = LightClientOptimisticUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockAltair]()] = BlockSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientFinalityUpdateAltair]()] = LightClientFinalityUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientOptimisticUpdateAltair]()] = LightClientOptimisticUpdateTopicFormat
// Specially handle Bellatrix objects.
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockBellatrix{})] = BlockSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockBellatrix]()] = BlockSubnetTopicFormat
// Specially handle Capella objects.
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockCapella{})] = BlockSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateCapella{})] = LightClientOptimisticUpdateTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateCapella{})] = LightClientFinalityUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockCapella]()] = BlockSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientOptimisticUpdateCapella]()] = LightClientOptimisticUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientFinalityUpdateCapella]()] = LightClientFinalityUpdateTopicFormat
// Specially handle Deneb objects.
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockDeneb{})] = BlockSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientOptimisticUpdateDeneb{})] = LightClientOptimisticUpdateTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateDeneb{})] = LightClientFinalityUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockDeneb]()] = BlockSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientOptimisticUpdateDeneb]()] = LightClientOptimisticUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientFinalityUpdateDeneb]()] = LightClientFinalityUpdateTopicFormat
// Specially handle Electra objects.
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockElectra{})] = BlockSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.SingleAttestation{})] = AttestationSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.AttesterSlashingElectra{})] = AttesterSlashingSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedAggregateAttestationAndProofElectra{})] = AggregateAndProofSubnetTopicFormat
- GossipTypeMapping[reflect.TypeOf(ðpb.LightClientFinalityUpdateElectra{})] = LightClientFinalityUpdateTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockElectra]()] = BlockSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SingleAttestation]()] = AttestationSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashingElectra]()] = AttesterSlashingSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProofElectra]()] = AggregateAndProofSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.LightClientFinalityUpdateElectra]()] = LightClientFinalityUpdateTopicFormat
// Specially handle Fulu objects.
- GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlockFulu{})] = BlockSubnetTopicFormat
+ GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockFulu]()] = BlockSubnetTopicFormat
}
diff --git a/beacon-chain/p2p/gossip_topic_mappings_test.go b/beacon-chain/p2p/gossip_topic_mappings_test.go
index 441a56f811..1fdb4db2a3 100644
--- a/beacon-chain/p2p/gossip_topic_mappings_test.go
+++ b/beacon-chain/p2p/gossip_topic_mappings_test.go
@@ -9,6 +9,7 @@ import (
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/assert"
+ "google.golang.org/protobuf/proto"
)
func TestMappingHasNoDuplicates(t *testing.T) {
@@ -18,7 +19,7 @@ func TestMappingHasNoDuplicates(t *testing.T) {
if _, ok := m[reflect.TypeOf(v())]; ok {
t.Errorf("%T is duplicated in the topic mapping", v)
}
- m[reflect.TypeOf(v)] = true
+ m[reflect.TypeFor[func() proto.Message]()] = true
}
}
diff --git a/beacon-chain/p2p/interfaces.go b/beacon-chain/p2p/interfaces.go
index bb77b5b4f5..ae9dad63d1 100644
--- a/beacon-chain/p2p/interfaces.go
+++ b/beacon-chain/p2p/interfaces.go
@@ -107,7 +107,7 @@ type (
// Sender abstracts the sending functionality from libp2p.
Sender interface {
- Send(context.Context, interface{}, string, peer.ID) (network.Stream, error)
+ Send(context.Context, any, string, peer.ID) (network.Stream, error)
}
// PeersProvider abstracts obtaining our current list of known peers status.
diff --git a/beacon-chain/p2p/peers/assigner.go b/beacon-chain/p2p/peers/assigner.go
index 45a27b4888..6c0f944e2c 100644
--- a/beacon-chain/p2p/peers/assigner.go
+++ b/beacon-chain/p2p/peers/assigner.go
@@ -38,10 +38,7 @@ type Assigner struct {
var ErrInsufficientSuitable = errors.New("no suitable peers")
func (a *Assigner) freshPeers() ([]peer.ID, error) {
- required := params.BeaconConfig().MaxPeersToSync
- if flags.Get().MinimumSyncPeers < required {
- required = flags.Get().MinimumSyncPeers
- }
+ required := min(flags.Get().MinimumSyncPeers, params.BeaconConfig().MaxPeersToSync)
_, peers := a.ps.BestFinalized(params.BeaconConfig().MaxPeersToSync, a.fc.FinalizedCheckpoint().Epoch)
if len(peers) < required {
log.WithFields(logrus.Fields{
diff --git a/beacon-chain/p2p/peers/assigner_test.go b/beacon-chain/p2p/peers/assigner_test.go
index 8fd064dc1e..816b8f3414 100644
--- a/beacon-chain/p2p/peers/assigner_test.go
+++ b/beacon-chain/p2p/peers/assigner_test.go
@@ -2,6 +2,7 @@ package peers
import (
"fmt"
+ "slices"
"testing"
"github.com/OffchainLabs/prysm/v7/testing/require"
@@ -35,7 +36,7 @@ func TestPickBest(t *testing.T) {
{
name: "all busy except i=5",
n: 1,
- busy: testBusyMap(append(append([]peer.ID{}, best[0:5]...), best[6:]...)),
+ busy: testBusyMap(slices.Concat(best[0:5], best[6:])),
expected: []peer.ID{best[5]},
},
{
diff --git a/beacon-chain/p2p/peers/benchmark_test.go b/beacon-chain/p2p/peers/benchmark_test.go
index e756a65baa..42ad7734a9 100644
--- a/beacon-chain/p2p/peers/benchmark_test.go
+++ b/beacon-chain/p2p/peers/benchmark_test.go
@@ -11,8 +11,8 @@ func Benchmark_retrieveIndicesFromBitfield(b *testing.B) {
for i := uint64(0); i < bv.Len(); i++ {
bv.SetBitAt(i, true)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
indicesFromBitfield(bv)
}
}
diff --git a/beacon-chain/p2p/peers/scorers/bad_responses_test.go b/beacon-chain/p2p/peers/scorers/bad_responses_test.go
index 45fb515ec5..89033dcd5a 100644
--- a/beacon-chain/p2p/peers/scorers/bad_responses_test.go
+++ b/beacon-chain/p2p/peers/scorers/bad_responses_test.go
@@ -1,7 +1,7 @@
package scorers_test
import (
- "sort"
+ "slices"
"testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/peers"
@@ -151,7 +151,7 @@ func TestScorers_BadResponses_IsBadPeer(t *testing.T) {
peerStatuses.Add(nil, pid, nil, network.DirUnknown)
assert.NoError(t, scorer.IsBadPeer(pid))
- for i := 0; i < scorers.DefaultBadResponsesThreshold; i++ {
+ for i := range scorers.DefaultBadResponsesThreshold {
scorer.Increment(pid)
if i == scorers.DefaultBadResponsesThreshold-1 {
assert.NotNil(t, scorer.IsBadPeer(pid), "Unexpected peer status")
@@ -170,10 +170,10 @@ func TestScorers_BadResponses_BadPeers(t *testing.T) {
})
scorer := peerStatuses.Scorers().BadResponsesScorer()
pids := []peer.ID{peer.ID("peer1"), peer.ID("peer2"), peer.ID("peer3"), peer.ID("peer4"), peer.ID("peer5")}
- for i := 0; i < len(pids); i++ {
+ for i := range pids {
peerStatuses.Add(nil, pids[i], nil, network.DirUnknown)
}
- for i := 0; i < scorers.DefaultBadResponsesThreshold; i++ {
+ for range scorers.DefaultBadResponsesThreshold {
scorer.Increment(pids[1])
scorer.Increment(pids[2])
scorer.Increment(pids[4])
@@ -185,8 +185,6 @@ func TestScorers_BadResponses_BadPeers(t *testing.T) {
assert.NotNil(t, scorer.IsBadPeer(pids[4]), "Invalid peer status")
want := []peer.ID{pids[1], pids[2], pids[4]}
badPeers := scorer.BadPeers()
- sort.Slice(badPeers, func(i, j int) bool {
- return badPeers[i] < badPeers[j]
- })
+ slices.Sort(badPeers)
assert.DeepEqual(t, want, badPeers, "Unexpected list of bad peers")
}
diff --git a/beacon-chain/p2p/peers/scorers/block_providers.go b/beacon-chain/p2p/peers/scorers/block_providers.go
index 9fea1b12bb..451c83ef4a 100644
--- a/beacon-chain/p2p/peers/scorers/block_providers.go
+++ b/beacon-chain/p2p/peers/scorers/block_providers.go
@@ -239,7 +239,7 @@ func (s *BlockProviderScorer) WeightSorted(
scores, _ := s.mapScoresAndPeers(pids, scoreFn)
peers := make([]peer.ID, 0)
- for i := 0; i < len(pids); i++ {
+ for range pids {
if pid := nextPID(scores); pid != "" {
peers = append(peers, pid)
delete(scores, pid)
diff --git a/beacon-chain/p2p/peers/scorers/block_providers_test.go b/beacon-chain/p2p/peers/scorers/block_providers_test.go
index f4a9b51cd5..50e90d321e 100644
--- a/beacon-chain/p2p/peers/scorers/block_providers_test.go
+++ b/beacon-chain/p2p/peers/scorers/block_providers_test.go
@@ -178,7 +178,7 @@ func TestScorers_BlockProvider_WeightSorted(t *testing.T) {
}
var pids []peer.ID
- for i := uint64(0); i < 10; i++ {
+ for i := range uint64(10) {
pid := peer.ID(strconv.FormatUint(i, 10))
scorer.IncrementProcessedBlocks(pid, i*batchSize)
pids = append(pids, pid)
@@ -190,7 +190,7 @@ func TestScorers_BlockProvider_WeightSorted(t *testing.T) {
// Run weighted sort lots of time, to get accurate statistics of whether more heavy items
// are indeed preferred when sorting.
scores := make(map[peer.ID]int, len(pids))
- for i := 0; i < 1000; i++ {
+ for range 1000 {
score := len(pids) - 1
// The earlier in the list the item is, the more of a score will it get.
for _, pid := range scorer.WeightSorted(r, shuffle(pids), nil) {
diff --git a/beacon-chain/p2p/peers/status.go b/beacon-chain/p2p/peers/status.go
index 7dafeb574c..0d2e959947 100644
--- a/beacon-chain/p2p/peers/status.go
+++ b/beacon-chain/p2p/peers/status.go
@@ -646,10 +646,7 @@ func (p *Status) Prune() {
return peersToPrune[i].score > peersToPrune[j].score
})
- limitDiff := len(p.store.Peers()) - p.store.Config().MaxPeers
- if limitDiff > len(peersToPrune) {
- limitDiff = len(peersToPrune)
- }
+ limitDiff := min(len(p.store.Peers())-p.store.Config().MaxPeers, len(peersToPrune))
peersToPrune = peersToPrune[:limitDiff]
@@ -698,10 +695,7 @@ func (p *Status) deprecatedPrune() {
return peersToPrune[i].badResp < peersToPrune[j].badResp
})
- limitDiff := len(p.store.Peers()) - p.store.Config().MaxPeers
- if limitDiff > len(peersToPrune) {
- limitDiff = len(peersToPrune)
- }
+ limitDiff := min(len(p.store.Peers())-p.store.Config().MaxPeers, len(peersToPrune))
peersToPrune = peersToPrune[:limitDiff]
// Delete peers from map.
for _, peerData := range peersToPrune {
@@ -1129,7 +1123,7 @@ func sameIP(firstAddr, secondAddr ma.Multiaddr) bool {
func indicesFromBitfield(bitV bitfield.Bitvector64) []uint64 {
committeeIdxs := make([]uint64, 0, bitV.Count())
- for i := uint64(0); i < 64; i++ {
+ for i := range uint64(64) {
if bitV.BitAt(i) {
committeeIdxs = append(committeeIdxs, i)
}
diff --git a/beacon-chain/p2p/peers/status_test.go b/beacon-chain/p2p/peers/status_test.go
index 97bd4b7a90..6412bbbff3 100644
--- a/beacon-chain/p2p/peers/status_test.go
+++ b/beacon-chain/p2p/peers/status_test.go
@@ -183,7 +183,7 @@ func TestPeerCommitteeIndices(t *testing.T) {
record.Set(enr.WithEntry("test", []byte{'a'}))
p.Add(record, id, address, direction)
bitV := bitfield.NewBitvector64()
- for i := 0; i < 64; i++ {
+ for i := range 64 {
if i == 2 || i == 8 || i == 9 {
bitV.SetBitAt(uint64(i), true)
}
@@ -218,7 +218,7 @@ func TestPeerSubscribedToSubnet(t *testing.T) {
}
expectedPeer := p.All()[1]
bitV := bitfield.NewBitvector64()
- for i := 0; i < 64; i++ {
+ for i := range 64 {
if i == 2 || i == 8 || i == 9 {
bitV.SetBitAt(uint64(i), true)
}
@@ -391,7 +391,7 @@ func TestAddMetaData(t *testing.T) {
// Add some peers with different states
numPeers := 5
- for i := 0; i < numPeers; i++ {
+ for range numPeers {
addPeer(t, p, peers.Connected)
}
newPeer := p.All()[2]
@@ -420,19 +420,19 @@ func TestPeerConnectionStatuses(t *testing.T) {
// Add some peers with different states
numPeersDisconnected := 11
- for i := 0; i < numPeersDisconnected; i++ {
+ for range numPeersDisconnected {
addPeer(t, p, peers.Disconnected)
}
numPeersConnecting := 7
- for i := 0; i < numPeersConnecting; i++ {
+ for range numPeersConnecting {
addPeer(t, p, peers.Connecting)
}
numPeersConnected := 43
- for i := 0; i < numPeersConnected; i++ {
+ for range numPeersConnected {
addPeer(t, p, peers.Connected)
}
numPeersDisconnecting := 4
- for i := 0; i < numPeersDisconnecting; i++ {
+ for range numPeersDisconnecting {
addPeer(t, p, peers.Disconnecting)
}
@@ -461,7 +461,7 @@ func TestPeerValidTime(t *testing.T) {
})
numPeersConnected := 6
- for i := 0; i < numPeersConnected; i++ {
+ for range numPeersConnected {
addPeer(t, p, peers.Connected)
}
@@ -564,7 +564,7 @@ func TestPeerIPTracker(t *testing.T) {
badIP := "211.227.218.116"
var badPeers []peer.ID
- for i := 0; i < peers.CollocationLimit+10; i++ {
+ for i := range peers.CollocationLimit + 10 {
port := strconv.Itoa(3000 + i)
addr, err := ma.NewMultiaddr("/ip4/" + badIP + "/tcp/" + port)
if err != nil {
@@ -685,12 +685,12 @@ func TestAtInboundPeerLimit(t *testing.T) {
},
},
})
- for i := 0; i < 15; i++ {
+ for range 15 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirOutbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
assert.Equal(t, false, p.IsAboveInboundLimit(), "Inbound limit exceeded")
- for i := 0; i < 31; i++ {
+ for range 31 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirInbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -710,7 +710,7 @@ func TestPrunePeers(t *testing.T) {
},
},
})
- for i := 0; i < 15; i++ {
+ for range 15 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirOutbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -718,7 +718,7 @@ func TestPrunePeers(t *testing.T) {
peersToPrune := p.PeersToPrune()
assert.Equal(t, 0, len(peersToPrune))
- for i := 0; i < 18; i++ {
+ for range 18 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirInbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -728,7 +728,7 @@ func TestPrunePeers(t *testing.T) {
assert.Equal(t, 3, len(peersToPrune))
// Add in more peers.
- for i := 0; i < 13; i++ {
+ for range 13 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirInbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -738,7 +738,7 @@ func TestPrunePeers(t *testing.T) {
for i, pid := range inboundPeers {
modulo := i % 5
// Increment bad scores for peers.
- for j := 0; j < modulo; j++ {
+ for range modulo {
p.Scorers().BadResponsesScorer().Increment(pid)
}
}
@@ -772,7 +772,7 @@ func TestPrunePeers_TrustedPeers(t *testing.T) {
},
})
- for i := 0; i < 15; i++ {
+ for range 15 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirOutbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -780,7 +780,7 @@ func TestPrunePeers_TrustedPeers(t *testing.T) {
peersToPrune := p.PeersToPrune()
assert.Equal(t, 0, len(peersToPrune))
- for i := 0; i < 18; i++ {
+ for range 18 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirInbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -790,7 +790,7 @@ func TestPrunePeers_TrustedPeers(t *testing.T) {
assert.Equal(t, 3, len(peersToPrune))
// Add in more peers.
- for i := 0; i < 13; i++ {
+ for range 13 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirInbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
@@ -801,7 +801,7 @@ func TestPrunePeers_TrustedPeers(t *testing.T) {
for i, pid := range inboundPeers {
modulo := i % 5
// Increment bad scores for peers.
- for j := 0; j < modulo; j++ {
+ for range modulo {
p.Scorers().BadResponsesScorer().Increment(pid)
}
if modulo == 4 {
@@ -826,7 +826,7 @@ func TestPrunePeers_TrustedPeers(t *testing.T) {
}
// Add more peers to check if trusted peers can be pruned after they are deleted from trusted peer set.
- for i := 0; i < 9; i++ {
+ for range 9 {
// Peer added to peer handler.
createPeer(t, p, nil, network.DirInbound, peerdata.ConnectionState(ethpb.ConnectionState_CONNECTED))
}
diff --git a/beacon-chain/p2p/pubsub_filter.go b/beacon-chain/p2p/pubsub_filter.go
index ae0cf816a0..cf7af2a721 100644
--- a/beacon-chain/p2p/pubsub_filter.go
+++ b/beacon-chain/p2p/pubsub_filter.go
@@ -148,8 +148,8 @@ func scanfcheck(input, format string) (int, error) {
// This method only checks that the input conforms to the format, the arguments are not used and
// therefore we can reuse the same integer pointer.
var cnt = strings.Count(format, "%")
- var args []interface{}
- for i := 0; i < cnt; i++ {
+ var args []any
+ for range cnt {
args = append(args, &t)
}
return fmt.Sscanf(input, format, args...)
diff --git a/beacon-chain/p2p/pubsub_filter_test.go b/beacon-chain/p2p/pubsub_filter_test.go
index 20bb5638ad..06fd63fab0 100644
--- a/beacon-chain/p2p/pubsub_filter_test.go
+++ b/beacon-chain/p2p/pubsub_filter_test.go
@@ -85,7 +85,7 @@ func TestService_CanSubscribe(t *testing.T) {
// Ensure all gossip topic mappings pass validation.
for _, topic := range AllTopics() {
- formatting := []interface{}{digest}
+ formatting := []any{digest}
// Special case for attestation subnets which have a second formatting placeholder.
topics := map[string]bool{
diff --git a/beacon-chain/p2p/pubsub_test.go b/beacon-chain/p2p/pubsub_test.go
index 0ddb7f91c4..876d875478 100644
--- a/beacon-chain/p2p/pubsub_test.go
+++ b/beacon-chain/p2p/pubsub_test.go
@@ -50,7 +50,7 @@ func TestService_PublishToTopicConcurrentMapWrite(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(10)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
go func(i int) {
assert.NoError(t, s.PublishToTopic(ctx, topic, []byte{}))
wg.Done()
@@ -131,8 +131,8 @@ func TestExtractGossipDigest(t *testing.T) {
func BenchmarkExtractGossipDigest(b *testing.B) {
topic := fmt.Sprintf(BlockSubnetTopicFormat, []byte{0xb5, 0x30, 0x3f, 0x2a}) + "/" + encoder.ProtocolSuffixSSZSnappy
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := ExtractGossipDigest(topic)
if err != nil {
b.Fatal(err)
diff --git a/beacon-chain/p2p/rpc_topic_mappings.go b/beacon-chain/p2p/rpc_topic_mappings.go
index cb206f3765..313e0a090d 100644
--- a/beacon-chain/p2p/rpc_topic_mappings.go
+++ b/beacon-chain/p2p/rpc_topic_mappings.go
@@ -129,7 +129,7 @@ const (
// RPCTopicMappings map the base message type to the rpc request.
var (
- RPCTopicMappings = map[string]interface{}{
+ RPCTopicMappings = map[string]any{
// RPC Status Message
RPCStatusTopicV1: new(pb.Status),
RPCStatusTopicV2: new(pb.StatusV2),
@@ -149,9 +149,9 @@ var (
RPCPingTopicV1: new(primitives.SSZUint64),
// RPC Metadata Message
- RPCMetaDataTopicV1: new(interface{}),
- RPCMetaDataTopicV2: new(interface{}),
- RPCMetaDataTopicV3: new(interface{}),
+ RPCMetaDataTopicV1: new(any),
+ RPCMetaDataTopicV2: new(any),
+ RPCMetaDataTopicV3: new(any),
// BlobSidecarsByRange v1 Message
RPCBlobSidecarsByRangeTopicV1: new(pb.BlobSidecarsByRangeRequest),
@@ -162,8 +162,8 @@ var (
// Light client
RPCLightClientBootstrapTopicV1: new([fieldparams.RootLength]byte),
RPCLightClientUpdatesByRangeTopicV1: new(pb.LightClientUpdatesByRangeRequest),
- RPCLightClientFinalityUpdateTopicV1: new(interface{}),
- RPCLightClientOptimisticUpdateTopicV1: new(interface{}),
+ RPCLightClientFinalityUpdateTopicV1: new(any),
+ RPCLightClientOptimisticUpdateTopicV1: new(any),
// DataColumnSidecarsByRange v1 Message
RPCDataColumnSidecarsByRangeTopicV1: new(pb.DataColumnSidecarsByRangeRequest),
@@ -230,7 +230,7 @@ var (
// VerifyTopicMapping verifies that the topic and its accompanying
// message type is correct.
-func VerifyTopicMapping(topic string, msg interface{}) error {
+func VerifyTopicMapping(topic string, msg any) error {
msgType, ok := RPCTopicMappings[topic]
if !ok {
return errors.New("rpc topic is not registered currently")
diff --git a/beacon-chain/p2p/rpc_topic_mappings_test.go b/beacon-chain/p2p/rpc_topic_mappings_test.go
index 9b14e93839..435e2fc6e5 100644
--- a/beacon-chain/p2p/rpc_topic_mappings_test.go
+++ b/beacon-chain/p2p/rpc_topic_mappings_test.go
@@ -19,7 +19,7 @@ func TestVerifyRPCMappings(t *testing.T) {
assert.NoError(t, VerifyTopicMapping(RPCStatusTopicV1, &pb.Status{}), "Failed to verify status rpc topic")
assert.NotNil(t, VerifyTopicMapping(RPCStatusTopicV1, new([]byte)), "Incorrect message type verified for status rpc topic")
- assert.NoError(t, VerifyTopicMapping(RPCMetaDataTopicV1, new(interface{})), "Failed to verify metadata rpc topic")
+ assert.NoError(t, VerifyTopicMapping(RPCMetaDataTopicV1, new(any)), "Failed to verify metadata rpc topic")
assert.NotNil(t, VerifyTopicMapping(RPCStatusTopicV1, new([]byte)), "Incorrect message type verified for metadata rpc topic")
assert.NoError(t, VerifyTopicMapping(RPCBlocksByRootTopicV1, new(types.BeaconBlockByRootsReq)), "Failed to verify blocks by root rpc topic")
diff --git a/beacon-chain/p2p/sender.go b/beacon-chain/p2p/sender.go
index abcc5377c0..6a65ca2640 100644
--- a/beacon-chain/p2p/sender.go
+++ b/beacon-chain/p2p/sender.go
@@ -18,7 +18,7 @@ import (
// closed for writing.
//
// When done, the caller must Close or Reset on the stream.
-func (s *Service) Send(ctx context.Context, message interface{}, baseTopic string, pid peer.ID) (network.Stream, error) {
+func (s *Service) Send(ctx context.Context, message any, baseTopic string, pid peer.ID) (network.Stream, error) {
ctx, span := trace.StartSpan(ctx, "p2p.Send")
defer span.End()
if err := VerifyTopicMapping(baseTopic, message); err != nil {
diff --git a/beacon-chain/p2p/service_test.go b/beacon-chain/p2p/service_test.go
index f7b180beec..c6d68dd508 100644
--- a/beacon-chain/p2p/service_test.go
+++ b/beacon-chain/p2p/service_test.go
@@ -369,7 +369,7 @@ func TestService_connectWithPeer(t *testing.T) {
ps := peers.NewStatus(t.Context(), &peers.StatusConfig{
ScorerParams: &scorers.Config{},
})
- for i := 0; i < 10; i++ {
+ for range 10 {
ps.Scorers().BadResponsesScorer().Increment("bad")
}
return ps
diff --git a/beacon-chain/p2p/subnets.go b/beacon-chain/p2p/subnets.go
index 8629da03c1..d2e448cd05 100644
--- a/beacon-chain/p2p/subnets.go
+++ b/beacon-chain/p2p/subnets.go
@@ -3,6 +3,7 @@ package p2p
import (
"context"
"fmt"
+ "maps"
"math"
"strings"
"sync"
@@ -165,9 +166,7 @@ func (s *Service) findPeersWithSubnets(
) ([]*enode.Node, error) {
// Copy the defective subnets map to avoid modifying the original map.
defectiveSubnets := make(map[uint64]int, len(defectiveSubnetsOrigin))
- for k, v := range defectiveSubnetsOrigin {
- defectiveSubnets[k] = v
- }
+ maps.Copy(defectiveSubnets, defectiveSubnetsOrigin)
// Create an discovery iterator to find new peers.
iterator := s.dv5Listener.RandomNodes()
@@ -302,9 +301,7 @@ func (s *Service) dialPeers(ctx context.Context, maxConcurrentDials int, nodes [
continue
}
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
if err := s.connectWithPeer(ctx, *info); err != nil {
log.WithError(err).WithField("info", info.String()).Debug("Could not connect with peer")
return
@@ -313,7 +310,7 @@ func (s *Service) dialPeers(ctx context.Context, maxConcurrentDials int, nodes [
mut.Lock()
defer mut.Unlock()
counter++
- }()
+ })
}
wg.Wait()
diff --git a/beacon-chain/p2p/subnets_test.go b/beacon-chain/p2p/subnets_test.go
index 8a22e608a4..dc9def338c 100644
--- a/beacon-chain/p2p/subnets_test.go
+++ b/beacon-chain/p2p/subnets_test.go
@@ -1094,7 +1094,7 @@ func TestFindPeersWithSubnets_received_bad_existing_node(t *testing.T) {
if peerData != nil {
service.peers.Add(node1_seq2.Record(), peerData.ID, nil, network.DirUnknown)
// Mark as bad peer - need enough increments to exceed threshold (6)
- for i := 0; i < 10; i++ {
+ for range 10 {
service.peers.Scorers().BadResponsesScorer().Increment(peerData.ID)
}
}
diff --git a/beacon-chain/p2p/testing/fuzz_p2p.go b/beacon-chain/p2p/testing/fuzz_p2p.go
index 130fc9f8d7..e0881600b6 100644
--- a/beacon-chain/p2p/testing/fuzz_p2p.go
+++ b/beacon-chain/p2p/testing/fuzz_p2p.go
@@ -99,7 +99,7 @@ func (*FakeP2P) PublishToTopic(_ context.Context, _ string, _ []byte, _ ...pubsu
}
// Send -- fake.
-func (*FakeP2P) Send(_ context.Context, _ interface{}, _ string, _ peer.ID) (network.Stream, error) {
+func (*FakeP2P) Send(_ context.Context, _ any, _ string, _ peer.ID) (network.Stream, error) {
return nil, nil
}
diff --git a/beacon-chain/p2p/testing/p2p.go b/beacon-chain/p2p/testing/p2p.go
index 208eb248ff..f31fb31f30 100644
--- a/beacon-chain/p2p/testing/p2p.go
+++ b/beacon-chain/p2p/testing/p2p.go
@@ -371,7 +371,7 @@ func (p *TestP2P) AddDisconnectionHandler(f func(ctx context.Context, id peer.ID
}
// Send a message to a specific peer.
-func (p *TestP2P) Send(ctx context.Context, msg interface{}, topic string, pid peer.ID) (network.Stream, error) {
+func (p *TestP2P) Send(ctx context.Context, msg any, topic string, pid peer.ID) (network.Stream, error) {
metadataTopics := map[string]bool{metadataV1Topic: true, metadataV2Topic: true, metadataV3Topic: true}
t := topic
diff --git a/beacon-chain/p2p/types/types.go b/beacon-chain/p2p/types/types.go
index c82d6487ee..9a6e46217f 100644
--- a/beacon-chain/p2p/types/types.go
+++ b/beacon-chain/p2p/types/types.go
@@ -78,7 +78,7 @@ func (r *BeaconBlockByRootsReq) UnmarshalSSZ(buf []byte) error {
}
numOfRoots := bufLen / fieldparams.RootLength
roots := make([][fieldparams.RootLength]byte, 0, numOfRoots)
- for i := 0; i < numOfRoots; i++ {
+ for i := range numOfRoots {
var rt [fieldparams.RootLength]byte
copy(rt[:], buf[i*fieldparams.RootLength:(i+1)*fieldparams.RootLength])
roots = append(roots, rt)
@@ -175,7 +175,7 @@ func (b *BlobSidecarsByRootReq) UnmarshalSSZ(buf []byte) error {
}
count := bufLen / blobIdSize
*b = make([]*eth.BlobIdentifier, count)
- for i := 0; i < count; i++ {
+ for i := range count {
id := ð.BlobIdentifier{}
err := id.UnmarshalSSZ(buf[i*blobIdSize : (i+1)*blobIdSize])
if err != nil {
@@ -323,7 +323,7 @@ func (d DataColumnsByRootIdentifiers) MarshalSSZTo(dst []byte) ([]byte, error) {
// SizeSSZ implements ssz.Marshaler. It returns the size of the serialized representation.
func (d DataColumnsByRootIdentifiers) SizeSSZ() int {
size := 0
- for i := 0; i < len(d); i++ {
+ for i := range d {
size += 4
size += (d)[i].SizeSSZ()
}
diff --git a/beacon-chain/p2p/types/types_test.go b/beacon-chain/p2p/types/types_test.go
index 945feb1185..681b2de7b1 100644
--- a/beacon-chain/p2p/types/types_test.go
+++ b/beacon-chain/p2p/types/types_test.go
@@ -16,7 +16,7 @@ import (
func generateBlobIdentifiers(n int) []*eth.BlobIdentifier {
r := make([]*eth.BlobIdentifier, n)
- for i := 0; i < n; i++ {
+ for i := range n {
r[i] = ð.BlobIdentifier{
BlockRoot: bytesutil.PadTo([]byte{byte(i)}, 32),
Index: 0,
@@ -111,7 +111,7 @@ func TestBeaconBlockByRootsReq_Limit(t *testing.T) {
func TestErrorResponse_Limit(t *testing.T) {
errorMessage := make([]byte, 0)
// Provide a message of size 6400 bytes.
- for i := uint64(0); i < 200; i++ {
+ for i := range uint64(200) {
byteArr := [32]byte{byte(i)}
errorMessage = append(errorMessage, byteArr[:]...)
}
@@ -126,7 +126,7 @@ func TestRoundTripSerialization(t *testing.T) {
func roundTripTestBlocksByRootReq(t *testing.T) {
fixedRoots := make([][32]byte, 0)
- for i := 0; i < 200; i++ {
+ for i := range 200 {
fixedRoots = append(fixedRoots, [32]byte{byte(i)})
}
req := BeaconBlockByRootsReq(fixedRoots)
@@ -210,7 +210,7 @@ func hexDecodeOrDie(t *testing.T, str string) []byte {
// ====================================
func generateDataColumnIdentifiers(n int) []*eth.DataColumnsByRootIdentifier {
r := make([]*eth.DataColumnsByRootIdentifier, n)
- for i := 0; i < n; i++ {
+ for i := range n {
r[i] = ð.DataColumnsByRootIdentifier{
BlockRoot: bytesutil.PadTo([]byte{byte(i)}, 32),
Columns: []uint64{uint64(i)},
diff --git a/beacon-chain/rpc/core/validator.go b/beacon-chain/rpc/core/validator.go
index 18e9f29b7d..ef24f61415 100644
--- a/beacon-chain/rpc/core/validator.go
+++ b/beacon-chain/rpc/core/validator.go
@@ -4,7 +4,7 @@ import (
"bytes"
"context"
"fmt"
- "sort"
+ "slices"
"time"
"github.com/OffchainLabs/prysm/v7/api/server"
@@ -133,9 +133,7 @@ func (s *Service) ComputeValidatorPerformance(
}
}
// Depending on the indices and public keys given, results might not be sorted.
- sort.Slice(validatorIndices, func(i, j int) bool {
- return validatorIndices[i] < validatorIndices[j]
- })
+ slices.Sort(validatorIndices)
currentEpoch := coreTime.CurrentEpoch(headState)
responseCap = len(validatorIndices)
@@ -239,9 +237,7 @@ func (s *Service) IndividualVotes(
filteredIndices = append(filteredIndices, index)
}
}
- sort.Slice(filteredIndices, func(i, j int) bool {
- return filteredIndices[i] < filteredIndices[j]
- })
+ slices.Sort(filteredIndices)
var v []*precompute.Validator
var bal *precompute.Balance
diff --git a/beacon-chain/rpc/core/validator_test.go b/beacon-chain/rpc/core/validator_test.go
index 849beef301..f017e1bdb0 100644
--- a/beacon-chain/rpc/core/validator_test.go
+++ b/beacon-chain/rpc/core/validator_test.go
@@ -22,7 +22,7 @@ func TestRegisterSyncSubnetProto(t *testing.T) {
k := pubKey(3)
committee := make([][]byte, 0)
- for i := 0; i < 100; i++ {
+ for i := range 100 {
committee = append(committee, pubKey(uint64(i)))
}
sCommittee := ðpb.SyncCommittee{
@@ -44,7 +44,7 @@ func TestRegisterSyncSubnet(t *testing.T) {
k := pubKey(3)
committee := make([][]byte, 0)
- for i := 0; i < 100; i++ {
+ for i := range 100 {
committee = append(committee, pubKey(uint64(i)))
}
sCommittee := ðpb.SyncCommittee{
diff --git a/beacon-chain/rpc/eth/beacon/handlers.go b/beacon-chain/rpc/eth/beacon/handlers.go
index 58eff169c2..bfd9352e59 100644
--- a/beacon-chain/rpc/eth/beacon/handlers.go
+++ b/beacon-chain/rpc/eth/beacon/handlers.go
@@ -304,7 +304,7 @@ func (s *Server) GetBlockAttestationsV2(w http.ResponseWriter, r *http.Request)
consensusAtts := blk.Block().Body().Attestations()
v := blk.Block().Version()
- attStructs := make([]interface{}, len(consensusAtts))
+ attStructs := make([]any, len(consensusAtts))
if v >= version.Electra {
for index, att := range consensusAtts {
a, ok := att.(*eth.AttestationElectra)
@@ -887,7 +887,7 @@ func (s *Server) proposeBlock(ctx context.Context, w http.ResponseWriter, blk *e
}
}
-func unmarshalStrict(data []byte, v interface{}) error {
+func unmarshalStrict(data []byte, v any) error {
dec := json.NewDecoder(bytes.NewReader(data))
dec.DisallowUnknownFields()
return dec.Decode(v)
diff --git a/beacon-chain/rpc/eth/beacon/handlers_pool.go b/beacon-chain/rpc/eth/beacon/handlers_pool.go
index e6adb7b1e1..09d7eeaa23 100644
--- a/beacon-chain/rpc/eth/beacon/handlers_pool.go
+++ b/beacon-chain/rpc/eth/beacon/handlers_pool.go
@@ -62,7 +62,7 @@ func (s *Server) ListAttestationsV2(w http.ResponseWriter, r *http.Request) {
attestations = append(attestations, unaggAtts...)
}
- filteredAtts := make([]interface{}, 0, len(attestations))
+ filteredAtts := make([]any, 0, len(attestations))
for _, att := range attestations {
var includeAttestation bool
if v >= version.Electra && att.Version() >= version.Electra {
@@ -594,10 +594,7 @@ func (s *Server) SubmitBLSToExecutionChanges(w http.ResponseWriter, r *http.Requ
// It validates the messages again because they could have been invalidated by being included in blocks since the last validation.
// It removes the messages from the slice and modifies it in place.
func (s *Server) broadcastBLSBatch(ctx context.Context, ptr *[]*eth.SignedBLSToExecutionChange) {
- limit := broadcastBLSChangesRateLimit
- if len(*ptr) < broadcastBLSChangesRateLimit {
- limit = len(*ptr)
- }
+ limit := min(len(*ptr), broadcastBLSChangesRateLimit)
st, err := s.ChainInfoFetcher.HeadStateReadOnly(ctx)
if err != nil {
log.WithError(err).Error("Could not get head state")
@@ -668,9 +665,9 @@ func (s *Server) GetAttesterSlashingsV2(w http.ResponseWriter, r *http.Request)
}
sourceSlashings := s.SlashingsPool.PendingAttesterSlashings(ctx, headState, true /* return unlimited slashings */)
- attStructs := make([]interface{}, 0, len(sourceSlashings))
+ attStructs := make([]any, 0, len(sourceSlashings))
for _, slashing := range sourceSlashings {
- var attStruct interface{}
+ var attStruct any
if v >= version.Electra && slashing.Version() >= version.Electra {
a, ok := slashing.(*eth.AttesterSlashingElectra)
if !ok {
diff --git a/beacon-chain/rpc/eth/beacon/handlers_state.go b/beacon-chain/rpc/eth/beacon/handlers_state.go
index 579cd93dfa..310a513e24 100644
--- a/beacon-chain/rpc/eth/beacon/handlers_state.go
+++ b/beacon-chain/rpc/eth/beacon/handlers_state.go
@@ -289,7 +289,7 @@ func nextCommitteeIndicesFromState(st state.BeaconState) ([]string, *ethpbalpha.
func extractSyncSubcommittees(st state.BeaconState, committee *ethpbalpha.SyncCommittee) ([][]string, error) {
subcommitteeCount := params.BeaconConfig().SyncCommitteeSubnetCount
subcommittees := make([][]string, subcommitteeCount)
- for i := uint64(0); i < subcommitteeCount; i++ {
+ for i := range subcommitteeCount {
pubkeys, err := altair.SyncSubCommitteePubkeys(committee, primitives.CommitteeIndex(i))
if err != nil {
return nil, fmt.Errorf(
diff --git a/beacon-chain/rpc/eth/beacon/handlers_state_test.go b/beacon-chain/rpc/eth/beacon/handlers_state_test.go
index f73d3f9f75..190a4d6c11 100644
--- a/beacon-chain/rpc/eth/beacon/handlers_state_test.go
+++ b/beacon-chain/rpc/eth/beacon/handlers_state_test.go
@@ -384,7 +384,7 @@ func Test_currentCommitteeIndicesFromState(t *testing.T) {
vals := st.Validators()
wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
wantedIndices := make([]string, len(wantedCommittee))
- for i := 0; i < len(wantedCommittee); i++ {
+ for i := range wantedCommittee {
wantedIndices[i] = strconv.FormatUint(uint64(i), 10)
wantedCommittee[i] = vals[i].PublicKey
}
@@ -415,7 +415,7 @@ func Test_nextCommitteeIndicesFromState(t *testing.T) {
vals := st.Validators()
wantedCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
wantedIndices := make([]string, len(wantedCommittee))
- for i := 0; i < len(wantedCommittee); i++ {
+ for i := range wantedCommittee {
wantedIndices[i] = strconv.FormatUint(uint64(i), 10)
wantedCommittee[i] = vals[i].PublicKey
}
@@ -445,7 +445,7 @@ func Test_extractSyncSubcommittees(t *testing.T) {
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
vals := st.Validators()
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
- for i := 0; i < len(syncCommittee); i++ {
+ for i := range syncCommittee {
syncCommittee[i] = vals[i].PublicKey
}
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
@@ -460,10 +460,7 @@ func Test_extractSyncSubcommittees(t *testing.T) {
for i := uint64(0); i < commSize; i += subCommSize {
sub := make([]string, 0)
start := i
- end := i + subCommSize
- if end > commSize {
- end = commSize
- }
+ end := min(i+subCommSize, commSize)
for j := start; j < end; j++ {
sub = append(sub, strconv.FormatUint(j, 10))
}
@@ -498,7 +495,7 @@ func TestGetSyncCommittees(t *testing.T) {
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
vals := st.Validators()
- for i := 0; i < len(syncCommittee); i++ {
+ for i := range syncCommittee {
syncCommittee[i] = vals[i].PublicKey
}
require.NoError(t, st.SetCurrentSyncCommittee(ðpbalpha.SyncCommittee{
@@ -661,7 +658,7 @@ func TestGetSyncCommittees_Future(t *testing.T) {
st, _ := util.DeterministicGenesisStateAltair(t, params.BeaconConfig().SyncCommitteeSize)
syncCommittee := make([][]byte, params.BeaconConfig().SyncCommitteeSize)
vals := st.Validators()
- for i := 0; i < len(syncCommittee); i++ {
+ for i := range syncCommittee {
syncCommittee[i] = vals[i].PublicKey
}
require.NoError(t, st.SetNextSyncCommittee(ðpbalpha.SyncCommittee{
diff --git a/beacon-chain/rpc/eth/beacon/handlers_test.go b/beacon-chain/rpc/eth/beacon/handlers_test.go
index c11ec05807..3711deffb9 100644
--- a/beacon-chain/rpc/eth/beacon/handlers_test.go
+++ b/beacon-chain/rpc/eth/beacon/handlers_test.go
@@ -61,7 +61,7 @@ func fillDBTestBlocks(ctx context.Context, t *testing.T, beaconDB db.Database) (
count := primitives.Slot(100)
blks := make([]interfaces.ReadOnlySignedBeaconBlock, count)
blkContainers := make([]*eth.BeaconBlockContainer, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
b := util.NewBeaconBlock()
b.Block.Slot = i
b.Block.ParentRoot = bytesutil.PadTo([]byte{uint8(i)}, 32)
@@ -3765,7 +3765,7 @@ func Test_validateBlobs(t *testing.T) {
fuluBlobs := make([][]byte, blobCount)
var kzgBlobs []kzg.Blob
- for i := 0; i < blobCount; i++ {
+ for i := range blobCount {
blob := util.GetRandBlob(int64(i))
fuluBlobs[i] = blob[:]
var kzgBlob kzg.Blob
@@ -3785,11 +3785,11 @@ func Test_validateBlobs(t *testing.T) {
// Generate cell proofs for the blobs (flattened format like execution client)
numberOfColumns := params.BeaconConfig().NumberOfColumns
cellProofs := make([][]byte, uint64(blobCount)*numberOfColumns)
- for blobIdx := 0; blobIdx < blobCount; blobIdx++ {
+ for blobIdx := range blobCount {
_, proofs, err := kzg.ComputeCellsAndKZGProofs(&kzgBlobs[blobIdx])
require.NoError(t, err)
- for colIdx := uint64(0); colIdx < numberOfColumns; colIdx++ {
+ for colIdx := range numberOfColumns {
cellProofIdx := uint64(blobIdx)*numberOfColumns + colIdx
cellProofs[cellProofIdx] = proofs[colIdx][:]
}
@@ -3808,7 +3808,7 @@ func Test_validateBlobs(t *testing.T) {
blobCount := 2
commitments := make([][]byte, blobCount)
fuluBlobs := make([][]byte, blobCount)
- for i := 0; i < blobCount; i++ {
+ for i := range blobCount {
blob := util.GetRandBlob(int64(i))
fuluBlobs[i] = blob[:]
@@ -3977,7 +3977,7 @@ func TestGetPendingConsolidations(t *testing.T) {
consolidationSize := (ð.PendingConsolidation{}).SizeSSZ()
require.Equal(t, len(responseBytes), consolidationSize*len(cs))
- for i := 0; i < len(cs); i++ {
+ for i := range cs {
start := i * consolidationSize
end := start + consolidationSize
@@ -4103,7 +4103,7 @@ func TestGetPendingDeposits(t *testing.T) {
validators := st.Validators()
dummySig := make([]byte, 96)
- for j := 0; j < 96; j++ {
+ for j := range 96 {
dummySig[j] = byte(j)
}
deps := make([]*eth.PendingDeposit, 10)
@@ -4170,7 +4170,7 @@ func TestGetPendingDeposits(t *testing.T) {
depositSize := (ð.PendingDeposit{}).SizeSSZ()
require.Equal(t, len(responseBytes), depositSize*len(deps))
- for i := 0; i < len(deps); i++ {
+ for i := range deps {
start := i * depositSize
end := start + depositSize
@@ -4357,7 +4357,7 @@ func TestGetPendingPartialWithdrawals(t *testing.T) {
withdrawalSize := (ð.PendingPartialWithdrawal{}).SizeSSZ()
require.Equal(t, len(responseBytes), withdrawalSize*len(withdrawals))
- for i := 0; i < len(withdrawals); i++ {
+ for i := range withdrawals {
start := i * withdrawalSize
end := start + withdrawalSize
@@ -4487,7 +4487,7 @@ func TestGetProposerLookahead(t *testing.T) {
st, _ := util.DeterministicGenesisStateFulu(t, uint64(numValidators))
lookaheadSize := int(params.BeaconConfig().MinSeedLookahead+1) * int(params.BeaconConfig().SlotsPerEpoch)
lookahead := make([]primitives.ValidatorIndex, lookaheadSize)
- for i := 0; i < lookaheadSize; i++ {
+ for i := range lookaheadSize {
lookahead[i] = primitives.ValidatorIndex(i % numValidators) // Cycle through validators
}
@@ -4525,7 +4525,7 @@ func TestGetProposerLookahead(t *testing.T) {
// Verify the data
require.Equal(t, lookaheadSize, len(resp.Data))
- for i := 0; i < lookaheadSize; i++ {
+ for i := range lookaheadSize {
expectedIdx := strconv.FormatUint(uint64(i%numValidators), 10)
require.Equal(t, expectedIdx, resp.Data[i])
}
@@ -4546,7 +4546,7 @@ func TestGetProposerLookahead(t *testing.T) {
require.Equal(t, len(responseBytes), validatorIndexSize*lookaheadSize)
recoveredIndices := make([]primitives.ValidatorIndex, lookaheadSize)
- for i := 0; i < lookaheadSize; i++ {
+ for i := range lookaheadSize {
start := i * validatorIndexSize
end := start + validatorIndexSize
diff --git a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go
index 8ff8389b49..a0263afe7c 100644
--- a/beacon-chain/rpc/eth/beacon/handlers_validators_test.go
+++ b/beacon-chain/rpc/eth/beacon/handlers_validators_test.go
@@ -842,7 +842,7 @@ func TestGetValidatorBalances(t *testing.T) {
count := uint64(4)
st, _ = util.DeterministicGenesisState(t, count)
balances := make([]uint64, count)
- for i := uint64(0); i < count; i++ {
+ for i := range count {
balances[i] = i
}
require.NoError(t, st.SetBalances(balances))
@@ -1189,7 +1189,7 @@ func TestGetValidatorIdentities(t *testing.T) {
count := uint64(4)
genesisState, _ := util.DeterministicGenesisState(t, count)
st := genesisState.ToProtoUnsafe().(*eth.BeaconState)
- for i := uint64(0); i < count; i++ {
+ for i := range count {
st.Validators[i].ActivationEpoch = primitives.Epoch(i)
}
@@ -1218,7 +1218,7 @@ func TestGetValidatorIdentities(t *testing.T) {
resp := &structs.GetValidatorIdentitiesResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), resp))
require.Equal(t, 4, len(resp.Data))
- for i := uint64(0); i < count; i++ {
+ for i := range count {
assert.Equal(t, fmt.Sprintf("%d", i), resp.Data[i].Index)
assert.DeepEqual(t, hexutil.Encode(st.Validators[i].PublicKey), resp.Data[i].Pubkey)
assert.Equal(t, fmt.Sprintf("%d", st.Validators[i].ActivationEpoch), resp.Data[i].ActivationEpoch)
diff --git a/beacon-chain/rpc/eth/config/handlers.go b/beacon-chain/rpc/eth/config/handlers.go
index 44185dd94c..6ef4da6e17 100644
--- a/beacon-chain/rpc/eth/config/handlers.go
+++ b/beacon-chain/rpc/eth/config/handlers.go
@@ -72,7 +72,7 @@ func GetSpec(w http.ResponseWriter, r *http.Request) {
httputil.WriteJson(w, &structs.GetSpecResponse{Data: data})
}
-func convertValueForJSON(v reflect.Value, tag string) interface{} {
+func convertValueForJSON(v reflect.Value, tag string) any {
// Unwrap pointers / interfaces
for v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr {
if v.IsNil() {
@@ -109,8 +109,8 @@ func convertValueForJSON(v reflect.Value, tag string) interface{} {
}
// Generic slice/array handling
n := v.Len()
- out := make([]interface{}, n)
- for i := 0; i < n; i++ {
+ out := make([]any, n)
+ for i := range n {
out[i] = convertValueForJSON(v.Index(i), tag)
}
return out
@@ -118,7 +118,7 @@ func convertValueForJSON(v reflect.Value, tag string) interface{} {
// ===== Struct =====
case reflect.Struct:
t := v.Type()
- m := make(map[string]interface{}, v.NumField())
+ m := make(map[string]any, v.NumField())
for i := 0; i < v.NumField(); i++ {
f := t.Field(i)
if !v.Field(i).CanInterface() {
@@ -158,11 +158,11 @@ func convertValueForJSON(v reflect.Value, tag string) interface{} {
}
}
-func prepareConfigSpec() (map[string]interface{}, error) {
- data := make(map[string]interface{})
+func prepareConfigSpec() (map[string]any, error) {
+ data := make(map[string]any)
config := *params.BeaconConfig()
- t := reflect.TypeOf(config)
+ t := reflect.TypeFor[params.BeaconChainConfig]()
v := reflect.ValueOf(config)
for i := 0; i < t.NumField(); i++ {
diff --git a/beacon-chain/rpc/eth/config/handlers_test.go b/beacon-chain/rpc/eth/config/handlers_test.go
index 04d11cf9dd..1c7386f7b3 100644
--- a/beacon-chain/rpc/eth/config/handlers_test.go
+++ b/beacon-chain/rpc/eth/config/handlers_test.go
@@ -204,7 +204,7 @@ func TestGetSpec(t *testing.T) {
require.Equal(t, http.StatusOK, writer.Code)
resp := structs.GetSpecResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
- data, ok := resp.Data.(map[string]interface{})
+ data, ok := resp.Data.(map[string]any)
require.Equal(t, true, ok)
assert.Equal(t, 176, len(data))
for k, v := range data {
@@ -583,7 +583,7 @@ func TestGetSpec(t *testing.T) {
case "SYNC_MESSAGE_DUE_BPS":
assert.Equal(t, "104", v)
case "BLOB_SCHEDULE":
- blobSchedule, ok := v.([]interface{})
+ blobSchedule, ok := v.([]any)
assert.Equal(t, true, ok)
assert.Equal(t, 2, len(blobSchedule))
default:
@@ -664,7 +664,7 @@ func TestGetSpec_BlobSchedule(t *testing.T) {
require.Equal(t, http.StatusOK, writer.Code)
resp := structs.GetSpecResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
- data, ok := resp.Data.(map[string]interface{})
+ data, ok := resp.Data.(map[string]any)
require.Equal(t, true, ok)
// Verify BLOB_SCHEDULE is present and properly formatted
@@ -673,13 +673,13 @@ func TestGetSpec_BlobSchedule(t *testing.T) {
// Verify it's a slice of maps (actual JSON object, not string)
// The JSON unmarshaling converts it to []interface{} with map[string]interface{} entries
- blobScheduleSlice, ok := blobScheduleValue.([]interface{})
+ blobScheduleSlice, ok := blobScheduleValue.([]any)
require.Equal(t, true, ok)
// Convert to generic interface for easier testing
- var blobSchedule []map[string]interface{}
+ var blobSchedule []map[string]any
for _, entry := range blobScheduleSlice {
- entryMap, ok := entry.(map[string]interface{})
+ entryMap, ok := entry.(map[string]any)
require.Equal(t, true, ok)
blobSchedule = append(blobSchedule, entryMap)
}
@@ -735,7 +735,7 @@ func TestGetSpec_BlobSchedule_NotFulu(t *testing.T) {
require.Equal(t, http.StatusOK, writer.Code)
resp := structs.GetSpecResponse{}
require.NoError(t, json.Unmarshal(writer.Body.Bytes(), &resp))
- data, ok := resp.Data.(map[string]interface{})
+ data, ok := resp.Data.(map[string]any)
require.Equal(t, true, ok)
_, exists := data["BLOB_SCHEDULE"]
diff --git a/beacon-chain/rpc/eth/debug/handlers.go b/beacon-chain/rpc/eth/debug/handlers.go
index 7e279bdfaf..a0c20c4a83 100644
--- a/beacon-chain/rpc/eth/debug/handlers.go
+++ b/beacon-chain/rpc/eth/debug/handlers.go
@@ -67,7 +67,7 @@ func (s *Server) getBeaconStateV2(ctx context.Context, w http.ResponseWriter, id
return
}
isFinalized := s.FinalizationFetcher.IsFinalized(ctx, blockRoot)
- var respSt interface{}
+ var respSt any
switch st.Version() {
case version.Phase0:
diff --git a/beacon-chain/rpc/eth/events/events_test.go b/beacon-chain/rpc/eth/events/events_test.go
index 329140a4e3..f34181f003 100644
--- a/beacon-chain/rpc/eth/events/events_test.go
+++ b/beacon-chain/rpc/eth/events/events_test.go
@@ -677,7 +677,7 @@ func TestFillEventData(t *testing.T) {
func setActiveValidators(t *testing.T, st state.BeaconState, count int) {
balances := make([]uint64, count)
validators := make([]*eth.Validator, 0, count)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
binary.LittleEndian.PutUint64(pubKey, uint64(i))
balances[i] = uint64(i)
diff --git a/beacon-chain/rpc/eth/light-client/handlers_test.go b/beacon-chain/rpc/eth/light-client/handlers_test.go
index 6e87cfb00f..bc36785542 100644
--- a/beacon-chain/rpc/eth/light-client/handlers_test.go
+++ b/beacon-chain/rpc/eth/light-client/handlers_test.go
@@ -493,7 +493,7 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
- for i := 0; i < 3; i++ {
+ for i := range 3 {
updates[i], err = createUpdate(t, version.Altair)
require.NoError(t, err)
@@ -551,7 +551,7 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
- for i := 0; i < 3; i++ {
+ for i := range 3 {
updates[i], err = createUpdate(t, version.Altair)
require.NoError(t, err)
@@ -633,7 +633,7 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
- for i := 0; i < 3; i++ {
+ for i := range 3 {
if i == 1 { // skip this update
updatePeriod++
continue
@@ -687,7 +687,7 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
updatePeriod := slot.Div(uint64(config.EpochsPerSyncCommitteePeriod)).Div(uint64(config.SlotsPerEpoch))
- for i := 0; i < 3; i++ {
+ for i := range 3 {
if i == 0 { // skip this update
updatePeriod++
continue
@@ -910,14 +910,14 @@ func createUpdate(t *testing.T, v int) (interfaces.LightClientUpdate, error) {
var err error
sampleRoot := make([]byte, 32)
- for i := 0; i < 32; i++ {
+ for i := range 32 {
sampleRoot[i] = byte(i)
}
sampleExecutionBranch := make([][]byte, fieldparams.ExecutionBranchDepth)
- for i := 0; i < 4; i++ {
+ for i := range 4 {
sampleExecutionBranch[i] = make([]byte, 32)
- for j := 0; j < 32; j++ {
+ for j := range 32 {
sampleExecutionBranch[i][j] = byte(i + j)
}
}
diff --git a/beacon-chain/rpc/eth/node/handlers_peers_test.go b/beacon-chain/rpc/eth/node/handlers_peers_test.go
index a9b89c0ad1..036ef20ea3 100644
--- a/beacon-chain/rpc/eth/node/handlers_peers_test.go
+++ b/beacon-chain/rpc/eth/node/handlers_peers_test.go
@@ -341,8 +341,7 @@ func BenchmarkGetPeers(b *testing.B) {
writer.Body = &bytes.Buffer{}
s.GetPeers(writer, request)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.GetPeers(writer, request)
}
}
diff --git a/beacon-chain/rpc/eth/rewards/handlers.go b/beacon-chain/rpc/eth/rewards/handlers.go
index a30e843553..87be7a5a60 100644
--- a/beacon-chain/rpc/eth/rewards/handlers.go
+++ b/beacon-chain/rpc/eth/rewards/handlers.go
@@ -436,7 +436,7 @@ func requestedValIndices(w http.ResponseWriter, r *http.Request, st state.Beacon
}
if len(valIndices) == 0 {
valIndices = make([]primitives.ValidatorIndex, len(allVals))
- for i := 0; i < len(allVals); i++ {
+ for i := range allVals {
valIndices[i] = primitives.ValidatorIndex(i)
}
}
diff --git a/beacon-chain/rpc/eth/rewards/handlers_test.go b/beacon-chain/rpc/eth/rewards/handlers_test.go
index 55d76f8c1a..e2d05e939b 100644
--- a/beacon-chain/rpc/eth/rewards/handlers_test.go
+++ b/beacon-chain/rpc/eth/rewards/handlers_test.go
@@ -85,7 +85,7 @@ func BlockRewardTestSetup(t *testing.T, ver int) (state.BeaconState, interfaces.
validators := make([]*eth.Validator, 0, valCount)
balances := make([]uint64, 0, valCount)
secretKeys := make([]bls.SecretKey, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
blsKey, err := bls.RandKey()
require.NoError(t, err)
secretKeys = append(secretKeys, blsKey)
@@ -475,7 +475,7 @@ func TestAttestationRewards(t *testing.T) {
validators := make([]*eth.Validator, 0, valCount)
balances := make([]uint64, 0, valCount)
secretKeys := make([]bls.SecretKey, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for i := range valCount {
blsKey, err := bls.RandKey()
require.NoError(t, err)
secretKeys = append(secretKeys, blsKey)
@@ -778,7 +778,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
require.NoError(t, st.SetSlot(params.BeaconConfig().SlotsPerEpoch-1))
validators := make([]*eth.Validator, 0, valCount)
secretKeys := make([]bls.SecretKey, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
blsKey, err := bls.RandKey()
require.NoError(t, err)
secretKeys = append(secretKeys, blsKey)
@@ -792,7 +792,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
require.NoError(t, st.SetValidators(validators))
require.NoError(t, st.SetInactivityScores(make([]uint64, len(validators))))
syncCommitteePubkeys := make([][]byte, fieldparams.SyncCommitteeLength)
- for i := 0; i < fieldparams.SyncCommitteeLength; i++ {
+ for i := range fieldparams.SyncCommitteeLength {
syncCommitteePubkeys[i] = secretKeys[i].PublicKey().Marshal()
}
aggPubkey, err := bls.AggregatePublicKeys(syncCommitteePubkeys)
@@ -807,7 +807,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
b.Block.ProposerIndex = proposerIndex
scBits := bitfield.NewBitvector512()
// last 10 sync committee members didn't perform their duty
- for i := uint64(0); i < fieldparams.SyncCommitteeLength-10; i++ {
+ for i := range uint64(fieldparams.SyncCommitteeLength - 10) {
scBits.SetBitAt(i, true)
}
domain, err := signing.Domain(st.Fork(), 0, params.BeaconConfig().DomainSyncCommittee, st.GenesisValidatorsRoot())
@@ -845,7 +845,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
t.Run("ok - filtered vals", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -878,7 +878,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("ok - all vals", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -903,7 +903,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("ok - validator outside sync committee is ignored", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -934,7 +934,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("ok - proposer reward is deducted", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -965,7 +965,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("invalid validator index/pubkey", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -989,7 +989,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("unknown validator pubkey", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -1016,7 +1016,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("validator index too large", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
@@ -1040,7 +1040,7 @@ func TestSyncCommiteeRewards(t *testing.T) {
})
t.Run("phase 0", func(t *testing.T) {
balances := make([]uint64, 0, valCount)
- for i := 0; i < valCount; i++ {
+ for range valCount {
balances = append(balances, params.BeaconConfig().MaxEffectiveBalance)
}
require.NoError(t, st.SetBalances(balances))
diff --git a/beacon-chain/rpc/eth/validator/handlers.go b/beacon-chain/rpc/eth/validator/handlers.go
index 24b1924891..2377a5fe9e 100644
--- a/beacon-chain/rpc/eth/validator/handlers.go
+++ b/beacon-chain/rpc/eth/validator/handlers.go
@@ -1168,10 +1168,7 @@ func (s *Server) GetSyncCommitteeDuties(w http.ResponseWriter, r *http.Request)
return
}
- startingEpoch := requestedEpoch
- if startingEpoch > currentEpoch {
- startingEpoch = currentEpoch
- }
+ startingEpoch := min(requestedEpoch, currentEpoch)
slot, err := slots.EpochStart(startingEpoch)
if err != nil {
httputil.HandleError(w, "Could not get sync committee slot: "+err.Error(), http.StatusInternalServerError)
diff --git a/beacon-chain/rpc/eth/validator/handlers_test.go b/beacon-chain/rpc/eth/validator/handlers_test.go
index 56ec6b0b04..51498b0c62 100644
--- a/beacon-chain/rpc/eth/validator/handlers_test.go
+++ b/beacon-chain/rpc/eth/validator/handlers_test.go
@@ -786,7 +786,7 @@ func TestSubmitSyncCommitteeSubscription(t *testing.T) {
require.NoError(t, bs.SetBlockRoots(roots))
pubkeys := make([][]byte, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubkeys[i] = deposits[i].Data.PublicKey
}
@@ -958,7 +958,7 @@ func TestSubmitBeaconCommitteeSubscription(t *testing.T) {
require.NoError(t, bs.SetBlockRoots(roots))
pubkeys := make([][]byte, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubkeys[i] = deposits[i].Data.PublicKey
}
@@ -1979,7 +1979,7 @@ func TestGetAttesterDuties(t *testing.T) {
require.NoError(t, bs.SetValidators(vals))
pubKeys := make([][]byte, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
}
@@ -2242,7 +2242,7 @@ func TestGetProposerDuties(t *testing.T) {
roots[31] = []byte("next_epoch_dependent_root")
pubKeys := make([][]byte, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
}
@@ -2441,7 +2441,7 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
require.NoError(t, st.SetGenesisTime(genesisTime))
vals := st.Validators()
currCommittee := ðpbalpha.SyncCommittee{}
- for i := 0; i < 5; i++ {
+ for i := range 5 {
currCommittee.Pubkeys = append(currCommittee.Pubkeys, vals[i].PublicKey)
currCommittee.AggregatePubkey = make([]byte, 48)
}
@@ -2633,7 +2633,7 @@ func TestGetSyncCommitteeDuties(t *testing.T) {
}
require.NoError(t, newSyncPeriodSt.SetCurrentSyncCommittee(currCommittee))
nextCommittee := ðpbalpha.SyncCommittee{}
- for i := 0; i < 5; i++ {
+ for i := range 5 {
nextCommittee.Pubkeys = append(nextCommittee.Pubkeys, vals[i].PublicKey)
nextCommittee.AggregatePubkey = make([]byte, 48)
@@ -2949,14 +2949,14 @@ func BenchmarkServer_PrepareBeaconProposer(b *testing.B) {
}
f := bytesutil.PadTo([]byte{0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF}, fieldparams.FeeRecipientLength)
recipients := make([]*structs.FeeRecipient, 0)
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
recipients = append(recipients, &structs.FeeRecipient{FeeRecipient: hexutil.Encode(f), ValidatorIndex: fmt.Sprint(i)})
}
byt, err := json.Marshal(recipients)
require.NoError(b, err)
var body bytes.Buffer
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err = body.WriteString(string(byt))
require.NoError(b, err)
url := "http://example.com/eth/v1/validator/prepare_beacon_proposer"
diff --git a/beacon-chain/rpc/lookup/blocker_test.go b/beacon-chain/rpc/lookup/blocker_test.go
index 2da59cc282..2b1f980a6e 100644
--- a/beacon-chain/rpc/lookup/blocker_test.go
+++ b/beacon-chain/rpc/lookup/blocker_test.go
@@ -96,7 +96,7 @@ func TestGetBlock(t *testing.T) {
},
{
name: "non canonical",
- blockID: []byte(fmt.Sprintf("%d", nextSlot)),
+ blockID: fmt.Appendf(nil, "%d", nextSlot),
want: nil,
},
{
@@ -761,7 +761,7 @@ func TestBlobs_CommitmentOrdering(t *testing.T) {
t.Run("request non-existent hash", func(t *testing.T) {
// Create a fake versioned hash
fakeHash := make([]byte, 32)
- for i := 0; i < 32; i++ {
+ for i := range 32 {
fakeHash[i] = 0xFF
}
@@ -780,7 +780,7 @@ func TestBlobs_CommitmentOrdering(t *testing.T) {
// Create two fake versioned hashes
fakeHash1 := make([]byte, 32)
fakeHash2 := make([]byte, 32)
- for i := 0; i < 32; i++ {
+ for i := range 32 {
fakeHash1[i] = 0xAA
fakeHash2[i] = 0xBB
}
diff --git a/beacon-chain/rpc/prysm/node/handlers_test.go b/beacon-chain/rpc/prysm/node/handlers_test.go
index 7e6875438a..42fa7c2942 100644
--- a/beacon-chain/rpc/prysm/node/handlers_test.go
+++ b/beacon-chain/rpc/prysm/node/handlers_test.go
@@ -91,7 +91,7 @@ func TestListTrustedPeer(t *testing.T) {
// assert number of trusted peer is right
assert.Equal(t, 9, len(peers))
- for i := 0; i < 9; i++ {
+ for i := range 9 {
pid, err := peer.Decode(peers[i].PeerId)
require.NoError(t, err)
if pid == ids[8] {
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go
index a114919742..f7a56cea21 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/assignments_test.go
@@ -49,7 +49,7 @@ func TestServer_ListAssignments_Pagination_InputOutOfRange(t *testing.T) {
ctx := t.Context()
count := 100
validators := make([]*ethpb.Validator, 0, count)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
withdrawalCred := make([]byte, 32)
binary.LittleEndian.PutUint64(pubKey, uint64(i))
@@ -114,7 +114,7 @@ func TestServer_ListAssignments_Pagination_DefaultPageSize_NoArchive(t *testing.
ctx := t.Context()
count := 500
validators := make([]*ethpb.Validator, 0, count)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
withdrawalCred := make([]byte, 32)
binary.LittleEndian.PutUint64(pubKey, uint64(i))
@@ -200,7 +200,7 @@ func TestServer_ListAssignments_FilterPubkeysIndices_NoPagination(t *testing.T)
count := 100
validators := make([]*ethpb.Validator, 0, count)
withdrawCreds := make([]byte, 32)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
binary.LittleEndian.PutUint64(pubKey, uint64(i))
val := ðpb.Validator{
@@ -272,7 +272,7 @@ func TestServer_ListAssignments_CanFilterPubkeysIndices_WithPagination(t *testin
count := 100
validators := make([]*ethpb.Validator, 0, count)
withdrawCred := make([]byte, 32)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
binary.LittleEndian.PutUint64(pubKey, uint64(i))
val := ðpb.Validator{
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations.go
index 1290130067..38e31626f5 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations.go
@@ -446,7 +446,7 @@ func blockIndexedAttestations[T ethpb.IndexedAtt](
err,
)
}
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
att := atts[i]
committee, err := helpers.BeaconCommitteeFromState(ctx, attState, att.GetData().Slot, att.GetData().CommitteeIndex)
if err != nil {
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations_test.go
index fe72b7d89a..b330d4d796 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/attestations_test.go
@@ -111,7 +111,7 @@ func TestServer_ListAttestations_NoPagination(t *testing.T) {
count := primitives.Slot(8)
atts := make([]*ethpb.Attestation, 0, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
blockExample := util.NewBeaconBlock()
blockExample.Block.Body.Attestations = []*ethpb.Attestation{
{
@@ -264,7 +264,7 @@ func TestServer_ListAttestations_Pagination_CustomPageParameters(t *testing.T) {
count := params.BeaconConfig().SlotsPerEpoch * 4
atts := make([]ethpb.Att, 0, count)
for i := primitives.Slot(0); i < params.BeaconConfig().SlotsPerEpoch; i++ {
- for s := primitives.CommitteeIndex(0); s < 4; s++ {
+ for s := range primitives.CommitteeIndex(4) {
blockExample := util.NewBeaconBlock()
blockExample.Block.Slot = i
blockExample.Block.Body.Attestations = []*ethpb.Attestation{
@@ -370,7 +370,7 @@ func TestServer_ListAttestations_Pagination_OutOfRange(t *testing.T) {
util.NewBeaconBlock()
count := primitives.Slot(1)
atts := make([]*ethpb.Attestation, 0, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
blockExample := util.HydrateSignedBeaconBlock(ðpb.SignedBeaconBlock{
Block: ðpb.BeaconBlock{
Body: ðpb.BeaconBlockBody{
@@ -426,7 +426,7 @@ func TestServer_ListAttestations_Pagination_DefaultPageSize(t *testing.T) {
count := primitives.Slot(params.BeaconConfig().DefaultPageSize)
atts := make([]*ethpb.Attestation, 0, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
blockExample := util.NewBeaconBlock()
blockExample.Block.Body.Attestations = []*ethpb.Attestation{
{
@@ -520,7 +520,7 @@ func TestServer_mapAttestationToTargetRoot(t *testing.T) {
targetRoot1 := bytesutil.ToBytes32([]byte("root1"))
targetRoot2 := bytesutil.ToBytes32([]byte("root2"))
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
var targetRoot [32]byte
if i%2 == 0 {
targetRoot = targetRoot1
@@ -556,7 +556,7 @@ func TestServer_ListIndexedAttestations_GenesisEpoch(t *testing.T) {
atts := make([]*ethpb.Attestation, 0, count)
atts2 := make([]*ethpb.Attestation, 0, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
var targetRoot [32]byte
if i%2 == 0 {
targetRoot = targetRoot1
@@ -697,7 +697,7 @@ func TestServer_ListIndexedAttestations_OldEpoch(t *testing.T) {
state, _ := util.DeterministicGenesisState(t, numValidators)
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
randaoMixes[i] = make([]byte, fieldparams.RootLength)
}
require.NoError(t, state.SetRandaoMixes(randaoMixes))
@@ -754,7 +754,7 @@ func TestServer_ListIndexedAttestationsElectra(t *testing.T) {
atts := make([]*ethpb.AttestationElectra, 0, count)
atts2 := make([]*ethpb.AttestationElectra, 0, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
var targetRoot [32]byte
if i%2 == 0 {
targetRoot = targetRoot1
@@ -924,7 +924,7 @@ func TestServer_AttestationPool_Pagination_DefaultPageSize(t *testing.T) {
}
atts := make([]ethpb.Att, params.BeaconConfig().DefaultPageSize+1)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
att := util.NewAttestation()
att.Data.Slot = primitives.Slot(i)
atts[i] = att
@@ -946,7 +946,7 @@ func TestServer_AttestationPool_Pagination_CustomPageSize(t *testing.T) {
numAtts := 100
atts := make([]ethpb.Att, numAtts)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
att := util.NewAttestation()
att.Data.Slot = primitives.Slot(i)
atts[i] = att
@@ -1002,7 +1002,7 @@ func TestServer_AttestationPoolElectra(t *testing.T) {
}
atts := make([]ethpb.Att, params.BeaconConfig().DefaultPageSize+1)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
att := util.NewAttestationElectra()
att.Data.Slot = primitives.Slot(i)
atts[i] = att
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/blocks_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/blocks_test.go
index a9df700a31..9703fafe03 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/blocks_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/blocks_test.go
@@ -419,7 +419,7 @@ func runListBeaconBlocksGenesisMultiBlocks(t *testing.T, genBlock interfaces.Rea
count := primitives.Slot(100)
blks := make([]interfaces.ReadOnlySignedBeaconBlock, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
blks[i] = blockCreator(i)
}
require.NoError(t, db.SaveBlocks(ctx, blks))
@@ -554,7 +554,7 @@ func runListBeaconBlocksPagination(t *testing.T, orphanedBlk interfaces.ReadOnly
count := primitives.Slot(100)
blks := make([]interfaces.ReadOnlySignedBeaconBlock, count)
blkContainers := make([]*ethpb.BeaconBlockContainer, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
b := blockCreator(i)
root, err := b.Block().HashTreeRoot()
require.NoError(t, err)
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go
index 0c746cc3fb..7fde903463 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/committees_test.go
@@ -95,7 +95,7 @@ func TestServer_ListBeaconCommittees_PreviousEpoch(t *testing.T) {
headState := setupActiveValidators(t, numValidators)
mixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(mixes); i++ {
+ for i := range mixes {
mixes[i] = make([]byte, fieldparams.RootLength)
}
require.NoError(t, headState.SetRandaoMixes(mixes))
@@ -216,7 +216,7 @@ func TestRetrieveCommitteesForRoot(t *testing.T) {
func setupActiveValidators(t *testing.T, count int) state.BeaconState {
balances := make([]uint64, count)
validators := make([]*ethpb.Validator, 0, count)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := make([]byte, params.BeaconConfig().BLSPubkeyLength)
binary.LittleEndian.PutUint64(pubKey, uint64(i))
balances[i] = uint64(i)
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/config.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/config.go
index 98a7184950..4bab546ca8 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/config.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/config.go
@@ -18,7 +18,7 @@ func (_ *Server) GetBeaconConfig(_ context.Context, _ *emptypb.Empty) (*ethpb.Be
val := reflect.ValueOf(conf).Elem()
numFields := val.Type().NumField()
res := make(map[string]string, numFields)
- for i := 0; i < numFields; i++ {
+ for i := range numFields {
field := val.Type().Field(i)
if field.IsExported() {
res[field.Name] = fmt.Sprintf("%v", val.Field(i).Interface())
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/config_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/config_test.go
index 799b27e54d..851b03fdad 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/config_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/config_test.go
@@ -17,12 +17,12 @@ func TestServer_GetBeaconConfig(t *testing.T) {
res, err := bs.GetBeaconConfig(ctx, &emptypb.Empty{})
require.NoError(t, err)
conf := params.BeaconConfig()
- confType := reflect.TypeOf(conf).Elem()
+ confType := reflect.TypeFor[params.BeaconChainConfig]()
numFields := confType.NumField()
// Count only exported fields, as unexported fields are not included in the config
exportedFields := 0
- for i := 0; i < numFields; i++ {
+ for i := range numFields {
if confType.Field(i).IsExported() {
exportedFields++
}
diff --git a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go
index e7e5583f50..cfe78de180 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/beacon/validators_test.go
@@ -151,7 +151,7 @@ func TestServer_ListValidatorBalances_DefaultResponse_NoArchive(t *testing.T) {
validators := make([]*ethpb.Validator, numItems)
balances := make([]uint64, numItems)
balancesResponse := make([]*ethpb.ValidatorBalances_Balance, numItems)
- for i := 0; i < numItems; i++ {
+ for i := range numItems {
validators[i] = ðpb.Validator{
PublicKey: pubKey(uint64(i)),
WithdrawalCredentials: make([]byte, 32),
@@ -531,7 +531,7 @@ func TestServer_ListValidators_OnlyActiveValidators(t *testing.T) {
balances := make([]uint64, count)
validators := make([]*ethpb.Validator, count)
activeValidators := make([]*ethpb.Validators_ValidatorContainer, 0)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := pubKey(uint64(i))
balances[i] = params.BeaconConfig().MaxEffectiveBalance
@@ -594,7 +594,7 @@ func TestServer_ListValidators_InactiveInTheMiddle(t *testing.T) {
balances := make([]uint64, count)
validators := make([]*ethpb.Validator, count)
activeValidators := make([]*ethpb.Validators_ValidatorContainer, 0)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := pubKey(uint64(i))
balances[i] = params.BeaconConfig().MaxEffectiveBalance
@@ -708,7 +708,7 @@ func TestServer_ListValidators_NoPagination(t *testing.T) {
validators, _, headState := setupValidators(t, beaconDB, 100)
want := make([]*ethpb.Validators_ValidatorContainer, len(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
want[i] = ðpb.Validators_ValidatorContainer{
Index: primitives.ValidatorIndex(i),
Validator: validators[i],
@@ -741,7 +741,7 @@ func TestServer_ListValidators_StategenNotUsed(t *testing.T) {
validators, _, headState := setupValidators(t, beaconDB, 100)
want := make([]*ethpb.Validators_ValidatorContainer, len(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
want[i] = ðpb.Validators_ValidatorContainer{
Index: primitives.ValidatorIndex(i),
Validator: validators[i],
@@ -991,7 +991,7 @@ func TestServer_ListValidators_DefaultPageSize(t *testing.T) {
validators, _, headState := setupValidators(t, beaconDB, 1000)
want := make([]*ethpb.Validators_ValidatorContainer, len(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
want[i] = ðpb.Validators_ValidatorContainer{
Index: primitives.ValidatorIndex(i),
Validator: validators[i],
@@ -1099,7 +1099,7 @@ func TestServer_ListValidators_ProcessHeadStateSlots(t *testing.T) {
numValidators := params.BeaconConfig().MinGenesisActiveValidatorCount
validators := make([]*ethpb.Validator, numValidators)
balances := make([]uint64, numValidators)
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
validators[i] = ðpb.Validator{
ActivationEpoch: 0,
PublicKey: make([]byte, 48),
@@ -1109,7 +1109,7 @@ func TestServer_ListValidators_ProcessHeadStateSlots(t *testing.T) {
balances[i] = params.BeaconConfig().MaxEffectiveBalance
}
want := make([]*ethpb.Validators_ValidatorContainer, len(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
want[i] = ðpb.Validators_ValidatorContainer{
Index: primitives.ValidatorIndex(i),
Validator: validators[i],
@@ -1154,7 +1154,7 @@ func TestServer_ListValidators_ProcessHeadStateSlots(t *testing.T) {
func TestServer_GetValidator(t *testing.T) {
count := primitives.Epoch(30)
validators := make([]*ethpb.Validator, count)
- for i := primitives.Epoch(0); i < count; i++ {
+ for i := range count {
validators[i] = ðpb.Validator{
ActivationEpoch: i,
PublicKey: pubKey(uint64(i)),
@@ -1241,7 +1241,7 @@ func TestServer_GetValidatorActiveSetChanges(t *testing.T) {
require.NoError(t, err)
require.NoError(t, headState.SetSlot(0))
require.NoError(t, headState.SetValidators(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
activationEpoch := params.BeaconConfig().FarFutureEpoch
withdrawableEpoch := params.BeaconConfig().FarFutureEpoch
exitEpoch := params.BeaconConfig().FarFutureEpoch
@@ -1513,7 +1513,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
validators := make([]*ethpb.Validator, validatorCount)
balances := make([]uint64, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: bytesutil.ToBytes(uint64(i), 48),
WithdrawalCredentials: make([]byte, 32),
@@ -1597,7 +1597,7 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
validators := make([]*ethpb.Validator, validatorCount)
balances := make([]uint64, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: bytesutil.ToBytes(uint64(i), 48),
WithdrawalCredentials: make([]byte, 32),
@@ -1823,7 +1823,7 @@ func TestGetValidatorPerformance_OK(t *testing.T) {
require.NoError(t, err)
require.NoError(t, headState.SetSlot(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epoch+1))))
atts := make([]*ethpb.PendingAttestation, 3)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = ðpb.PendingAttestation{
Data: ðpb.AttestationData{
Target: ðpb.Checkpoint{Root: make([]byte, 32)},
@@ -2249,7 +2249,7 @@ func TestGetValidatorPerformanceCapella_OK(t *testing.T) {
func setupValidators(t testing.TB, _ db.Database, count int) ([]*ethpb.Validator, []uint64, state.BeaconState) {
balances := make([]uint64, count)
validators := make([]*ethpb.Validator, 0, count)
- for i := 0; i < count; i++ {
+ for i := range count {
pubKey := pubKey(uint64(i))
balances[i] = uint64(i)
validators = append(validators, ðpb.Validator{
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go
index 7d2faf7302..70916c4ac7 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/attester_test.go
@@ -46,7 +46,7 @@ func TestProposeAttestation(t *testing.T) {
require.NoError(t, err)
validators := make([]*ethpb.Validator, 64)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, 48),
WithdrawalCredentials: make([]byte, 32),
@@ -253,12 +253,11 @@ func BenchmarkGetAttestationDataConcurrent(b *testing.B) {
Slot: 3*params.BeaconConfig().SlotsPerEpoch + 1,
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
var wg sync.WaitGroup
wg.Add(5000) // for 5000 concurrent accesses
- for j := 0; j < 5000; j++ {
+ for range 5000 {
go func() {
defer wg.Done()
_, err := attesterServer.GetAttestationData(b.Context(), req)
@@ -577,7 +576,7 @@ func TestServer_SubscribeCommitteeSubnets_MultipleSlots(t *testing.T) {
randGen := rand.New(s)
validators := make([]*ethpb.Validator, 64)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/blocks_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/blocks_test.go
index 25a2f1904e..8bba2a5704 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/blocks_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/blocks_test.go
@@ -94,7 +94,7 @@ func TestServer_StreamAltairBlocks_OnHeadUpdated(t *testing.T) {
defer ctrl.Finish()
mockStream := mock.NewMockBeaconNodeValidatorAltair_StreamBlocksServer(ctrl)
- mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_AltairBlock{AltairBlock: b}}).Do(func(arg0 interface{}) {
+ mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_AltairBlock{AltairBlock: b}}).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -139,7 +139,7 @@ func TestServer_StreamCapellaBlocks_OnHeadUpdated(t *testing.T) {
defer ctrl.Finish()
mockStream := mock.NewMockBeaconNodeValidatorAltair_StreamBlocksServer(ctrl)
- mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_CapellaBlock{CapellaBlock: b}}).Do(func(arg0 interface{}) {
+ mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_CapellaBlock{CapellaBlock: b}}).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -185,7 +185,7 @@ func TestServer_StreamAltairBlocksVerified_OnHeadUpdated(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStream := mock.NewMockBeaconNodeValidatorAltair_StreamBlocksServer(ctrl)
- mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_AltairBlock{AltairBlock: b}}).Do(func(arg0 interface{}) {
+ mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_AltairBlock{AltairBlock: b}}).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -229,7 +229,7 @@ func TestServer_StreamCapellaBlocksVerified_OnHeadUpdated(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStream := mock.NewMockBeaconNodeValidatorAltair_StreamBlocksServer(ctrl)
- mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_CapellaBlock{CapellaBlock: b}}).Do(func(arg0 interface{}) {
+ mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_CapellaBlock{CapellaBlock: b}}).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -320,7 +320,7 @@ func TestServer_StreamSlots_OnHeadUpdated(t *testing.T) {
Slot: 123,
PreviousDutyDependentRoot: params.BeaconConfig().ZeroHash[:],
CurrentDutyDependentRoot: params.BeaconConfig().ZeroHash[:],
- }).Do(func(arg0 interface{}) {
+ }).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -359,7 +359,7 @@ func TestServer_StreamSlotsVerified_OnHeadUpdated(t *testing.T) {
Slot: 123,
PreviousDutyDependentRoot: params.BeaconConfig().ZeroHash[:],
CurrentDutyDependentRoot: params.BeaconConfig().ZeroHash[:],
- }).Do(func(arg0 interface{}) {
+ }).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -405,7 +405,7 @@ func TestServer_StreamBlocksVerified_FuluBlock(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockStream := mock.NewMockBeaconNodeValidatorAltair_StreamBlocksServer(ctrl)
- mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_FuluBlock{FuluBlock: b}}).Do(func(arg0 interface{}) {
+ mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_FuluBlock{FuluBlock: b}}).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
@@ -448,7 +448,7 @@ func TestServer_StreamBlocks_FuluBlock(t *testing.T) {
defer ctrl.Finish()
mockStream := mock.NewMockBeaconNodeValidatorAltair_StreamBlocksServer(ctrl)
- mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_FuluBlock{FuluBlock: b}}).Do(func(arg0 interface{}) {
+ mockStream.EXPECT().Send(ðpb.StreamBlocksResponse{Block: ðpb.StreamBlocksResponse_FuluBlock{FuluBlock: b}}).Do(func(arg0 any) {
exitRoutine <- true
})
mockStream.EXPECT().Context().Return(ctx).AnyTimes()
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/duties_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/duties_test.go
index 65eb5b45bd..f19532516a 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/duties_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/duties_test.go
@@ -45,7 +45,7 @@ func TestGetDuties_OK(t *testing.T) {
pubKeys := make([][]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
indices[i] = uint64(i)
}
@@ -123,7 +123,7 @@ func TestGetAltairDuties_SyncCommitteeOK(t *testing.T) {
require.NoError(t, bs.SetCurrentSyncCommittee(syncCommittee))
pubKeys := make([][]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
indices[i] = uint64(i)
}
@@ -227,7 +227,7 @@ func TestGetBellatrixDuties_SyncCommitteeOK(t *testing.T) {
require.NoError(t, bs.SetCurrentSyncCommittee(syncCommittee))
pubKeys := make([][]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
indices[i] = uint64(i)
}
@@ -391,7 +391,7 @@ func TestGetDuties_CurrentEpoch_ShouldNotFail(t *testing.T) {
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
indices[i] = uint64(i)
}
@@ -431,7 +431,7 @@ func TestGetDuties_MultipleKeys_OK(t *testing.T) {
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
indices[i] = uint64(i)
}
@@ -484,7 +484,7 @@ func BenchmarkCommitteeAssignment(b *testing.B) {
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
indices[i] = uint64(i)
}
@@ -505,8 +505,8 @@ func BenchmarkCommitteeAssignment(b *testing.B) {
PublicKeys: pks,
Epoch: 0,
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := vs.GetDuties(b.Context(), req)
assert.NoError(b, err)
}
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/duties_v2_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/duties_v2_test.go
index 09e58e7a10..73a386104c 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/duties_v2_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/duties_v2_test.go
@@ -39,7 +39,7 @@ func TestGetDutiesV2_OK(t *testing.T) {
pubKeys := make([][]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
indices[i] = uint64(i)
}
@@ -117,7 +117,7 @@ func TestGetAltairDutiesV2_SyncCommitteeOK(t *testing.T) {
require.NoError(t, bs.SetCurrentSyncCommittee(syncCommittee))
pubKeys := make([][]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
indices[i] = uint64(i)
}
@@ -221,7 +221,7 @@ func TestGetBellatrixDutiesV2_SyncCommitteeOK(t *testing.T) {
require.NoError(t, bs.SetCurrentSyncCommittee(syncCommittee))
pubKeys := make([][]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = deposits[i].Data.PublicKey
indices[i] = uint64(i)
}
@@ -428,7 +428,7 @@ func TestGetDutiesV2_CurrentEpoch_ShouldNotFail(t *testing.T) {
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
indices[i] = uint64(i)
}
@@ -468,7 +468,7 @@ func TestGetDutiesV2_MultipleKeys_OK(t *testing.T) {
pubKeys := make([][fieldparams.BLSPubkeyLength]byte, len(deposits))
indices := make([]uint64, len(deposits))
- for i := 0; i < len(deposits); i++ {
+ for i := range deposits {
pubKeys[i] = bytesutil.ToBytes48(deposits[i].Data.PublicKey)
indices[i] = uint64(i)
}
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go
index a06a1a5b6c..d3bb406952 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go
@@ -191,9 +191,7 @@ func (vs *Server) getParentState(ctx context.Context, slot primitives.Slot) (sta
func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.SignedBeaconBlock, head state.BeaconState, skipMevBoost bool, builderBoostFactor primitives.Gwei) (*ethpb.GenericBeaconBlock, error) {
// Build consensus fields in background
var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
// Set eth1 data.
eth1Data, err := vs.eth1DataMajorityVote(ctx, head)
@@ -233,7 +231,7 @@ func (vs *Server) BuildBlockParallel(ctx context.Context, sBlk interfaces.Signed
// Set bls to execution change. New in Capella.
vs.setBlsToExecData(sBlk, head)
- }()
+ })
winningBid := primitives.ZeroWei()
var bundle enginev1.BlobsBundler
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair.go
index 7894c49261..e2ea14693a 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair.go
@@ -71,12 +71,12 @@ func (vs *Server) getSyncAggregate(ctx context.Context, slot primitives.Slot, ro
subcommitteeCount := params.BeaconConfig().SyncCommitteeSubnetCount
var bitsHolder [][]byte
- for i := uint64(0); i < subcommitteeCount; i++ {
+ for range subcommitteeCount {
bitsHolder = append(bitsHolder, ethpb.NewSyncCommitteeAggregationBits())
}
sigsHolder := make([]bls.Signature, 0, params.BeaconConfig().SyncCommitteeSize/subcommitteeCount)
- for i := uint64(0); i < subcommitteeCount; i++ {
+ for i := range subcommitteeCount {
cs := proposerContributions.filterBySubIndex(i)
aggregates, err := synccontribution.Aggregate(cs)
if err != nil {
@@ -130,7 +130,7 @@ func (vs *Server) aggregatedSyncCommitteeMessages(
subcommitteeSize := params.BeaconConfig().SyncCommitteeSize / subcommitteeCount
sigsPerSubcommittee := make([][][]byte, subcommitteeCount)
bitsPerSubcommittee := make([]bitfield.Bitfield, subcommitteeCount)
- for i := uint64(0); i < subcommitteeCount; i++ {
+ for i := range subcommitteeCount {
sigsPerSubcommittee[i] = make([][]byte, 0, subcommitteeSize)
bitsPerSubcommittee[i] = ethpb.NewSyncCommitteeAggregationBits()
}
@@ -180,7 +180,7 @@ func (vs *Server) aggregatedSyncCommitteeMessages(
// Aggregate.
result := make([]*ethpb.SyncCommitteeContribution, 0, subcommitteeCount)
- for i := uint64(0); i < subcommitteeCount; i++ {
+ for i := range subcommitteeCount {
aggregatedSig := make([]byte, 96)
aggregatedSig[0] = 0xC0
if len(sigsPerSubcommittee[i]) != 0 {
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair_test.go
index b97366263a..a73d41ac02 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_altair_test.go
@@ -271,14 +271,14 @@ func TestGetSyncAggregate_CorrectStateAtSyncCommitteePeriodBoundary(t *testing.T
wrongStateHasValidators := false
correctStateHasValidators := false
- for i := 0; i < len(wrongStateBits); i++ {
+ for i := range wrongStateBits {
if wrongStateBits[i] != 0 {
wrongStateHasValidators = true
break
}
}
- for i := 0; i < len(correctStateBits); i++ {
+ for i := range correctStateBits {
if correctStateBits[i] != 0 {
correctStateHasValidators = true
break
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go
index ad8a57f701..7dbd2fcab9 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations.go
@@ -289,7 +289,7 @@ func (a proposerAtts) sortByProfitabilityUsingMaxCover_committeeAwarePacking() (
return a, nil
}
candidates := make([]*bitfield.Bitlist64, len(a))
- for i := 0; i < len(a); i++ {
+ for i := range a {
var err error
candidates[i], err = a[i].GetAggregationBits().ToBitlist64()
if err != nil {
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go
index 8ae2978674..20a9434216 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_attestations_test.go
@@ -703,7 +703,7 @@ func Benchmark_packAttestations_Electra(b *testing.B) {
r := rand.New(rand.NewSource(123))
var atts []ethpb.Att
- for c := uint64(0); c < committeeCount; c++ {
+ for c := range committeeCount {
for a := uint64(0); a < params.BeaconConfig().TargetAggregatorsPerCommittee; a++ {
cb := primitives.NewAttestationCommitteeBits()
cb.SetBitAt(c, true)
@@ -718,7 +718,7 @@ func Benchmark_packAttestations_Electra(b *testing.B) {
CommitteeBits: cb,
Signature: sig.Marshal(),
}
- for bit := uint64(0); bit < valsPerCommittee; bit++ {
+ for bit := range valsPerCommittee {
att.AggregationBits.SetBitAt(bit, r.Intn(100) < 2) // 2% that the bit is set
}
} else {
@@ -728,7 +728,7 @@ func Benchmark_packAttestations_Electra(b *testing.B) {
CommitteeBits: cb,
Signature: sig.Marshal(),
}
- for bit := uint64(0); bit < valsPerCommittee; bit++ {
+ for bit := range valsPerCommittee {
att.AggregationBits.SetBitAt(bit, r.Intn(100) < 98) // 98% that the bit is set
}
}
@@ -745,8 +745,7 @@ func Benchmark_packAttestations_Electra(b *testing.B) {
require.NoError(b, st.SetSlot(params.BeaconConfig().SlotsPerEpoch))
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err = s.packAttestations(ctx, st, params.BeaconConfig().SlotsPerEpoch+1)
require.NoError(b, err)
}
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deneb_bench_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deneb_bench_test.go
index 7427dd46b9..770d6da939 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deneb_bench_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_deneb_bench_test.go
@@ -55,7 +55,7 @@ func setupBenchmarkData(b *testing.B, numBlobs int) (interfaces.SignedBeaconBloc
// Create KZG commitments
kzgCommitments := make([][]byte, numBlobs)
- for i := 0; i < numBlobs; i++ {
+ for i := range numBlobs {
kzgCommitments[i] = bytesutil.PadTo([]byte{byte(i)}, 48)
}
@@ -70,7 +70,7 @@ func setupBenchmarkData(b *testing.B, numBlobs int) (interfaces.SignedBeaconBloc
// Create blobs
blobs := make([][]byte, numBlobs)
- for i := 0; i < numBlobs; i++ {
+ for i := range numBlobs {
blobs[i] = make([]byte, fieldparams.BlobLength)
// Add some variation to the blob data
blobs[i][0] = byte(i)
@@ -82,7 +82,7 @@ func setupBenchmarkData(b *testing.B, numBlobs int) (interfaces.SignedBeaconBloc
b.Fatal(err)
}
kzgProofs := make([][]byte, numBlobs)
- for i := 0; i < numBlobs; i++ {
+ for i := range numBlobs {
kzgProofs[i] = proof
}
@@ -92,8 +92,7 @@ func setupBenchmarkData(b *testing.B, numBlobs int) (interfaces.SignedBeaconBloc
func BenchmarkBuildBlobSidecars_Original_1Blob(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 1)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -104,8 +103,7 @@ func BenchmarkBuildBlobSidecars_Original_1Blob(b *testing.B) {
func BenchmarkBuildBlobSidecars_Optimized_1Blob(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 1)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -116,8 +114,7 @@ func BenchmarkBuildBlobSidecars_Optimized_1Blob(b *testing.B) {
func BenchmarkBuildBlobSidecars_Original_2Blobs(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 2)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -128,8 +125,7 @@ func BenchmarkBuildBlobSidecars_Original_2Blobs(b *testing.B) {
func BenchmarkBuildBlobSidecars_Optimized_3Blobs(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 3)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -140,8 +136,7 @@ func BenchmarkBuildBlobSidecars_Optimized_3Blobs(b *testing.B) {
func BenchmarkBuildBlobSidecars_Original_3Blobs(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 3)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -152,8 +147,7 @@ func BenchmarkBuildBlobSidecars_Original_3Blobs(b *testing.B) {
func BenchmarkBuildBlobSidecars_Optimized_4Blobs(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 4)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -164,8 +158,7 @@ func BenchmarkBuildBlobSidecars_Optimized_4Blobs(b *testing.B) {
func BenchmarkBuildBlobSidecars_Original_9Blobs(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 9)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecarsOriginal(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -176,8 +169,7 @@ func BenchmarkBuildBlobSidecars_Original_9Blobs(b *testing.B) {
func BenchmarkBuildBlobSidecars_Optimized_9Blobs(b *testing.B) {
blk, blobs, kzgProofs := setupBenchmarkData(b, 9)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := BuildBlobSidecars(blk, blobs, kzgProofs)
if err != nil {
b.Fatal(err)
@@ -190,9 +182,8 @@ func BenchmarkMerkleProofKZGCommitment_Original(b *testing.B) {
blk, _, _ := setupBenchmarkData(b, 4)
body := blk.Block().Body()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- for j := 0; j < 4; j++ {
+ for b.Loop() {
+ for j := range 4 {
_, err := blocks.MerkleProofKZGCommitment(body, j)
if err != nil {
b.Fatal(err)
@@ -205,8 +196,7 @@ func BenchmarkMerkleProofKZGCommitment_Optimized(b *testing.B) {
blk, _, _ := setupBenchmarkData(b, 4)
body := blk.Block().Body()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
// Pre-compute components once
components, err := blocks.PrecomputeMerkleProofComponents(body)
if err != nil {
@@ -214,7 +204,7 @@ func BenchmarkMerkleProofKZGCommitment_Optimized(b *testing.B) {
}
// Generate proofs for each index
- for j := 0; j < 4; j++ {
+ for j := range 4 {
_, err := blocks.MerkleProofKZGCommitmentFromComponents(components, j)
if err != nil {
b.Fatal(err)
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go
index 7365b6a501..11681e5ef8 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_test.go
@@ -1121,7 +1121,7 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
numberOfColumns := uint64(128)
// For Fulu, we have cell proofs (blobs * numberOfColumns)
cellProofs := make([][]byte, numberOfColumns)
- for i := uint64(0); i < numberOfColumns; i++ {
+ for i := range numberOfColumns {
cellProofs[i] = bytesutil.PadTo([]byte{byte(i)}, 48)
}
// Blob must be exactly 131072 bytes
@@ -1155,7 +1155,7 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
}
// Create properly sized blobs (131072 bytes each)
blobs := make([][]byte, blobCount)
- for i := 0; i < blobCount; i++ {
+ for i := range blobCount {
blob := make([]byte, 131072)
blob[0] = byte(i + 1)
blobs[i] = blob
@@ -1244,7 +1244,7 @@ func TestProposer_ProposeBlock_OK(t *testing.T) {
// Create cell proofs for Fulu blocks (128 proofs per blob)
numberOfColumns := uint64(128)
cellProofs := make([][]byte, numberOfColumns)
- for i := uint64(0); i < numberOfColumns; i++ {
+ for i := range numberOfColumns {
cellProofs[i] = bytesutil.PadTo([]byte{byte(i)}, 48)
}
// Create properly sized blob for mock builder
@@ -2908,7 +2908,7 @@ func TestProposer_FilterAttestation(t *testing.T) {
name: "invalid attestations",
inputAtts: func() []ethpb.Att {
atts := make([]ethpb.Att, 10)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = util.HydrateAttestation(ðpb.Attestation{
Data: ðpb.AttestationData{
CommitteeIndex: primitives.CommitteeIndex(i),
@@ -2925,7 +2925,7 @@ func TestProposer_FilterAttestation(t *testing.T) {
name: "filter aggregates ok",
inputAtts: func() []ethpb.Att {
atts := make([]ethpb.Att, 10)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = util.HydrateAttestation(ðpb.Attestation{
Data: ðpb.AttestationData{
CommitteeIndex: primitives.CommitteeIndex(i),
@@ -3227,15 +3227,15 @@ func BenchmarkServer_PrepareBeaconProposer(b *testing.B) {
}
f := bytesutil.PadTo([]byte{0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF, 0x01, 0xFF}, fieldparams.FeeRecipientLength)
recipients := make([]*ethpb.PrepareBeaconProposerRequest_FeeRecipientContainer, 0)
- for i := 0; i < 10000; i++ {
+ for i := range 10000 {
recipients = append(recipients, ðpb.PrepareBeaconProposerRequest_FeeRecipientContainer{FeeRecipient: f, ValidatorIndex: primitives.ValidatorIndex(i)})
}
req := ðpb.PrepareBeaconProposerRequest{
Recipients: recipients,
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := proposerServer.PrepareBeaconProposer(ctx, req)
if err != nil {
b.Fatal(err)
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_utils_bench_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_utils_bench_test.go
index 2bc3b57da1..2d493b3368 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_utils_bench_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_utils_bench_test.go
@@ -58,7 +58,7 @@ func BenchmarkProposerAtts_sortByProfitability(b *testing.B) {
b.StopTimer()
atts := aggtesting.MakeAttestationsFromBitlists(tt.inputs)
b.StartTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
runner(atts)
}
})
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go
index 8d8f20c616..6a1e68629b 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/server_test.go
@@ -268,14 +268,12 @@ func TestWaitForChainStart_HeadStateDoesNotExist(t *testing.T) {
mockStream.EXPECT().Context().Return(t.Context())
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
err := Server.WaitForChainStart(&emptypb.Empty{}, mockStream)
if s, _ := status.FromError(err); s.Code() != codes.Canceled {
assert.NoError(t, err)
}
- wg.Done()
- }()
+ })
util.WaitTimeout(wg, time.Second)
}
diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go b/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go
index b38b96bad2..baa6105c23 100644
--- a/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go
+++ b/beacon-chain/rpc/prysm/v1alpha1/validator/status_test.go
@@ -661,7 +661,7 @@ func TestValidatorStatus_CorrectActivationQueue(t *testing.T) {
depositCache, err := depositsnapshot.New()
require.NoError(t, err)
- for i := 0; i < 6; i++ {
+ for i := range 6 {
depData := ðpb.Deposit_Data{
PublicKey: pubKey(uint64(i)),
Signature: bytesutil.PadTo([]byte("hi"), 96),
@@ -975,7 +975,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
}
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
- for i := 0; i < 3; i++ {
+ for i := range 3 {
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
PublicKey: keys[i].PublicKey().Marshal(),
Epoch: 1,
@@ -1011,7 +1011,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
}
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
- for i := 0; i < 2; i++ {
+ for i := range 2 {
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
PublicKey: keys[i].PublicKey().Marshal(),
Epoch: 1,
@@ -1058,7 +1058,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
}
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
- for i := 0; i < 2; i++ {
+ for i := range 2 {
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
PublicKey: keys[i].PublicKey().Marshal(),
Epoch: 1,
@@ -1161,7 +1161,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
}
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
- for i := 0; i < 15; i++ {
+ for i := range 15 {
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
PublicKey: keys[i].PublicKey().Marshal(),
Epoch: 2,
@@ -1198,7 +1198,7 @@ func TestServer_CheckDoppelGanger(t *testing.T) {
ValidatorRequests: make([]*ethpb.DoppelGangerRequest_ValidatorRequest, 0),
}
response := ðpb.DoppelGangerResponse{Responses: make([]*ethpb.DoppelGangerResponse_ValidatorResponse, 0)}
- for i := 0; i < 15; i++ {
+ for i := range 15 {
request.ValidatorRequests = append(request.ValidatorRequests, ðpb.DoppelGangerRequest_ValidatorRequest{
PublicKey: keys[i].PublicKey().Marshal(),
Epoch: 1,
diff --git a/beacon-chain/rpc/prysm/validator/handlers_test.go b/beacon-chain/rpc/prysm/validator/handlers_test.go
index 730f144348..0900e7226f 100644
--- a/beacon-chain/rpc/prysm/validator/handlers_test.go
+++ b/beacon-chain/rpc/prysm/validator/handlers_test.go
@@ -85,7 +85,7 @@ func TestServer_GetValidatorParticipation_CurrentAndPrevEpoch(t *testing.T) {
validators := make([]*ethpb.Validator, validatorCount)
balances := make([]uint64, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: bytesutil.ToBytes(uint64(i), 48),
WithdrawalCredentials: make([]byte, 32),
@@ -189,7 +189,7 @@ func TestServer_GetValidatorParticipation_OrphanedUntilGenesis(t *testing.T) {
validators := make([]*ethpb.Validator, validatorCount)
balances := make([]uint64, validatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: bytesutil.ToBytes(uint64(i), 48),
WithdrawalCredentials: make([]byte, 32),
@@ -446,7 +446,7 @@ func TestServer_GetValidatorActiveSetChanges(t *testing.T) {
require.NoError(t, err)
require.NoError(t, headState.SetSlot(0))
require.NoError(t, headState.SetValidators(validators))
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
activationEpoch := params.BeaconConfig().FarFutureEpoch
withdrawableEpoch := params.BeaconConfig().FarFutureEpoch
exitEpoch := params.BeaconConfig().FarFutureEpoch
diff --git a/beacon-chain/rpc/prysm/validator/validator_performance_test.go b/beacon-chain/rpc/prysm/validator/validator_performance_test.go
index 5d055b3769..795e2ae285 100644
--- a/beacon-chain/rpc/prysm/validator/validator_performance_test.go
+++ b/beacon-chain/rpc/prysm/validator/validator_performance_test.go
@@ -425,7 +425,7 @@ func setHeadState(t *testing.T, headState state.BeaconState, publicKeys [][48]by
require.NoError(t, headState.SetSlot(params.BeaconConfig().SlotsPerEpoch.Mul(uint64(epoch+1))))
if headState.Version() < version.Altair {
atts := make([]*ethpb.PendingAttestation, 3)
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
atts[i] = ðpb.PendingAttestation{
Data: ðpb.AttestationData{
Target: ðpb.Checkpoint{Root: make([]byte, 32)},
diff --git a/beacon-chain/rpc/service.go b/beacon-chain/rpc/service.go
index 1aeeb18448..8953780f8a 100644
--- a/beacon-chain/rpc/service.go
+++ b/beacon-chain/rpc/service.go
@@ -377,7 +377,7 @@ func (s *Service) Status() error {
// Stream interceptor for new validator client connections to the beacon node.
func (s *Service) validatorStreamConnectionInterceptor(
- srv interface{},
+ srv any,
ss grpc.ServerStream,
_ *grpc.StreamServerInfo,
handler grpc.StreamHandler,
@@ -389,10 +389,10 @@ func (s *Service) validatorStreamConnectionInterceptor(
// Unary interceptor for new validator client connections to the beacon node.
func (s *Service) validatorUnaryConnectionInterceptor(
ctx context.Context,
- req interface{},
+ req any,
_ *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
-) (interface{}, error) {
+) (any, error) {
s.logNewClientConnection(ctx)
return handler(ctx, req)
}
diff --git a/beacon-chain/rpc/testutil/db.go b/beacon-chain/rpc/testutil/db.go
index 5eb563bc5d..41871680c4 100644
--- a/beacon-chain/rpc/testutil/db.go
+++ b/beacon-chain/rpc/testutil/db.go
@@ -26,7 +26,7 @@ func FillDBWithBlocks(ctx context.Context, t *testing.T, beaconDB db.Database) (
count := primitives.Slot(100)
blks := make([]interfaces.ReadOnlySignedBeaconBlock, count)
blkContainers := make([]*ethpbalpha.BeaconBlockContainer, count)
- for i := primitives.Slot(0); i < count; i++ {
+ for i := range count {
b := util.NewBeaconBlock()
b.Block.Slot = i
b.Block.ParentRoot = bytesutil.PadTo([]byte{uint8(i)}, 32)
diff --git a/beacon-chain/slasher/chunks.go b/beacon-chain/slasher/chunks.go
index aa979a32a8..074c3cb2d1 100644
--- a/beacon-chain/slasher/chunks.go
+++ b/beacon-chain/slasher/chunks.go
@@ -101,7 +101,7 @@ func EmptyMinSpanChunksSlice(params *Parameters) *MinSpanChunksSlice {
params: params,
}
data := make([]uint16, params.chunkSize*params.validatorChunkSize)
- for i := 0; i < len(data); i++ {
+ for i := range data {
data[i] = m.NeutralElement()
}
m.data = data
@@ -116,7 +116,7 @@ func EmptyMaxSpanChunksSlice(params *Parameters) *MaxSpanChunksSlice {
params: params,
}
data := make([]uint16, params.chunkSize*params.validatorChunkSize)
- for i := 0; i < len(data); i++ {
+ for i := range data {
data[i] = m.NeutralElement()
}
m.data = data
diff --git a/beacon-chain/slasher/detect_attestations.go b/beacon-chain/slasher/detect_attestations.go
index 28cb94e4c8..24c83634aa 100644
--- a/beacon-chain/slasher/detect_attestations.go
+++ b/beacon-chain/slasher/detect_attestations.go
@@ -29,9 +29,7 @@ func (s *Service) checkSlashableAttestations(
return nil, errors.Wrap(err, "could not check slashable double votes")
}
- for root, slashing := range doubleVoteSlashings {
- slashings[root] = slashing
- }
+ maps.Copy(slashings, doubleVoteSlashings)
// Save the attestation records to our database.
// If multiple attestations are provided for the same validator index + target epoch combination,
@@ -46,9 +44,7 @@ func (s *Service) checkSlashableAttestations(
return nil, errors.Wrap(err, "could not check slashable surround votes")
}
- for root, slashing := range surroundSlashings {
- slashings[root] = slashing
- }
+ maps.Copy(slashings, surroundSlashings)
return slashings, nil
}
@@ -97,9 +93,7 @@ func (s *Service) checkSurroundVotes(
return nil, errors.Wrapf(err, "could not update min attestation spans for validator chunk index %d", validatorChunkIndex)
}
- for root, slashing := range surroundingSlashings {
- slashings[root] = slashing
- }
+ maps.Copy(slashings, surroundingSlashings)
// Check for surrounded votes.
surroundedSlashings, err := s.updateSpans(ctx, maxChunkByChunkIndex, attWrappersByChunkIndex, slashertypes.MaxSpan, validatorChunkIndex, currentEpoch)
@@ -107,9 +101,7 @@ func (s *Service) checkSurroundVotes(
return nil, errors.Wrapf(err, "could not update max attestation spans for validator chunk index %d", validatorChunkIndex)
}
- for root, slashing := range surroundedSlashings {
- slashings[root] = slashing
- }
+ maps.Copy(slashings, surroundedSlashings)
// Memoize the updated chunks for the current validator chunk index.
minChunkByChunkIndexByValidatorChunkIndex[validatorChunkIndex] = minChunkByChunkIndex
@@ -755,7 +747,7 @@ func (s *Service) loadChunksFromDisk(
// Initialize the chunks.
chunksByChunkIdx := make(map[uint64]Chunker, chunksCount)
- for i := 0; i < len(rawChunks); i++ {
+ for i := range rawChunks {
// If the chunk exists in the database, we initialize it from the raw bytes data.
// If it does not exist, we initialize an empty chunk.
var (
diff --git a/beacon-chain/slasher/detect_attestations_test.go b/beacon-chain/slasher/detect_attestations_test.go
index ee5c9d2c3a..c1690aa34b 100644
--- a/beacon-chain/slasher/detect_attestations_test.go
+++ b/beacon-chain/slasher/detect_attestations_test.go
@@ -659,7 +659,7 @@ func Test_processAttestations(t *testing.T) {
validators := make([]*ethpb.Validator, numVals)
privateKeys := make([]bls.SecretKey, numVals)
- for i := uint64(0); i < numVals; i++ {
+ for i := range numVals {
// Create a random private key.
privateKey, err := bls.RandKey()
require.NoError(t, err)
@@ -1463,7 +1463,7 @@ func Benchmark_saveChunksToDisk(b *testing.B) {
chunkByChunkIndexByValidatorChunkIndex := make(map[uint64]map[uint64]Chunker, validatorsChunksCount)
// Populate the chunkers.
- for i := 0; i < validatorsChunksCount; i++ {
+ for i := range validatorsChunksCount {
data := make([]uint16, params.chunkSize)
for j := 0; j < int(params.chunkSize); j++ {
data[j] = uint16(rand.Intn(1 << 16))
@@ -1481,10 +1481,9 @@ func Benchmark_saveChunksToDisk(b *testing.B) {
require.NoError(b, err)
// Reset the benchmark timer.
- b.ResetTimer()
// Run the benchmark.
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
b.StartTimer()
err = service.saveChunksToDisk(ctx, slashertypes.MinSpan, chunkByChunkIndexByValidatorChunkIndex)
b.StopTimer()
@@ -1553,11 +1552,11 @@ func BenchmarkCheckSlashableAttestations(b *testing.B) {
func runAttestationsBenchmark(b *testing.B, s *Service, numAtts, numValidators uint64) {
indices := make([]uint64, numValidators)
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
indices[i] = i
}
atts := make([]*slashertypes.IndexedAttestationWrapper, numAtts)
- for i := uint64(0); i < numAtts; i++ {
+ for i := range numAtts {
source := primitives.Epoch(i)
target := primitives.Epoch(i + 1)
var signingRoot [32]byte
@@ -1571,7 +1570,7 @@ func runAttestationsBenchmark(b *testing.B, s *Service, numAtts, numValidators u
signingRoot[:], /* signingRoot */
)
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
numEpochs := numAtts
totalSeconds := numEpochs * uint64(params.BeaconConfig().SlotsPerEpoch) * params.BeaconConfig().SecondsPerSlot
genesisTime := time.Now().Add(-time.Second * time.Duration(totalSeconds))
@@ -1623,7 +1622,7 @@ func Benchmark_checkSurroundVotes(b *testing.B) {
attestingValidatorsCount := validatorsCount / slotsPerEpoch
validatorIndexes := make([]uint64, attestingValidatorsCount)
- for i := 0; i < attestingValidatorsCount; i++ {
+ for i := range attestingValidatorsCount {
validatorIndexes[i] = 32 * uint64(i)
}
@@ -1633,8 +1632,8 @@ func Benchmark_checkSurroundVotes(b *testing.B) {
attWrappers := []*slashertypes.IndexedAttestationWrapper{attWrapper}
// Run the benchmark.
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
b.StartTimer()
_, err = service.checkSurroundVotes(ctx, attWrappers, currentEpoch)
b.StopTimer()
diff --git a/beacon-chain/slasher/service.go b/beacon-chain/slasher/service.go
index 870fcb5c37..f6b2cc426a 100644
--- a/beacon-chain/slasher/service.go
+++ b/beacon-chain/slasher/service.go
@@ -99,7 +99,7 @@ func (s *Service) run() {
}
numVals := headState.NumValidators()
validatorIndices := make([]primitives.ValidatorIndex, numVals)
- for i := 0; i < numVals; i++ {
+ for i := range numVals {
validatorIndices[i] = primitives.ValidatorIndex(i)
}
start := time.Now()
diff --git a/beacon-chain/state/fieldtrie/field_trie.go b/beacon-chain/state/fieldtrie/field_trie.go
index abcb25b5cc..953f49eae2 100644
--- a/beacon-chain/state/fieldtrie/field_trie.go
+++ b/beacon-chain/state/fieldtrie/field_trie.go
@@ -40,7 +40,7 @@ type FieldTrie struct {
// NewFieldTrie is the constructor for the field trie data structure. It creates the corresponding
// trie according to the given parameters. Depending on whether the field is a basic/composite array
// which is either fixed/variable length, it will appropriately determine the trie.
-func NewFieldTrie(field types.FieldIndex, fieldInfo types.DataType, elements interface{}, length uint64) (*FieldTrie, error) {
+func NewFieldTrie(field types.FieldIndex, fieldInfo types.DataType, elements any, length uint64) (*FieldTrie, error) {
if elements == nil {
return &FieldTrie{
field: field,
@@ -92,14 +92,14 @@ func NewFieldTrie(field types.FieldIndex, fieldInfo types.DataType, elements int
numOfElems: numOfElems,
}, nil
default:
- return nil, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeOf(fieldInfo).Name())
+ return nil, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeFor[types.DataType]().Name())
}
}
// RecomputeTrie rebuilds the affected branches in the trie according to the provided
// changed indices and elements. This recomputes the trie according to the particular
// field the trie is based on.
-func (f *FieldTrie) RecomputeTrie(indices []uint64, elements interface{}) ([32]byte, error) {
+func (f *FieldTrie) RecomputeTrie(indices []uint64, elements any) ([32]byte, error) {
f.Lock()
defer f.Unlock()
var fieldRoot [32]byte
@@ -162,7 +162,7 @@ func (f *FieldTrie) RecomputeTrie(indices []uint64, elements interface{}) ([32]b
}
return stateutil.AddInMixin(fieldRoot, uint64(f.numOfElems))
default:
- return [32]byte{}, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeOf(f.dataType).Name())
+ return [32]byte{}, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeFor[types.DataType]().Name())
}
}
@@ -251,7 +251,7 @@ func (f *FieldTrie) TrieRoot() ([32]byte, error) {
trieRoot := *f.fieldLayers[len(f.fieldLayers)-1][0]
return stateutil.AddInMixin(trieRoot, uint64(f.numOfElems))
default:
- return [32]byte{}, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeOf(f.dataType).Name())
+ return [32]byte{}, errors.Errorf("unrecognized data type in field map: %v", reflect.TypeFor[types.DataType]().Name())
}
}
diff --git a/beacon-chain/state/fieldtrie/field_trie_helpers.go b/beacon-chain/state/fieldtrie/field_trie_helpers.go
index 07667eb0eb..dad12a85da 100644
--- a/beacon-chain/state/fieldtrie/field_trie_helpers.go
+++ b/beacon-chain/state/fieldtrie/field_trie_helpers.go
@@ -31,7 +31,7 @@ func (f *FieldTrie) validateIndices(idxs []uint64) error {
return nil
}
-func validateElements(field types.FieldIndex, fieldInfo types.DataType, elements interface{}, length uint64) error {
+func validateElements(field types.FieldIndex, fieldInfo types.DataType, elements any, length uint64) error {
if fieldInfo == types.CompressedArray {
comLength, err := field.ElemsInChunk()
if err != nil {
@@ -54,7 +54,7 @@ func validateElements(field types.FieldIndex, fieldInfo types.DataType, elements
}
// fieldConverters converts the corresponding field and the provided elements to the appropriate roots.
-func fieldConverters(field types.FieldIndex, indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) {
+func fieldConverters(field types.FieldIndex, indices []uint64, elements any, convertAll bool) ([][32]byte, error) {
switch field {
case types.BlockRoots, types.StateRoots, types.RandaoMixes:
return convertRoots(indices, elements, convertAll)
@@ -71,7 +71,7 @@ func fieldConverters(field types.FieldIndex, indices []uint64, elements interfac
}
}
-func convertRoots(indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) {
+func convertRoots(indices []uint64, elements any, convertAll bool) ([][32]byte, error) {
switch castedType := elements.(type) {
case customtypes.BlockRoots:
return handle32ByteMVslice(multi_value_slice.BuildEmptyCompositeSlice[[32]byte](castedType), indices, convertAll)
@@ -86,7 +86,7 @@ func convertRoots(indices []uint64, elements interface{}, convertAll bool) ([][3
}
}
-func convertEth1DataVotes(indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) {
+func convertEth1DataVotes(indices []uint64, elements any, convertAll bool) ([][32]byte, error) {
val, ok := elements.([]*ethpb.Eth1Data)
if !ok {
return nil, errors.Errorf("Wanted type of %T but got %T", []*ethpb.Eth1Data{}, elements)
@@ -94,7 +94,7 @@ func convertEth1DataVotes(indices []uint64, elements interface{}, convertAll boo
return handleEth1DataSlice(val, indices, convertAll)
}
-func convertValidators(indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) {
+func convertValidators(indices []uint64, elements any, convertAll bool) ([][32]byte, error) {
switch casted := elements.(type) {
case []*ethpb.Validator:
return handleValidatorMVSlice(multi_value_slice.BuildEmptyCompositeSlice[*ethpb.Validator](casted), indices, convertAll)
@@ -105,7 +105,7 @@ func convertValidators(indices []uint64, elements interface{}, convertAll bool)
}
}
-func convertAttestations(indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) {
+func convertAttestations(indices []uint64, elements any, convertAll bool) ([][32]byte, error) {
val, ok := elements.([]*ethpb.PendingAttestation)
if !ok {
return nil, errors.Errorf("Wanted type of %T but got %T", []*ethpb.PendingAttestation{}, elements)
@@ -113,7 +113,7 @@ func convertAttestations(indices []uint64, elements interface{}, convertAll bool
return handlePendingAttestationSlice(val, indices, convertAll)
}
-func convertBalances(indices []uint64, elements interface{}, convertAll bool) ([][32]byte, error) {
+func convertBalances(indices []uint64, elements any, convertAll bool) ([][32]byte, error) {
switch casted := elements.(type) {
case []uint64:
return handleBalanceMVSlice(multi_value_slice.BuildEmptyCompositeSlice[uint64](casted), indices, convertAll)
diff --git a/beacon-chain/state/fieldtrie/helpers_test.go b/beacon-chain/state/fieldtrie/helpers_test.go
index 3ba766e9dd..0a97f3c4b3 100644
--- a/beacon-chain/state/fieldtrie/helpers_test.go
+++ b/beacon-chain/state/fieldtrie/helpers_test.go
@@ -94,7 +94,7 @@ func TestFieldTrie_NativeState_fieldConvertersNative(t *testing.T) {
type args struct {
field types.FieldIndex
indices []uint64
- elements interface{}
+ elements any
convertAll bool
}
tests := []struct {
diff --git a/beacon-chain/state/interfaces.go b/beacon-chain/state/interfaces.go
index 51511fe9f5..c884a92d50 100644
--- a/beacon-chain/state/interfaces.go
+++ b/beacon-chain/state/interfaces.go
@@ -63,8 +63,8 @@ type ReadOnlyBeaconState interface {
ReadOnlyDeposits
ReadOnlyConsolidations
ReadOnlyProposerLookahead
- ToProtoUnsafe() interface{}
- ToProto() interface{}
+ ToProtoUnsafe() any
+ ToProto() any
GenesisTime() time.Time
GenesisValidatorsRoot() []byte
Slot() primitives.Slot
diff --git a/beacon-chain/state/state-native/getters_attestation.go b/beacon-chain/state/state-native/getters_attestation.go
index ee3a24e2ca..15a01a99fd 100644
--- a/beacon-chain/state/state-native/getters_attestation.go
+++ b/beacon-chain/state/state-native/getters_attestation.go
@@ -29,7 +29,7 @@ func (b *BeaconState) previousEpochAttestationsVal() []*ethpb.PendingAttestation
}
res := make([]*ethpb.PendingAttestation, len(b.previousEpochAttestations))
- for i := 0; i < len(res); i++ {
+ for i := range res {
res[i] = b.previousEpochAttestations[i].Copy()
}
return res
@@ -59,7 +59,7 @@ func (b *BeaconState) currentEpochAttestationsVal() []*ethpb.PendingAttestation
}
res := make([]*ethpb.PendingAttestation, len(b.currentEpochAttestations))
- for i := 0; i < len(res); i++ {
+ for i := range res {
res[i] = b.currentEpochAttestations[i].Copy()
}
return res
diff --git a/beacon-chain/state/state-native/getters_eth1.go b/beacon-chain/state/state-native/getters_eth1.go
index 6abf95bc28..9884597805 100644
--- a/beacon-chain/state/state-native/getters_eth1.go
+++ b/beacon-chain/state/state-native/getters_eth1.go
@@ -48,7 +48,7 @@ func (b *BeaconState) eth1DataVotesVal() []*ethpb.Eth1Data {
}
res := make([]*ethpb.Eth1Data, len(b.eth1DataVotes))
- for i := 0; i < len(res); i++ {
+ for i := range res {
res[i] = b.eth1DataVotes[i].Copy()
}
return res
diff --git a/beacon-chain/state/state-native/getters_participation_test.go b/beacon-chain/state/state-native/getters_participation_test.go
index 7e18caad65..96098de404 100644
--- a/beacon-chain/state/state-native/getters_participation_test.go
+++ b/beacon-chain/state/state-native/getters_participation_test.go
@@ -11,7 +11,7 @@ import (
func TestState_UnrealizedCheckpointBalances(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().MinGenesisActiveValidatorCount)
balances := make([]uint64, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
diff --git a/beacon-chain/state/state-native/getters_state.go b/beacon-chain/state/state-native/getters_state.go
index bb00922978..86a503fdc2 100644
--- a/beacon-chain/state/state-native/getters_state.go
+++ b/beacon-chain/state/state-native/getters_state.go
@@ -9,7 +9,7 @@ import (
// ToProtoUnsafe returns the pointer value of the underlying
// beacon state proto object, bypassing immutability. Use with care.
-func (b *BeaconState) ToProtoUnsafe() interface{} {
+func (b *BeaconState) ToProtoUnsafe() any {
if b == nil {
return nil
}
@@ -265,7 +265,7 @@ func (b *BeaconState) ToProtoUnsafe() interface{} {
}
// ToProto the beacon state into a protobuf for usage.
-func (b *BeaconState) ToProto() interface{} {
+func (b *BeaconState) ToProto() any {
if b == nil {
return nil
}
@@ -552,7 +552,7 @@ func (b *BeaconState) StateRootAtIndex(idx uint64) ([]byte, error) {
// ProtobufBeaconStatePhase0 transforms an input into beacon state in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStatePhase0(s interface{}) (*ethpb.BeaconState, error) {
+func ProtobufBeaconStatePhase0(s any) (*ethpb.BeaconState, error) {
pbState, ok := s.(*ethpb.BeaconState)
if !ok {
return nil, errors.New("input is not type ethpb.BeaconState")
@@ -562,7 +562,7 @@ func ProtobufBeaconStatePhase0(s interface{}) (*ethpb.BeaconState, error) {
// ProtobufBeaconStateAltair transforms an input into beacon state Altair in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStateAltair(s interface{}) (*ethpb.BeaconStateAltair, error) {
+func ProtobufBeaconStateAltair(s any) (*ethpb.BeaconStateAltair, error) {
pbState, ok := s.(*ethpb.BeaconStateAltair)
if !ok {
return nil, errors.New("input is not type pb.BeaconStateAltair")
@@ -572,7 +572,7 @@ func ProtobufBeaconStateAltair(s interface{}) (*ethpb.BeaconStateAltair, error)
// ProtobufBeaconStateBellatrix transforms an input into beacon state Bellatrix in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStateBellatrix(s interface{}) (*ethpb.BeaconStateBellatrix, error) {
+func ProtobufBeaconStateBellatrix(s any) (*ethpb.BeaconStateBellatrix, error) {
pbState, ok := s.(*ethpb.BeaconStateBellatrix)
if !ok {
return nil, errors.New("input is not type pb.BeaconStateBellatrix")
@@ -582,7 +582,7 @@ func ProtobufBeaconStateBellatrix(s interface{}) (*ethpb.BeaconStateBellatrix, e
// ProtobufBeaconStateCapella transforms an input into beacon state Capella in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStateCapella(s interface{}) (*ethpb.BeaconStateCapella, error) {
+func ProtobufBeaconStateCapella(s any) (*ethpb.BeaconStateCapella, error) {
pbState, ok := s.(*ethpb.BeaconStateCapella)
if !ok {
return nil, errors.New("input is not type pb.BeaconStateCapella")
@@ -592,7 +592,7 @@ func ProtobufBeaconStateCapella(s interface{}) (*ethpb.BeaconStateCapella, error
// ProtobufBeaconStateDeneb transforms an input into beacon state Deneb in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStateDeneb(s interface{}) (*ethpb.BeaconStateDeneb, error) {
+func ProtobufBeaconStateDeneb(s any) (*ethpb.BeaconStateDeneb, error) {
pbState, ok := s.(*ethpb.BeaconStateDeneb)
if !ok {
return nil, errors.New("input is not type pb.BeaconStateDeneb")
@@ -602,7 +602,7 @@ func ProtobufBeaconStateDeneb(s interface{}) (*ethpb.BeaconStateDeneb, error) {
// ProtobufBeaconStateElectra transforms an input into beacon state Electra in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStateElectra(s interface{}) (*ethpb.BeaconStateElectra, error) {
+func ProtobufBeaconStateElectra(s any) (*ethpb.BeaconStateElectra, error) {
pbState, ok := s.(*ethpb.BeaconStateElectra)
if !ok {
return nil, errors.New("input is not type pb.BeaconStateElectra")
@@ -612,7 +612,7 @@ func ProtobufBeaconStateElectra(s interface{}) (*ethpb.BeaconStateElectra, error
// ProtobufBeaconStateFulu transforms an input into beacon state Fulu in the form of protobuf.
// Error is returned if the input is not type protobuf beacon state.
-func ProtobufBeaconStateFulu(s interface{}) (*ethpb.BeaconStateFulu, error) {
+func ProtobufBeaconStateFulu(s any) (*ethpb.BeaconStateFulu, error) {
pbState, ok := s.(*ethpb.BeaconStateFulu)
if !ok {
return nil, errors.New("input is not type pb.BeaconStateFulu")
diff --git a/beacon-chain/state/state-native/getters_validator.go b/beacon-chain/state/state-native/getters_validator.go
index 037418e167..5029887059 100644
--- a/beacon-chain/state/state-native/getters_validator.go
+++ b/beacon-chain/state/state-native/getters_validator.go
@@ -34,7 +34,7 @@ func (b *BeaconState) validatorsVal() []*ethpb.Validator {
v = b.validatorsMultiValue.Value(b)
res := make([]*ethpb.Validator, len(v))
- for i := 0; i < len(res); i++ {
+ for i := range res {
val := v[i]
if val == nil {
continue
@@ -52,7 +52,7 @@ func (b *BeaconState) validatorsReadOnlyVal() []state.ReadOnlyValidator {
res := make([]state.ReadOnlyValidator, len(v))
var err error
- for i := 0; i < len(res); i++ {
+ for i := range res {
val := v[i]
if val == nil {
continue
@@ -172,7 +172,7 @@ func (b *BeaconState) PublicKeys() ([][fieldparams.BLSPubkeyLength]byte, error)
l := b.validatorsLen()
res := make([][fieldparams.BLSPubkeyLength]byte, l)
- for i := 0; i < l; i++ {
+ for i := range l {
val, err := b.validatorsMultiValue.At(b, uint64(i))
if err != nil {
return nil, err
@@ -201,7 +201,7 @@ func (b *BeaconState) ReadFromEveryValidator(f func(idx int, val state.ReadOnlyV
return state.ErrNilValidatorsInState
}
l := b.validatorsMultiValue.Len(b)
- for i := 0; i < l; i++ {
+ for i := range l {
v, err := b.validatorsMultiValue.At(b, uint64(i))
if err != nil {
return err
diff --git a/beacon-chain/state/state-native/getters_withdrawal.go b/beacon-chain/state/state-native/getters_withdrawal.go
index 3b6ea2a3d0..fad6beeafd 100644
--- a/beacon-chain/state/state-native/getters_withdrawal.go
+++ b/beacon-chain/state/state-native/getters_withdrawal.go
@@ -161,7 +161,7 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, err
validatorsLen := b.validatorsLen()
bound := min(uint64(validatorsLen), params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
- for i := uint64(0); i < bound; i++ {
+ for range bound {
val, err := b.validatorAtIndexReadOnly(validatorIndex)
if err != nil {
return nil, 0, errors.Wrapf(err, "could not retrieve validator at index %d", validatorIndex)
diff --git a/beacon-chain/state/state-native/getters_withdrawal_test.go b/beacon-chain/state/state-native/getters_withdrawal_test.go
index 4154504dc2..b2f64c702c 100644
--- a/beacon-chain/state/state-native/getters_withdrawal_test.go
+++ b/beacon-chain/state/state-native/getters_withdrawal_test.go
@@ -92,7 +92,7 @@ func TestExpectedWithdrawals(t *testing.T) {
vals := make([]*ethpb.Validator, 100)
balances := make([]uint64, 100)
- for i := 0; i < 100; i++ {
+ for i := range 100 {
balances[i] = params.BeaconConfig().MaxEffectiveBalance
val := ðpb.Validator{
WithdrawalCredentials: make([]byte, 32),
@@ -124,7 +124,7 @@ func TestExpectedWithdrawals(t *testing.T) {
vals := make([]*ethpb.Validator, 100)
balances := make([]uint64, 100)
- for i := 0; i < 100; i++ {
+ for i := range 100 {
balances[i] = params.BeaconConfig().MaxEffectiveBalance
val := ðpb.Validator{
WithdrawalCredentials: make([]byte, 32),
diff --git a/beacon-chain/state/state-native/setters_attestation_test.go b/beacon-chain/state/state-native/setters_attestation_test.go
index ee16f0c622..d591dda47a 100644
--- a/beacon-chain/state/state-native/setters_attestation_test.go
+++ b/beacon-chain/state/state-native/setters_attestation_test.go
@@ -29,16 +29,16 @@ func TestBeaconState_RotateAttestations(t *testing.T) {
func TestAppendBeyondIndicesLimit(t *testing.T) {
zeroHash := params.BeaconConfig().ZeroHash
mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(mockblockRoots); i++ {
+ for i := range mockblockRoots {
mockblockRoots[i] = zeroHash[:]
}
mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(mockstateRoots); i++ {
+ for i := range mockstateRoots {
mockstateRoots[i] = zeroHash[:]
}
mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(mockrandaoMixes); i++ {
+ for i := range mockrandaoMixes {
mockrandaoMixes[i] = zeroHash[:]
}
st, err := InitializeFromProtoPhase0(ðpb.BeaconState{
@@ -61,13 +61,13 @@ func TestAppendBeyondIndicesLimit(t *testing.T) {
}
_, err = st.HashTreeRoot(t.Context())
require.NoError(t, err)
- for i := 0; i < 10; i++ {
+ for range 10 {
assert.NoError(t, st.AppendValidator(ðpb.Validator{}))
}
assert.Equal(t, false, s.rebuildTrie[types.Validators])
assert.NotEqual(t, len(s.dirtyIndices[types.Validators]), 0)
- for i := 0; i < indicesLimit; i++ {
+ for range indicesLimit {
assert.NoError(t, st.AppendValidator(ðpb.Validator{}))
}
assert.Equal(t, true, s.rebuildTrie[types.Validators])
@@ -88,10 +88,8 @@ func BenchmarkAppendPreviousEpochAttestations(b *testing.B) {
require.NoError(b, err)
}
- b.ResetTimer()
-
ref := st.Copy()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
err := ref.AppendPreviousEpochAttestations(ðpb.PendingAttestation{Data: ðpb.AttestationData{Slot: primitives.Slot(i)}})
require.NoError(b, err)
ref = st.Copy()
diff --git a/beacon-chain/state/state-native/setters_eth1_test.go b/beacon-chain/state/state-native/setters_eth1_test.go
index 5908abc159..ffa8454c58 100644
--- a/beacon-chain/state/state-native/setters_eth1_test.go
+++ b/beacon-chain/state/state-native/setters_eth1_test.go
@@ -30,7 +30,7 @@ func BenchmarkAppendEth1DataVotes(b *testing.B) {
ref := st.Copy()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
err := ref.AppendEth1DataVotes(ðpb.Eth1Data{DepositCount: uint64(i)})
require.NoError(b, err)
ref = st.Copy()
diff --git a/beacon-chain/state/state-native/setters_misc_test.go b/beacon-chain/state/state-native/setters_misc_test.go
index 594c4a295b..ffda120523 100644
--- a/beacon-chain/state/state-native/setters_misc_test.go
+++ b/beacon-chain/state/state-native/setters_misc_test.go
@@ -27,9 +27,7 @@ func BenchmarkAppendHistoricalRoots(b *testing.B) {
ref := st.Copy()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
err := ref.AppendHistoricalRoots(root)
require.NoError(b, err)
ref = st.Copy()
@@ -52,9 +50,7 @@ func BenchmarkAppendHistoricalSummaries(b *testing.B) {
ref := st.Copy()
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
err := ref.AppendHistoricalSummaries(ðpb.HistoricalSummary{})
require.NoError(b, err)
ref = st.Copy()
diff --git a/beacon-chain/state/state-native/setters_participation_test.go b/beacon-chain/state/state-native/setters_participation_test.go
index 3d51816cab..4b65ac26b4 100644
--- a/beacon-chain/state/state-native/setters_participation_test.go
+++ b/beacon-chain/state/state-native/setters_participation_test.go
@@ -18,9 +18,8 @@ func BenchmarkParticipationBits(b *testing.B) {
}
ref := st.Copy()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.NoError(b, ref.AppendCurrentParticipationBits(byte(2)))
ref = st.Copy()
}
diff --git a/beacon-chain/state/state-native/setters_payload_header_test.go b/beacon-chain/state/state-native/setters_payload_header_test.go
index 51d552e42b..4fe4ac6cd2 100644
--- a/beacon-chain/state/state-native/setters_payload_header_test.go
+++ b/beacon-chain/state/state-native/setters_payload_header_test.go
@@ -88,8 +88,8 @@ func TestSetLatestExecutionPayloadHeader(t *testing.T) {
t.Run("mismatched type version returns error", func(t *testing.T) {
require.Equal(t, len(payloads), len(payloadHeaders), "This test will fail if the payloads and payload headers are not same length")
- for i := 0; i < len(payloads); i++ {
- for j := 0; j < len(payloads); j++ {
+ for i := range payloads {
+ for j := range payloads {
if i == j {
continue
}
diff --git a/beacon-chain/state/state-native/setters_validator.go b/beacon-chain/state/state-native/setters_validator.go
index 70d10e23e2..01f79bb7fd 100644
--- a/beacon-chain/state/state-native/setters_validator.go
+++ b/beacon-chain/state/state-native/setters_validator.go
@@ -33,7 +33,7 @@ func (b *BeaconState) SetValidators(val []*ethpb.Validator) error {
func (b *BeaconState) ApplyToEveryValidator(f func(idx int, val state.ReadOnlyValidator) (*ethpb.Validator, error)) error {
var changedVals []uint64
l := b.validatorsMultiValue.Len(b)
- for i := 0; i < l; i++ {
+ for i := range l {
v, err := b.validatorsMultiValue.At(b, uint64(i))
if err != nil {
return err
diff --git a/beacon-chain/state/state-native/setters_validator_test.go b/beacon-chain/state/state-native/setters_validator_test.go
index 83b19766cd..3d8b598361 100644
--- a/beacon-chain/state/state-native/setters_validator_test.go
+++ b/beacon-chain/state/state-native/setters_validator_test.go
@@ -18,9 +18,8 @@ func BenchmarkAppendBalance(b *testing.B) {
}
ref := st.Copy()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
require.NoError(b, ref.AppendBalance(uint64(i)))
ref = st.Copy()
}
@@ -36,9 +35,8 @@ func BenchmarkAppendInactivityScore(b *testing.B) {
}
ref := st.Copy()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
require.NoError(b, ref.AppendInactivityScore(uint64(i)))
ref = st.Copy()
}
diff --git a/beacon-chain/state/state-native/state_test.go b/beacon-chain/state/state-native/state_test.go
index 56f1f4a632..e6e2626674 100644
--- a/beacon-chain/state/state-native/state_test.go
+++ b/beacon-chain/state/state-native/state_test.go
@@ -47,11 +47,10 @@ func TestBeaconState_NoDeadlock_Phase0(t *testing.T) {
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
// Continuously lock and unlock the state
// by acquiring the lock.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
for _, f := range st.stateFieldLeaves {
f.Lock()
if f.Empty() {
@@ -61,12 +60,11 @@ func TestBeaconState_NoDeadlock_Phase0(t *testing.T) {
f.FieldReference().AddRef()
}
}
- wg.Done()
- }()
+ })
// Constantly read from the offending portion
// of the code to ensure there is no possible
// recursive read locking.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
go func() {
_ = st.FieldReferencesCount()
}()
@@ -103,11 +101,10 @@ func TestBeaconState_NoDeadlock_Altair(t *testing.T) {
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
// Continuously lock and unlock the state
// by acquiring the lock.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
for _, f := range s.stateFieldLeaves {
f.Lock()
if f.Empty() {
@@ -117,12 +114,11 @@ func TestBeaconState_NoDeadlock_Altair(t *testing.T) {
f.FieldReference().AddRef()
}
}
- wg.Done()
- }()
+ })
// Constantly read from the offending portion
// of the code to ensure there is no possible
// recursive read locking.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
go func() {
_ = st.FieldReferencesCount()
}()
@@ -159,11 +155,10 @@ func TestBeaconState_NoDeadlock_Bellatrix(t *testing.T) {
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
// Continuously lock and unlock the state
// by acquiring the lock.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
for _, f := range s.stateFieldLeaves {
f.Lock()
if f.Empty() {
@@ -173,12 +168,11 @@ func TestBeaconState_NoDeadlock_Bellatrix(t *testing.T) {
f.FieldReference().AddRef()
}
}
- wg.Done()
- }()
+ })
// Constantly read from the offending portion
// of the code to ensure there is no possible
// recursive read locking.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
go func() {
_ = st.FieldReferencesCount()
}()
@@ -215,11 +209,10 @@ func TestBeaconState_NoDeadlock_Capella(t *testing.T) {
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
// Continuously lock and unlock the state
// by acquiring the lock.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
for _, f := range s.stateFieldLeaves {
f.Lock()
if f.Empty() {
@@ -229,12 +222,11 @@ func TestBeaconState_NoDeadlock_Capella(t *testing.T) {
f.FieldReference().AddRef()
}
}
- wg.Done()
- }()
+ })
// Constantly read from the offending portion
// of the code to ensure there is no possible
// recursive read locking.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
go func() {
_ = st.FieldReferencesCount()
}()
@@ -271,11 +263,10 @@ func TestBeaconState_NoDeadlock_Deneb(t *testing.T) {
wg := new(sync.WaitGroup)
- wg.Add(1)
- go func() {
+ wg.Go(func() {
// Continuously lock and unlock the state
// by acquiring the lock.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
for _, f := range s.stateFieldLeaves {
f.Lock()
if f.Empty() {
@@ -285,12 +276,11 @@ func TestBeaconState_NoDeadlock_Deneb(t *testing.T) {
f.FieldReference().AddRef()
}
}
- wg.Done()
- }()
+ })
// Constantly read from the offending portion
// of the code to ensure there is no possible
// recursive read locking.
- for i := 0; i < 1000; i++ {
+ for range 1000 {
go func() {
_ = st.FieldReferencesCount()
}()
@@ -307,7 +297,7 @@ func TestBeaconState_AppendBalanceWithTrie(t *testing.T) {
_, err := st.HashTreeRoot(t.Context())
assert.NoError(t, err)
- for i := 0; i < 100; i++ {
+ for i := range 100 {
if i%2 == 0 {
assert.NoError(t, st.UpdateBalancesAtIndex(primitives.ValidatorIndex(i), 1000))
}
@@ -392,7 +382,7 @@ func TestDuplicateDirtyIndices(t *testing.T) {
rebuildTrie: make(map[types.FieldIndex]bool),
dirtyIndices: make(map[types.FieldIndex][]uint64),
}
- for i := uint64(0); i < indicesLimit-5; i++ {
+ for i := range uint64(indicesLimit - 5) {
newState.dirtyIndices[types.Balances] = append(newState.dirtyIndices[types.Balances], i)
}
// Append duplicates
@@ -430,16 +420,16 @@ func generateState(t *testing.T) state.BeaconState {
}
zeroHash := params.BeaconConfig().ZeroHash
mockblockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(mockblockRoots); i++ {
+ for i := range mockblockRoots {
mockblockRoots[i] = zeroHash[:]
}
mockstateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(mockstateRoots); i++ {
+ for i := range mockstateRoots {
mockstateRoots[i] = zeroHash[:]
}
mockrandaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(mockrandaoMixes); i++ {
+ for i := range mockrandaoMixes {
mockrandaoMixes[i] = zeroHash[:]
}
newState, err := InitializeFromProtoPhase0(ðpb.BeaconState{
diff --git a/beacon-chain/state/state-native/state_trie.go b/beacon-chain/state/state-native/state_trie.go
index b4e6011fd2..d9eca3866e 100644
--- a/beacon-chain/state/state-native/state_trie.go
+++ b/beacon-chain/state/state-native/state_trie.go
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
"runtime"
- "sort"
+ "slices"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state/fieldtrie"
@@ -1204,7 +1204,7 @@ func (b *BeaconState) CopyAllTries() {
}
}
-func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements interface{}) ([32]byte, error) {
+func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements any) ([32]byte, error) {
fTrie := b.stateFieldLeaves[index]
fTrieMutex := fTrie.RWMutex
// We can't lock the trie directly because the trie's variable gets reassigned,
@@ -1241,9 +1241,7 @@ func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements interf
// remove duplicate indexes
b.dirtyIndices[index] = slice.SetUint64(b.dirtyIndices[index])
// sort indexes again
- sort.Slice(b.dirtyIndices[index], func(i int, j int) bool {
- return b.dirtyIndices[index][i] < b.dirtyIndices[index][j]
- })
+ slices.Sort(b.dirtyIndices[index])
root, err := fTrie.RecomputeTrie(b.dirtyIndices[index], elements)
if err != nil {
return [32]byte{}, err
@@ -1252,7 +1250,7 @@ func (b *BeaconState) recomputeFieldTrie(index types.FieldIndex, elements interf
return root, nil
}
-func (b *BeaconState) resetFieldTrie(index types.FieldIndex, elements interface{}, length uint64) error {
+func (b *BeaconState) resetFieldTrie(index types.FieldIndex, elements any, length uint64) error {
fTrie, err := fieldtrie.NewFieldTrie(index, fieldMap[index], elements, length)
if err != nil {
return err
diff --git a/beacon-chain/state/state-native/types_test.go b/beacon-chain/state/state-native/types_test.go
index a749c8455c..8ead7cbe49 100644
--- a/beacon-chain/state/state-native/types_test.go
+++ b/beacon-chain/state/state-native/types_test.go
@@ -74,11 +74,11 @@ func setupGenesisState(t testing.TB, count uint64) *ethpb.BeaconState {
}
func BenchmarkCloneValidators_Proto(b *testing.B) {
- b.StopTimer()
+
validators := make([]*ethpb.Validator, 16384)
somePubKey := [fieldparams.BLSPubkeyLength]byte{1, 2, 3}
someRoot := [32]byte{3, 4, 5}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: somePubKey[:],
WithdrawalCredentials: someRoot[:],
@@ -90,18 +90,18 @@ func BenchmarkCloneValidators_Proto(b *testing.B) {
WithdrawableEpoch: 5,
}
}
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
cloneValidatorsWithProto(validators)
}
}
func BenchmarkCloneValidators_Manual(b *testing.B) {
- b.StopTimer()
+
validators := make([]*ethpb.Validator, 16384)
somePubKey := [fieldparams.BLSPubkeyLength]byte{1, 2, 3}
someRoot := [32]byte{3, 4, 5}
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
PublicKey: somePubKey[:],
WithdrawalCredentials: someRoot[:],
@@ -113,33 +113,33 @@ func BenchmarkCloneValidators_Manual(b *testing.B) {
WithdrawableEpoch: 5,
}
}
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
cloneValidatorsManually(validators)
}
}
func BenchmarkStateClone_Proto(b *testing.B) {
- b.StopTimer()
+
params.SetupTestConfigCleanup(b)
params.OverrideBeaconConfig(params.MinimalSpecConfig())
genesis := setupGenesisState(b, 64)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, ok := proto.Clone(genesis).(*ethpb.BeaconState)
assert.Equal(b, true, ok, "Entity is not of type *ethpb.BeaconState")
}
}
func BenchmarkStateClone_Manual(b *testing.B) {
- b.StopTimer()
+
params.SetupTestConfigCleanup(b)
params.OverrideBeaconConfig(params.MinimalSpecConfig())
genesis := setupGenesisState(b, 64)
st, err := statenative.InitializeFromProtoPhase0(genesis)
require.NoError(b, err)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_ = st.ToProto()
}
}
@@ -147,7 +147,7 @@ func BenchmarkStateClone_Manual(b *testing.B) {
func cloneValidatorsWithProto(vals []*ethpb.Validator) []*ethpb.Validator {
var ok bool
res := make([]*ethpb.Validator, len(vals))
- for i := 0; i < len(res); i++ {
+ for i := range res {
res[i], ok = proto.Clone(vals[i]).(*ethpb.Validator)
if !ok {
log.Debug("Entity is not of type *ethpb.Validator")
@@ -158,7 +158,7 @@ func cloneValidatorsWithProto(vals []*ethpb.Validator) []*ethpb.Validator {
func cloneValidatorsManually(vals []*ethpb.Validator) []*ethpb.Validator {
res := make([]*ethpb.Validator, len(vals))
- for i := 0; i < len(res); i++ {
+ for i := range res {
val := vals[i]
res[i] = ðpb.Validator{
PublicKey: val.PublicKey,
diff --git a/beacon-chain/state/stategen/epoch_boundary_state_cache.go b/beacon-chain/state/stategen/epoch_boundary_state_cache.go
index 83043152b8..c429da939b 100644
--- a/beacon-chain/state/stategen/epoch_boundary_state_cache.go
+++ b/beacon-chain/state/stategen/epoch_boundary_state_cache.go
@@ -26,7 +26,7 @@ type slotRootInfo struct {
// slotKeyFn takes the string representation of the slot to be used as key
// to retrieve root.
-func slotKeyFn(obj interface{}) (string, error) {
+func slotKeyFn(obj any) (string, error) {
s, ok := obj.(*slotRootInfo)
if !ok {
return "", errNotSlotRootInfo
@@ -42,7 +42,7 @@ type rootStateInfo struct {
// rootKeyFn takes the string representation of the block root to be used as key
// to retrieve epoch boundary state.
-func rootKeyFn(obj interface{}) (string, error) {
+func rootKeyFn(obj any) (string, error) {
s, ok := obj.(*rootStateInfo)
if !ok {
return "", errNotRootStateInfo
@@ -184,7 +184,7 @@ func trim(queue *cache.FIFO, maxSize uint64) {
}
// popProcessNoopFunc is a no-op function that never returns an error.
-func popProcessNoopFunc(_ interface{}, _ bool) error {
+func popProcessNoopFunc(_ any, _ bool) error {
return nil
}
diff --git a/beacon-chain/state/stategen/history.go b/beacon-chain/state/stategen/history.go
index e5858dd0ab..f161d43ab8 100644
--- a/beacon-chain/state/stategen/history.go
+++ b/beacon-chain/state/stategen/history.go
@@ -188,7 +188,7 @@ func (c *CanonicalHistory) ancestorChain(ctx context.Context, tail interfaces.Re
func reverseChain(c []interfaces.ReadOnlySignedBeaconBlock) {
last := len(c) - 1
swaps := (last + 1) / 2
- for i := 0; i < swaps; i++ {
+ for i := range swaps {
c[i], c[last-i] = c[last-i], c[i]
}
}
diff --git a/beacon-chain/state/stategen/history_test.go b/beacon-chain/state/stategen/history_test.go
index b586d1d472..b58c32e0c4 100644
--- a/beacon-chain/state/stategen/history_test.go
+++ b/beacon-chain/state/stategen/history_test.go
@@ -530,7 +530,7 @@ func (m *mockCanonicalChecker) IsCanonical(_ context.Context, root [32]byte) (bo
func TestReverseChain(t *testing.T) {
// test 0,1,2,3 elements to handle: zero case; single element; even number; odd number
- for i := 0; i < 4; i++ {
+ for i := range 4 {
t.Run(fmt.Sprintf("reverseChain with %d elements", i), func(t *testing.T) {
actual := mockBlocks(i, incrFwd)
expected := mockBlocks(i, incrBwd)
@@ -538,7 +538,7 @@ func TestReverseChain(t *testing.T) {
if len(actual) != len(expected) {
t.Errorf("different list lengths")
}
- for i := 0; i < len(actual); i++ {
+ for i := range actual {
sblockA, ok := actual[i].(*mock.SignedBeaconBlock)
require.Equal(t, true, ok)
blockA, ok := sblockA.BeaconBlock.(*mock.BeaconBlock)
@@ -561,7 +561,7 @@ func incrBwd(n int, c chan uint32) {
}
func incrFwd(n int, c chan uint32) {
- for i := 0; i < n; i++ {
+ for i := range n {
c <- uint32(i)
}
close(c)
diff --git a/beacon-chain/state/stategen/migrate.go b/beacon-chain/state/stategen/migrate.go
index 498caa8e50..25407bc969 100644
--- a/beacon-chain/state/stategen/migrate.go
+++ b/beacon-chain/state/stategen/migrate.go
@@ -86,7 +86,7 @@ func (s *State) MigrateToCold(ctx context.Context, fRoot [32]byte) error {
// you can just remove it from the hot state cache as it becomes redundant.
s.saveHotStateDB.lock.Lock()
roots := s.saveHotStateDB.blockRootsOfSavedStates
- for i := 0; i < len(roots); i++ {
+ for i := range roots {
if aRoot == roots[i] {
s.saveHotStateDB.blockRootsOfSavedStates = append(roots[:i], roots[i+1:]...)
// There shouldn't be duplicated roots in `blockRootsOfSavedStates`.
diff --git a/beacon-chain/state/stateutil/benchmark_test.go b/beacon-chain/state/stateutil/benchmark_test.go
index a97ea609a9..9d73381a15 100644
--- a/beacon-chain/state/stateutil/benchmark_test.go
+++ b/beacon-chain/state/stateutil/benchmark_test.go
@@ -10,7 +10,7 @@ import (
func BenchmarkMerkleize_Buffered(b *testing.B) {
roots := make([][32]byte, 8192)
- for i := 0; i < 8192; i++ {
+ for i := range 8192 {
roots[0] = [32]byte{byte(i)}
}
@@ -21,9 +21,8 @@ func BenchmarkMerkleize_Buffered(b *testing.B) {
return ssz.Merkleize(ssz.NewHasherFunc(hash.CustomSHA256Hasher()), count, limit, leafIndexer), nil
}
- b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := newMerkleize(roots, 8192, 8192)
require.NoError(b, err)
}
diff --git a/beacon-chain/state/stateutil/eth1_root.go b/beacon-chain/state/stateutil/eth1_root.go
index 4189b6d616..4ed2ee5db5 100644
--- a/beacon-chain/state/stateutil/eth1_root.go
+++ b/beacon-chain/state/stateutil/eth1_root.go
@@ -18,7 +18,7 @@ func Eth1DataRootWithHasher(eth1Data *ethpb.Eth1Data) ([32]byte, error) {
}
fieldRoots := make([][32]byte, 3)
- for i := 0; i < len(fieldRoots); i++ {
+ for i := range fieldRoots {
fieldRoots[i] = [32]byte{}
}
@@ -42,7 +42,7 @@ func Eth1DataRootWithHasher(eth1Data *ethpb.Eth1Data) ([32]byte, error) {
// Eth1DatasRoot returns the hash tree root of input `eth1Datas`.
func Eth1DatasRoot(eth1Datas []*ethpb.Eth1Data) ([32]byte, error) {
eth1VotesRoots := make([][32]byte, 0, len(eth1Datas))
- for i := 0; i < len(eth1Datas); i++ {
+ for i := range eth1Datas {
eth1, err := Eth1DataRootWithHasher(eth1Datas[i])
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not compute eth1data merkleization")
diff --git a/beacon-chain/state/stateutil/field_root_attestation.go b/beacon-chain/state/stateutil/field_root_attestation.go
index 0c26460f77..d17504b780 100644
--- a/beacon-chain/state/stateutil/field_root_attestation.go
+++ b/beacon-chain/state/stateutil/field_root_attestation.go
@@ -24,7 +24,7 @@ func EpochAttestationsRoot(atts []*ethpb.PendingAttestation) ([32]byte, error) {
}
roots := make([][32]byte, len(atts))
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
pendingRoot, err := pendingAttestationRoot(atts[i])
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not attestation merkleization")
diff --git a/beacon-chain/state/stateutil/field_root_validator.go b/beacon-chain/state/stateutil/field_root_validator.go
index b1772043ca..fbc6bd905f 100644
--- a/beacon-chain/state/stateutil/field_root_validator.go
+++ b/beacon-chain/state/stateutil/field_root_validator.go
@@ -56,7 +56,7 @@ func validatorRegistryRoot(validators []*ethpb.Validator) ([32]byte, error) {
func hashValidatorHelper(validators []*ethpb.Validator, roots [][32]byte, j int, groupSize int, wg *sync.WaitGroup) {
defer wg.Done()
- for i := 0; i < groupSize; i++ {
+ for i := range groupSize {
fRoots, err := ValidatorFieldRoots(validators[j*groupSize+i])
if err != nil {
logrus.WithError(err).Error("Could not get validator field roots")
@@ -98,7 +98,7 @@ func OptimizedValidatorRoots(validators []*ethpb.Validator) ([][32]byte, error)
// A validator's tree can represented with a depth of 3. As log2(8) = 3
// Using this property we can lay out all the individual fields of a
// validator and hash them in single level using our vectorized routine.
- for i := 0; i < validatorTreeDepth; i++ {
+ for range validatorTreeDepth {
// Overwrite input lists as we are hashing by level
// and only need the highest level to proceed.
roots = htr.VectorizedSha256(roots)
diff --git a/beacon-chain/state/stateutil/field_root_validator_test.go b/beacon-chain/state/stateutil/field_root_validator_test.go
index 7e4c1b86e6..cc2a23b3bb 100644
--- a/beacon-chain/state/stateutil/field_root_validator_test.go
+++ b/beacon-chain/state/stateutil/field_root_validator_test.go
@@ -18,7 +18,7 @@ func TestValidatorConstants(t *testing.T) {
numFields := refV.NumField()
numOfValFields := 0
- for i := 0; i < numFields; i++ {
+ for i := range numFields {
if strings.Contains(refV.Type().Field(i).Name, "state") ||
strings.Contains(refV.Type().Field(i).Name, "sizeCache") ||
strings.Contains(refV.Type().Field(i).Name, "unknownFields") {
@@ -43,13 +43,13 @@ func TestHashValidatorHelper(t *testing.T) {
}
roots := make([][32]byte, len(valList))
hashValidatorHelper(valList, roots, 2, 2, &wg)
- for i := 0; i < 4*validatorFieldRoots; i++ {
+ for i := range 4 * validatorFieldRoots {
require.Equal(t, [32]byte{}, roots[i])
}
emptyValRoots, err := ValidatorFieldRoots(v)
require.NoError(t, err)
for i := 4; i < 6; i++ {
- for j := 0; j < validatorFieldRoots; j++ {
+ for j := range validatorFieldRoots {
require.Equal(t, emptyValRoots[j], roots[i*validatorFieldRoots+j])
}
}
diff --git a/beacon-chain/state/stateutil/participation_bit_root.go b/beacon-chain/state/stateutil/participation_bit_root.go
index fc0166805e..202b5f9e72 100644
--- a/beacon-chain/state/stateutil/participation_bit_root.go
+++ b/beacon-chain/state/stateutil/participation_bit_root.go
@@ -34,12 +34,10 @@ func packParticipationBits(bytes []byte) ([][32]byte, error) {
numItems := len(bytes)
chunks := make([][32]byte, 0, numItems/32)
for i := 0; i < numItems; i += 32 {
- j := i + 32
- // We create our upper bound index of the chunk, if it is greater than numItems,
- // we set it as numItems itself.
- if j > numItems {
- j = numItems
- }
+ j := min(
+ // We create our upper bound index of the chunk, if it is greater than numItems,
+ // we set it as numItems itself.
+ i+32, numItems)
// We create chunks from the list of items based on the
// indices determined above.
var chunk [32]byte
diff --git a/beacon-chain/state/stateutil/reference_bench_test.go b/beacon-chain/state/stateutil/reference_bench_test.go
index 1dcad8e012..9aa2af5ae0 100644
--- a/beacon-chain/state/stateutil/reference_bench_test.go
+++ b/beacon-chain/state/stateutil/reference_bench_test.go
@@ -9,7 +9,7 @@ func BenchmarkReference_MinusRef(b *testing.B) {
ref := &Reference{
refs: math.MaxUint64,
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ref.MinusRef()
}
}
diff --git a/beacon-chain/state/stateutil/state_root_test.go b/beacon-chain/state/stateutil/state_root_test.go
index 04ed135884..78392023b7 100644
--- a/beacon-chain/state/stateutil/state_root_test.go
+++ b/beacon-chain/state/stateutil/state_root_test.go
@@ -15,7 +15,7 @@ import (
func TestState_FieldCount(t *testing.T) {
count := params.BeaconConfig().BeaconStateFieldCount
- typ := reflect.TypeOf(ethpb.BeaconState{})
+ typ := reflect.TypeFor[ethpb.BeaconState]()
numFields := 0
for i := 0; i < typ.NumField(); i++ {
if typ.Field(i).Name == "state" ||
@@ -29,30 +29,30 @@ func TestState_FieldCount(t *testing.T) {
}
func BenchmarkHashTreeRoot_Generic_512(b *testing.B) {
- b.StopTimer()
+
genesisState := setupGenesisState(b, 512)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := genesisState.HashTreeRoot()
require.NoError(b, err)
}
}
func BenchmarkHashTreeRoot_Generic_16384(b *testing.B) {
- b.StopTimer()
+
genesisState := setupGenesisState(b, 16384)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := genesisState.HashTreeRoot()
require.NoError(b, err)
}
}
func BenchmarkHashTreeRoot_Generic_300000(b *testing.B) {
- b.StopTimer()
+
genesisState := setupGenesisState(b, 300000)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := genesisState.HashTreeRoot()
require.NoError(b, err)
}
diff --git a/beacon-chain/state/stateutil/trie_helpers.go b/beacon-chain/state/stateutil/trie_helpers.go
index 4cd2fd5b19..5e3e25336e 100644
--- a/beacon-chain/state/stateutil/trie_helpers.go
+++ b/beacon-chain/state/stateutil/trie_helpers.go
@@ -61,7 +61,7 @@ func ReturnTrieLayerVariable(elements [][32]byte, length uint64) [][]*[32]byte {
buffer := bytes.NewBuffer([]byte{})
buffer.Grow(64)
- for i := uint8(0); i < depth; i++ {
+ for i := range depth {
layerLen := len(layers[i])
oddNodeLength := layerLen%2 == 1
if oddNodeLength {
diff --git a/beacon-chain/state/stateutil/trie_helpers_test.go b/beacon-chain/state/stateutil/trie_helpers_test.go
index 90a3642b59..8b1ed17aad 100644
--- a/beacon-chain/state/stateutil/trie_helpers_test.go
+++ b/beacon-chain/state/stateutil/trie_helpers_test.go
@@ -36,8 +36,7 @@ func BenchmarkReturnTrieLayer_NormalAlgorithm(b *testing.B) {
require.NoError(b, err)
roots := retrieveBlockRoots(newState)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
layers, err := stateutil.ReturnTrieLayer(roots, uint64(len(roots)))
assert.NoError(b, err)
newRoot := *layers[len(layers)-1][0]
@@ -51,8 +50,7 @@ func BenchmarkReturnTrieLayer_VectorizedAlgorithm(b *testing.B) {
require.NoError(b, err)
roots := retrieveBlockRoots(newState)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
layers, err := stateutil.ReturnTrieLayer(roots, uint64(len(roots)))
assert.NoError(b, err)
newRoot := *layers[len(layers)-1][0]
@@ -96,8 +94,8 @@ func BenchmarkReturnTrieLayerVariable_NormalAlgorithm(b *testing.B) {
require.NoError(b, err)
roots = append(roots, rt)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
layers := stateutil.ReturnTrieLayerVariable(roots, params.BeaconConfig().ValidatorRegistryLimit)
newRoot := *layers[len(layers)-1][0]
newRoot, err = stateutil.AddInMixin(newRoot, uint64(len(validators)))
@@ -118,8 +116,8 @@ func BenchmarkReturnTrieLayerVariable_VectorizedAlgorithm(b *testing.B) {
require.NoError(b, err)
roots = append(roots, rt)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
layers := stateutil.ReturnTrieLayerVariable(roots, params.BeaconConfig().ValidatorRegistryLimit)
newRoot := *layers[len(layers)-1][0]
newRoot, err = stateutil.AddInMixin(newRoot, uint64(len(validators)))
diff --git a/beacon-chain/state/stateutil/unrealized_justification.go b/beacon-chain/state/stateutil/unrealized_justification.go
index 9500ed739a..9e79a21991 100644
--- a/beacon-chain/state/stateutil/unrealized_justification.go
+++ b/beacon-chain/state/stateutil/unrealized_justification.go
@@ -22,7 +22,7 @@ func UnrealizedCheckpointBalances(cp, pp []byte, validators ValReader, currentEp
}
valLength := validators.Len()
- for i := 0; i < valLength; i++ {
+ for i := range valLength {
v, err := validators.At(i)
if err != nil {
return 0, 0, 0, err
diff --git a/beacon-chain/state/stateutil/unrealized_justification_test.go b/beacon-chain/state/stateutil/unrealized_justification_test.go
index dabb95452b..36d27b7675 100644
--- a/beacon-chain/state/stateutil/unrealized_justification_test.go
+++ b/beacon-chain/state/stateutil/unrealized_justification_test.go
@@ -15,7 +15,7 @@ func TestState_UnrealizedCheckpointBalances(t *testing.T) {
expectedActive := params.BeaconConfig().MinGenesisActiveValidatorCount * params.BeaconConfig().MaxEffectiveBalance
balances := make([]uint64, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
@@ -100,7 +100,7 @@ func TestState_MVSlice_UnrealizedCheckpointBalances(t *testing.T) {
expectedActive := params.BeaconConfig().MinGenesisActiveValidatorCount * params.BeaconConfig().MaxEffectiveBalance
balances := make([]uint64, params.BeaconConfig().MinGenesisActiveValidatorCount)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
diff --git a/beacon-chain/state/stateutil/validator_root_test.go b/beacon-chain/state/stateutil/validator_root_test.go
index 144f458163..34d51543e9 100644
--- a/beacon-chain/state/stateutil/validator_root_test.go
+++ b/beacon-chain/state/stateutil/validator_root_test.go
@@ -8,11 +8,11 @@ import (
func BenchmarkUint64ListRootWithRegistryLimit(b *testing.B) {
balances := make([]uint64, 100000)
- for i := 0; i < len(balances); i++ {
+ for i := range balances {
balances[i] = uint64(i)
}
b.Run("100k balances", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := stateutil.Uint64ListRootWithRegistryLimit(balances)
if err != nil {
b.Fatal(err)
diff --git a/beacon-chain/sync/backfill/batch.go b/beacon-chain/sync/backfill/batch.go
index a8874d7410..871f82790b 100644
--- a/beacon-chain/sync/backfill/batch.go
+++ b/beacon-chain/sync/backfill/batch.go
@@ -78,7 +78,7 @@ type batch struct {
}
func (b batch) logFields() logrus.Fields {
- f := map[string]interface{}{
+ f := map[string]any{
"batchId": b.id(),
"state": b.state.String(),
"scheduled": b.scheduled.String(),
diff --git a/beacon-chain/sync/backfill/batcher_test.go b/beacon-chain/sync/backfill/batcher_test.go
index 52667dfb31..1f0670ecdf 100644
--- a/beacon-chain/sync/backfill/batcher_test.go
+++ b/beacon-chain/sync/backfill/batcher_test.go
@@ -112,7 +112,7 @@ func TestBatchSequencer(t *testing.T) {
}
got, err := seq.sequence()
require.Equal(t, seqLen, len(got))
- for i := 0; i < seqLen; i++ {
+ for i := range seqLen {
g := got[i]
exp := expected[i]
require.NoError(t, err)
diff --git a/beacon-chain/sync/backfill/blobs_test.go b/beacon-chain/sync/backfill/blobs_test.go
index 19eab84ebb..a0022d51b0 100644
--- a/beacon-chain/sync/backfill/blobs_test.go
+++ b/beacon-chain/sync/backfill/blobs_test.go
@@ -16,7 +16,7 @@ import (
func testBlobGen(t *testing.T, start primitives.Slot, n int) ([]blocks.ROBlock, [][]blocks.ROBlob) {
blks := make([]blocks.ROBlock, n)
blobs := make([][]blocks.ROBlob, n)
- for i := 0; i < n; i++ {
+ for i := range n {
bk, bl := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, start+primitives.Slot(i), 3)
blks[i] = bk
blobs[i] = bl
diff --git a/beacon-chain/sync/backfill/pool.go b/beacon-chain/sync/backfill/pool.go
index 95bdb6b757..477ef466b2 100644
--- a/beacon-chain/sync/backfill/pool.go
+++ b/beacon-chain/sync/backfill/pool.go
@@ -65,7 +65,7 @@ func newP2PBatchWorkerPool(p p2p.P2P, maxBatches int) *p2pBatchWorkerPool {
func (p *p2pBatchWorkerPool) spawn(ctx context.Context, n int, c *startup.Clock, a PeerAssigner, v *verifier, cm sync.ContextByteVersions, nbv verification.NewBlobVerifier, bfs *filesystem.BlobStorage) {
p.ctx, p.cancel = context.WithCancel(ctx)
go p.batchRouter(a)
- for i := 0; i < n; i++ {
+ for i := range n {
go p.newWorker(workerId(i), p.toWorkers, p.fromWorkers, c, v, cm, nbv, bfs).run(p.ctx)
}
}
diff --git a/beacon-chain/sync/backfill/pool_test.go b/beacon-chain/sync/backfill/pool_test.go
index 54c184376f..ae2b131155 100644
--- a/beacon-chain/sync/backfill/pool_test.go
+++ b/beacon-chain/sync/backfill/pool_test.go
@@ -57,7 +57,7 @@ func TestPoolDetectAllEnded(t *testing.T) {
br := batcher{min: 10, size: 10}
endSeq := br.before(0)
require.Equal(t, batchEndSequence, endSeq.state)
- for i := 0; i < nw; i++ {
+ for range nw {
pool.todo(endSeq)
}
b, err := pool.complete()
diff --git a/beacon-chain/sync/backfill/service.go b/beacon-chain/sync/backfill/service.go
index 2229cc908d..18d4c1ebde 100644
--- a/beacon-chain/sync/backfill/service.go
+++ b/beacon-chain/sync/backfill/service.go
@@ -367,10 +367,7 @@ func (*Service) Status() error {
// minimumBackfillSlot determines the lowest slot that backfill needs to download based on looking back
// MIN_EPOCHS_FOR_BLOCK_REQUESTS from the current slot.
func minimumBackfillSlot(current primitives.Slot) primitives.Slot {
- oe := primitives.Epoch(params.BeaconConfig().MinEpochsForBlockRequests)
- if oe > slots.MaxSafeEpoch() {
- oe = slots.MaxSafeEpoch()
- }
+ oe := min(primitives.Epoch(params.BeaconConfig().MinEpochsForBlockRequests), slots.MaxSafeEpoch())
offset := slots.UnsafeEpochStart(oe)
if offset >= current {
// Slot 0 is the genesis block, therefore the signature in it is invalid.
diff --git a/beacon-chain/sync/backfill/service_test.go b/beacon-chain/sync/backfill/service_test.go
index 9636b76daa..f7e708a546 100644
--- a/beacon-chain/sync/backfill/service_test.go
+++ b/beacon-chain/sync/backfill/service_test.go
@@ -69,7 +69,7 @@ func TestServiceInit(t *testing.T) {
todo := make([]batch, 0)
todo = testReadN(ctx, t, pool.todoChan, nWorkers, todo)
require.Equal(t, nWorkers, len(todo))
- for i := 0; i < remaining; i++ {
+ for i := range remaining {
b := todo[i]
if b.state == batchSequenced {
b.state = batchImportable
@@ -96,7 +96,7 @@ func TestMinimumBackfillSlot(t *testing.T) {
}
func testReadN(ctx context.Context, t *testing.T, c chan batch, n int, into []batch) []batch {
- for i := 0; i < n; i++ {
+ for range n {
select {
case b := <-c:
into = append(into, b)
diff --git a/beacon-chain/sync/backfill/verify_test.go b/beacon-chain/sync/backfill/verify_test.go
index 0f3466d43e..12ec04d986 100644
--- a/beacon-chain/sync/backfill/verify_test.go
+++ b/beacon-chain/sync/backfill/verify_test.go
@@ -51,7 +51,7 @@ func testBlocksWithKeys(t *testing.T, nBlocks uint64, nBlobs int, vr []byte) ([]
sks, pks, err := interop.DeterministicallyGenerateKeys(0, nBlocks)
require.NoError(t, err)
prevRoot := [32]byte{}
- for i := uint64(0); i < nBlocks; i++ {
+ for i := range nBlocks {
block, blobs := util.GenerateTestDenebBlockWithSidecar(t, prevRoot, primitives.Slot(i), nBlobs, util.WithProposerSigning(primitives.ValidatorIndex(i), sks[i], vr))
prevRoot = block.Root()
blks[i] = block
diff --git a/beacon-chain/sync/batch_verifier.go b/beacon-chain/sync/batch_verifier.go
index 4e6a517993..0f1e61f35d 100644
--- a/beacon-chain/sync/batch_verifier.go
+++ b/beacon-chain/sync/batch_verifier.go
@@ -130,7 +130,7 @@ func verifyBatch(verifierBatch []*signatureVerifier) {
verificationErr = errors.New("batch signature verification failed")
}
}
- for i := 0; i < len(verifierBatch); i++ {
+ for i := range verifierBatch {
verifierBatch[i].resChan <- verificationErr
}
}
diff --git a/beacon-chain/sync/blobs_test.go b/beacon-chain/sync/blobs_test.go
index 7bf450f4ee..e2ef055a8c 100644
--- a/beacon-chain/sync/blobs_test.go
+++ b/beacon-chain/sync/blobs_test.go
@@ -53,8 +53,8 @@ type blobsTestCase struct {
}
type testHandler func(s *Service) rpcHandler
-type expectedDefiner func(t *testing.T, scs []blocks.ROBlob, req interface{}) []*expectedBlobChunk
-type requestFromSidecars func([]blocks.ROBlob) interface{}
+type expectedDefiner func(t *testing.T, scs []blocks.ROBlob, req any) []*expectedBlobChunk
+type requestFromSidecars func([]blocks.ROBlob) any
type oldestSlotCallback func(t *testing.T) types.Slot
type expectedRequirer func(*testing.T, *Service, []*expectedBlobChunk) func(network.Stream)
diff --git a/beacon-chain/sync/block_batcher.go b/beacon-chain/sync/block_batcher.go
index aa1963d425..ca7a2a1146 100644
--- a/beacon-chain/sync/block_batcher.go
+++ b/beacon-chain/sync/block_batcher.go
@@ -100,7 +100,7 @@ func (bb *blockRangeBatcher) next(ctx context.Context, stream libp2pcore.Stream)
}
rob = append(rob, gb)
}
- for i := 0; i < len(blks); i++ {
+ for i := range blks {
rb, err := blocks.NewROBlockWithRoot(blks[i], roots[i])
if err != nil {
return blockBatch{err: errors.Wrap(err, "Could not initialize ROBlock")}, false
diff --git a/beacon-chain/sync/block_batcher_test.go b/beacon-chain/sync/block_batcher_test.go
index 7cdce247da..74de2bce23 100644
--- a/beacon-chain/sync/block_batcher_test.go
+++ b/beacon-chain/sync/block_batcher_test.go
@@ -19,7 +19,7 @@ func TestSortedObj_SortBlocksRoots(t *testing.T) {
}
var blks []blocks.ROBlock
- for i := 0; i < 10; i++ {
+ for range 10 {
slot := primitives.Slot(randFunc())
newBlk, err := blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{Slot: slot, Body: ðpb.BeaconBlockBody{}}})
require.NoError(t, err)
@@ -47,7 +47,7 @@ func TestSortedObj_NoDuplicates(t *testing.T) {
return randGen.Int63n(50)
}
- for i := 0; i < 10; i++ {
+ for range 10 {
slot := primitives.Slot(randFunc())
newBlk := ðpb.SignedBeaconBlock{Block: ðpb.BeaconBlock{Slot: slot, Body: ðpb.BeaconBlockBody{}}}
// append twice
diff --git a/beacon-chain/sync/checkpoint/weak-subjectivity_test.go b/beacon-chain/sync/checkpoint/weak-subjectivity_test.go
index e7e2548419..9f577beea8 100644
--- a/beacon-chain/sync/checkpoint/weak-subjectivity_test.go
+++ b/beacon-chain/sync/checkpoint/weak-subjectivity_test.go
@@ -24,7 +24,7 @@ import (
"github.com/pkg/errors"
)
-func marshalToEnvelope(val interface{}) ([]byte, error) {
+func marshalToEnvelope(val any) ([]byte, error) {
raw, err := json.Marshal(val)
if err != nil {
return nil, errors.Wrap(err, "error marshaling value to place in data envelope")
@@ -136,8 +136,8 @@ func TestDownloadWeakSubjectivityCheckpoint(t *testing.T) {
Root: fmt.Sprintf("%#x", bRoot),
}
wsr := struct {
- Checkpoint interface{} `json:"ws_checkpoint"`
- StateRoot string `json:"state_root"`
+ Checkpoint any `json:"ws_checkpoint"`
+ StateRoot string `json:"state_root"`
}{
Checkpoint: cp,
StateRoot: fmt.Sprintf("%#x", wRoot),
@@ -306,7 +306,7 @@ func defaultTestHeadState(t *testing.T, cfg *params.BeaconChainConfig) (state.Be
func populateValidators(cfg *params.BeaconChainConfig, st state.BeaconState, valCount, avgBalance uint64) error {
validators := make([]*ethpb.Validator, valCount)
balances := make([]uint64, len(validators))
- for i := uint64(0); i < valCount; i++ {
+ for i := range valCount {
validators[i] = ðpb.Validator{
PublicKey: make([]byte, cfg.BLSPubkeyLength),
WithdrawalCredentials: make([]byte, 32),
diff --git a/beacon-chain/sync/data_column_sidecars.go b/beacon-chain/sync/data_column_sidecars.go
index 2e9c895244..9206025c5b 100644
--- a/beacon-chain/sync/data_column_sidecars.go
+++ b/beacon-chain/sync/data_column_sidecars.go
@@ -3,6 +3,7 @@ package sync
import (
"bytes"
"context"
+ "maps"
"slices"
"sync"
"time"
@@ -129,9 +130,7 @@ func FetchDataColumnSidecars(
return nil, nil, errors.Wrap(err, "try merge storage and mandatory inputs")
}
- for root, sidecars := range mergedSidecarsByRoot {
- result[root] = sidecars
- }
+ maps.Copy(result, mergedSidecarsByRoot)
if len(incompleteRoots) == 0 {
log.WithField("finalMissingRootCount", 0).Debug("Fetched data column sidecars from storage and peers")
@@ -150,9 +149,7 @@ func FetchDataColumnSidecars(
return nil, nil, errors.Wrap(err, "try merge storage and all inputs")
}
- for root, sidecars := range mergedSidecarsByRoot {
- result[root] = sidecars
- }
+ maps.Copy(result, mergedSidecarsByRoot)
if len(incompleteRoots) == 0 {
log.WithField("finalMissingRootCount", 0).Debug("Fetched data column sidecars from storage and peers using rescue mode")
@@ -165,9 +162,7 @@ func FetchDataColumnSidecars(
return nil, nil, errors.Wrap(err, "assemble available sidecars for incomplete roots")
}
- for root, sidecars := range incompleteSidecarsByRoot {
- result[root] = sidecars
- }
+ maps.Copy(result, incompleteSidecarsByRoot)
log.WithField("finalMissingRootCount", len(incompleteRoots)).Warning("Failed to fetch data column sidecars")
return result, missingByRoot, nil
@@ -1159,9 +1154,7 @@ func copyIndicesByRoot(original map[[fieldparams.RootLength]byte]map[uint64]bool
copied := make(map[[fieldparams.RootLength]byte]map[uint64]bool, len(original))
for root, indexMap := range original {
copied[root] = make(map[uint64]bool, len(indexMap))
- for index, value := range indexMap {
- copied[root][index] = value
- }
+ maps.Copy(copied[root], indexMap)
}
return copied
}
diff --git a/beacon-chain/sync/data_columns_reconstruct.go b/beacon-chain/sync/data_columns_reconstruct.go
index dd81b2de77..47b4704f06 100644
--- a/beacon-chain/sync/data_columns_reconstruct.go
+++ b/beacon-chain/sync/data_columns_reconstruct.go
@@ -21,7 +21,7 @@ import (
// https:github.com/ethereum/consensus-specs/blob/master/specs/fulu/das-core.md#reconstruction-and-cross-seeding
func (s *Service) processDataColumnSidecarsFromReconstruction(ctx context.Context, sidecar blocks.VerifiedRODataColumn) error {
key := fmt.Sprintf("%#x", sidecar.BlockRoot())
- if _, err, _ := s.reconstructionSingleFlight.Do(key, func() (interface{}, error) {
+ if _, err, _ := s.reconstructionSingleFlight.Do(key, func() (any, error) {
var wg sync.WaitGroup
root := sidecar.BlockRoot()
diff --git a/beacon-chain/sync/decode_pubsub.go b/beacon-chain/sync/decode_pubsub.go
index 050798ff57..c2c6c31820 100644
--- a/beacon-chain/sync/decode_pubsub.go
+++ b/beacon-chain/sync/decode_pubsub.go
@@ -35,15 +35,15 @@ func (s *Service) decodePubsubMessage(msg *pubsub.Message) (ssz.Unmarshaler, err
// Specially handle subnet messages.
switch {
case strings.Contains(topic, p2p.GossipAttestationMessage):
- topic = p2p.GossipTypeMapping[reflect.TypeOf(ðpb.Attestation{})]
+ topic = p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.Attestation]()]
// Given that both sync message related subnets have the same message name, we have to
// differentiate them below.
case strings.Contains(topic, p2p.GossipSyncCommitteeMessage) && !strings.Contains(topic, p2p.SyncContributionAndProofSubnetTopicFormat):
- topic = p2p.GossipTypeMapping[reflect.TypeOf(ðpb.SyncCommitteeMessage{})]
+ topic = p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SyncCommitteeMessage]()]
case strings.Contains(topic, p2p.GossipBlobSidecarMessage):
- topic = p2p.GossipTypeMapping[reflect.TypeOf(ðpb.BlobSidecar{})]
+ topic = p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.BlobSidecar]()]
case strings.Contains(topic, p2p.GossipDataColumnSidecarMessage):
- topic = p2p.GossipTypeMapping[reflect.TypeOf(ðpb.DataColumnSidecar{})]
+ topic = p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.DataColumnSidecar]()]
}
base := p2p.GossipTopicMappings(topic, 0)
diff --git a/beacon-chain/sync/decode_pubsub_test.go b/beacon-chain/sync/decode_pubsub_test.go
index 0d5c2e0209..8bd263c4b3 100644
--- a/beacon-chain/sync/decode_pubsub_test.go
+++ b/beacon-chain/sync/decode_pubsub_test.go
@@ -35,7 +35,7 @@ func TestService_decodePubsubMessage(t *testing.T) {
name string
topic string
input *pubsub.Message
- want interface{}
+ want any
wantErr error
}{
{
@@ -64,7 +64,7 @@ func TestService_decodePubsubMessage(t *testing.T) {
},
{
name: "valid message -- beacon block",
- topic: fmt.Sprintf(p2p.GossipTypeMapping[reflect.TypeOf(ðpb.SignedBeaconBlock{})], entry.ForkDigest),
+ topic: fmt.Sprintf(p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()], entry.ForkDigest),
input: &pubsub.Message{
Message: &pb.Message{
Data: func() []byte {
diff --git a/beacon-chain/sync/fork_watcher_test.go b/beacon-chain/sync/fork_watcher_test.go
index 2d81bb37a5..d9c5756893 100644
--- a/beacon-chain/sync/fork_watcher_test.go
+++ b/beacon-chain/sync/fork_watcher_test.go
@@ -233,11 +233,9 @@ func TestService_CheckForNextEpochFork(t *testing.T) {
func attachSpawner(s *Service) *sync.WaitGroup {
wg := new(sync.WaitGroup)
s.subscriptionSpawner = func(f func()) {
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
f()
- }()
+ })
}
return wg
}
diff --git a/beacon-chain/sync/initial-sync/blocks_fetcher.go b/beacon-chain/sync/initial-sync/blocks_fetcher.go
index cbb5c62acd..3d32afcfe4 100644
--- a/beacon-chain/sync/initial-sync/blocks_fetcher.go
+++ b/beacon-chain/sync/initial-sync/blocks_fetcher.go
@@ -267,14 +267,12 @@ func (f *blocksFetcher) loop() {
log.Debug("Context closed, exiting goroutine (blocks fetcher)")
return
case req := <-f.fetchRequests:
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
select {
case <-f.ctx.Done():
case f.fetchResponses <- f.handleRequest(req.ctx, req.start, req.count):
}
- }()
+ })
}
}
}
diff --git a/beacon-chain/sync/initial-sync/blocks_fetcher_peers.go b/beacon-chain/sync/initial-sync/blocks_fetcher_peers.go
index a6f1edcd01..9796f3ee2f 100644
--- a/beacon-chain/sync/initial-sync/blocks_fetcher_peers.go
+++ b/beacon-chain/sync/initial-sync/blocks_fetcher_peers.go
@@ -62,10 +62,7 @@ func (f *blocksFetcher) selectFailOverPeer(excludedPID peer.ID, peers []peer.ID)
// waitForMinimumPeers spins and waits up until enough peers are available.
func (f *blocksFetcher) waitForMinimumPeers(ctx context.Context) ([]peer.ID, error) {
- required := params.BeaconConfig().MaxPeersToSync
- if flags.Get().MinimumSyncPeers < required {
- required = flags.Get().MinimumSyncPeers
- }
+ required := min(flags.Get().MinimumSyncPeers, params.BeaconConfig().MaxPeersToSync)
for {
if ctx.Err() != nil {
return nil, ctx.Err()
@@ -123,10 +120,7 @@ func (f *blocksFetcher) filterPeers(ctx context.Context, peers []peer.ID, peersP
// trimPeers limits peer list, returning only specified percentage of peers.
// Takes system constraints into account (min/max peers to sync).
func trimPeers(peers []peer.ID, peersPercentage float64) []peer.ID {
- required := params.BeaconConfig().MaxPeersToSync
- if flags.Get().MinimumSyncPeers < required {
- required = flags.Get().MinimumSyncPeers
- }
+ required := min(flags.Get().MinimumSyncPeers, params.BeaconConfig().MaxPeersToSync)
// Weak/slow peers will be pushed down the list and trimmed since only percentage of peers is selected.
limit := uint64(math.Round(float64(len(peers)) * peersPercentage))
// Limit cannot be less that minimum peers required by sync mechanism.
diff --git a/beacon-chain/sync/initial-sync/blocks_fetcher_peers_test.go b/beacon-chain/sync/initial-sync/blocks_fetcher_peers_test.go
index 8e41e65df0..13100bc1a2 100644
--- a/beacon-chain/sync/initial-sync/blocks_fetcher_peers_test.go
+++ b/beacon-chain/sync/initial-sync/blocks_fetcher_peers_test.go
@@ -252,7 +252,7 @@ func TestBlocksFetcher_filterPeers(t *testing.T) {
peerStats := make(map[peer.ID]int, len(tt.want))
var filteredPIDs []peer.ID
var err error
- for i := 0; i < 1000; i++ {
+ for range 1000 {
filteredPIDs = fetcher.filterPeers(t.Context(), peerIDs, tt.args.peersPercentage)
if len(filteredPIDs) <= 1 {
break
diff --git a/beacon-chain/sync/initial-sync/blocks_fetcher_test.go b/beacon-chain/sync/initial-sync/blocks_fetcher_test.go
index 1e4be4fa5e..e7fef8674c 100644
--- a/beacon-chain/sync/initial-sync/blocks_fetcher_test.go
+++ b/beacon-chain/sync/initial-sync/blocks_fetcher_test.go
@@ -388,7 +388,7 @@ func TestBlocksFetcher_scheduleRequest(t *testing.T) {
t.Run("unblock on context cancellation", func(t *testing.T) {
fetcher := newBlocksFetcher(t.Context(), &blocksFetcherConfig{})
- for i := 0; i < maxPendingRequests; i++ {
+ for range maxPendingRequests {
assert.NoError(t, fetcher.scheduleRequest(t.Context(), 1, blockBatchLimit))
}
diff --git a/beacon-chain/sync/initial-sync/blocks_fetcher_utils.go b/beacon-chain/sync/initial-sync/blocks_fetcher_utils.go
index b44b1fad7e..3db193aa50 100644
--- a/beacon-chain/sync/initial-sync/blocks_fetcher_utils.go
+++ b/beacon-chain/sync/initial-sync/blocks_fetcher_utils.go
@@ -297,7 +297,7 @@ func (f *blocksFetcher) findForkWithPeer(ctx context.Context, pid peer.ID, slot
// findAncestor tries to figure out common ancestor slot that connects a given root to known block.
func (f *blocksFetcher) findAncestor(ctx context.Context, pid peer.ID, b interfaces.ReadOnlySignedBeaconBlock) (*forkData, error) {
outBlocks := []interfaces.ReadOnlySignedBeaconBlock{b}
- for i := uint64(0); i < backtrackingMaxHops; i++ {
+ for range uint64(backtrackingMaxHops) {
parentRoot := outBlocks[len(outBlocks)-1].Block().ParentRoot()
if f.chain.HasBlock(ctx, parentRoot) {
// Common ancestor found, forward blocks back to processor.
diff --git a/beacon-chain/sync/initial-sync/blocks_fetcher_utils_test.go b/beacon-chain/sync/initial-sync/blocks_fetcher_utils_test.go
index 456c8a5036..9e526d2b6e 100644
--- a/beacon-chain/sync/initial-sync/blocks_fetcher_utils_test.go
+++ b/beacon-chain/sync/initial-sync/blocks_fetcher_utils_test.go
@@ -35,7 +35,7 @@ func TestBlocksFetcher_nonSkippedSlotAfter(t *testing.T) {
blocks = append(blocks, 55000)
blocks = append(blocks, makeSequence(57000, 57256)...)
var peersData []*peerData
- for i := 0; i < size; i++ {
+ for range size {
peersData = append(peersData, &peerData{
blocks: blocks,
finalizedEpoch: 1800,
@@ -177,7 +177,7 @@ func TestBlocksFetcher_findFork(t *testing.T) {
DB: beaconDB,
FinalizedCheckPoint: ðpb.Checkpoint{
Epoch: finalizedEpoch,
- Root: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
+ Root: fmt.Appendf(nil, "finalized_root %d", finalizedEpoch),
},
Genesis: time.Now(),
ValidatorsRoot: [32]byte{},
@@ -199,7 +199,7 @@ func TestBlocksFetcher_findFork(t *testing.T) {
// Consume all chain1 blocks from many peers (alternative fork will be featured by a single peer,
// and should still be enough to explore alternative paths).
peers := make([]peer.ID, 0)
- for i := 0; i < 5; i++ {
+ for range 5 {
peers = append(peers, connectPeerHavingBlocks(t, p2p, chain1, finalizedSlot, p2p.Peers()))
}
@@ -487,7 +487,7 @@ func TestBlocksFetcher_findAncestor(t *testing.T) {
DB: beaconDB,
FinalizedCheckPoint: ðpb.Checkpoint{
Epoch: finalizedEpoch,
- Root: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
+ Root: fmt.Appendf(nil, "finalized_root %d", finalizedEpoch),
},
Genesis: time.Now(),
ValidatorsRoot: [32]byte{},
diff --git a/beacon-chain/sync/initial-sync/blocks_queue.go b/beacon-chain/sync/initial-sync/blocks_queue.go
index 8701885c18..c8be8e6d97 100644
--- a/beacon-chain/sync/initial-sync/blocks_queue.go
+++ b/beacon-chain/sync/initial-sync/blocks_queue.go
@@ -303,7 +303,7 @@ func waitHighestExpectedSlot(q *blocksQueue) bool {
// onScheduleEvent is an event called on newly arrived epochs. Transforms state to scheduled.
func (q *blocksQueue) onScheduleEvent(ctx context.Context) eventHandlerFn {
- return func(m *stateMachine, in interface{}) (stateID, error) {
+ return func(m *stateMachine, in any) (stateID, error) {
if m.state != stateNew {
return m.state, errInvalidInitialState
}
@@ -321,7 +321,7 @@ func (q *blocksQueue) onScheduleEvent(ctx context.Context) eventHandlerFn {
// onDataReceivedEvent is an event called when data is received from fetcher.
func (q *blocksQueue) onDataReceivedEvent(ctx context.Context) eventHandlerFn {
- return func(m *stateMachine, in interface{}) (stateID, error) {
+ return func(m *stateMachine, in any) (stateID, error) {
if ctx.Err() != nil {
return m.state, ctx.Err()
}
@@ -359,7 +359,7 @@ func (q *blocksQueue) onDataReceivedEvent(ctx context.Context) eventHandlerFn {
// onReadyToSendEvent is an event called to allow epochs with available blocks to send them downstream.
func (q *blocksQueue) onReadyToSendEvent(ctx context.Context) eventHandlerFn {
- return func(m *stateMachine, in interface{}) (stateID, error) {
+ return func(m *stateMachine, in any) (stateID, error) {
if ctx.Err() != nil {
return m.state, ctx.Err()
}
@@ -404,7 +404,7 @@ func (q *blocksQueue) onReadyToSendEvent(ctx context.Context) eventHandlerFn {
// onProcessSkippedEvent is an event triggered on skipped machines, allowing handlers to
// extend lookahead window, in case where progress is not possible otherwise.
func (q *blocksQueue) onProcessSkippedEvent(ctx context.Context) eventHandlerFn {
- return func(m *stateMachine, in interface{}) (stateID, error) {
+ return func(m *stateMachine, in any) (stateID, error) {
if ctx.Err() != nil {
return m.state, ctx.Err()
}
@@ -468,7 +468,7 @@ func (q *blocksQueue) downscorePeer(peerID peer.ID, reason string) {
// onCheckStaleEvent is an event that allows to mark stale epochs,
// so that they can be re-processed.
func onCheckStaleEvent(ctx context.Context) eventHandlerFn {
- return func(m *stateMachine, in interface{}) (stateID, error) {
+ return func(m *stateMachine, in any) (stateID, error) {
if ctx.Err() != nil {
return m.state, ctx.Err()
}
diff --git a/beacon-chain/sync/initial-sync/blocks_queue_test.go b/beacon-chain/sync/initial-sync/blocks_queue_test.go
index 6d058f0375..3050341c8d 100644
--- a/beacon-chain/sync/initial-sync/blocks_queue_test.go
+++ b/beacon-chain/sync/initial-sync/blocks_queue_test.go
@@ -1049,7 +1049,7 @@ func TestBlocksQueue_stuckInUnfavourableFork(t *testing.T) {
DB: beaconDB,
FinalizedCheckPoint: ð.Checkpoint{
Epoch: finalizedEpoch,
- Root: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
+ Root: fmt.Appendf(nil, "finalized_root %d", finalizedEpoch),
},
Genesis: time.Now(),
ValidatorsRoot: [32]byte{},
@@ -1252,7 +1252,7 @@ func TestBlocksQueue_stuckWhenHeadIsSetToOrphanedBlock(t *testing.T) {
DB: beaconDB,
FinalizedCheckPoint: ð.Checkpoint{
Epoch: finalizedEpoch,
- Root: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
+ Root: fmt.Appendf(nil, "finalized_root %d", finalizedEpoch),
},
Genesis: time.Now(),
ValidatorsRoot: [32]byte{},
diff --git a/beacon-chain/sync/initial-sync/fsm.go b/beacon-chain/sync/initial-sync/fsm.go
index 284b1e5d5a..a59375fe69 100644
--- a/beacon-chain/sync/initial-sync/fsm.go
+++ b/beacon-chain/sync/initial-sync/fsm.go
@@ -3,7 +3,7 @@ package initialsync
import (
"errors"
"fmt"
- "sort"
+ "slices"
"time"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
@@ -48,7 +48,7 @@ type stateMachine struct {
}
// eventHandlerFn is an event handler function's signature.
-type eventHandlerFn func(m *stateMachine, data interface{}) (newState stateID, err error)
+type eventHandlerFn func(m *stateMachine, data any) (newState stateID, err error)
// newStateMachineManager returns fully initialized state machine manager.
func newStateMachineManager() *stateMachineManager {
@@ -110,9 +110,7 @@ func (smm *stateMachineManager) recalculateMachineAttribs() {
for key := range smm.machines {
keys = append(keys, key)
}
- sort.Slice(keys, func(i, j int) bool {
- return keys[i] < keys[j]
- })
+ slices.Sort(keys)
smm.keys = keys
}
@@ -159,7 +157,7 @@ func (m *stateMachine) setState(name stateID) {
}
// trigger invokes the event handler on a given state machine.
-func (m *stateMachine) trigger(event eventID, data interface{}) error {
+func (m *stateMachine) trigger(event eventID, data any) error {
handlers, ok := m.smm.handlers[m.state]
if !ok {
return fmt.Errorf("no event handlers registered for event: %v, state: %v", event, m.state)
diff --git a/beacon-chain/sync/initial-sync/fsm_benchmark_test.go b/beacon-chain/sync/initial-sync/fsm_benchmark_test.go
index 684985fcdc..66f7c828fa 100644
--- a/beacon-chain/sync/initial-sync/fsm_benchmark_test.go
+++ b/beacon-chain/sync/initial-sync/fsm_benchmark_test.go
@@ -9,7 +9,7 @@ import (
func BenchmarkStateMachine_trigger(b *testing.B) {
sm := newStateMachineManager()
- handlerFn := func(m *stateMachine, in interface{}) (id stateID, err error) {
+ handlerFn := func(m *stateMachine, in any) (id stateID, err error) {
response, ok := in.(*fetchRequestParams)
if !ok {
return 0, errInputNotFetchRequestParams
@@ -26,9 +26,8 @@ func BenchmarkStateMachine_trigger(b *testing.B) {
sm.addStateMachine(64)
b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
data := &fetchRequestParams{
start: 23,
count: 32,
diff --git a/beacon-chain/sync/initial-sync/fsm_test.go b/beacon-chain/sync/initial-sync/fsm_test.go
index a9965bd2af..40262d6195 100644
--- a/beacon-chain/sync/initial-sync/fsm_test.go
+++ b/beacon-chain/sync/initial-sync/fsm_test.go
@@ -58,7 +58,7 @@ func TestStateMachine_EventIDString(t *testing.T) {
func TestStateMachineManager_addEventHandler(t *testing.T) {
smm := newStateMachineManager()
- smm.addEventHandler(eventTick, stateNew, func(m *stateMachine, i interface{}) (id stateID, err error) {
+ smm.addEventHandler(eventTick, stateNew, func(m *stateMachine, i any) (id stateID, err error) {
return stateScheduled, nil
})
assert.Equal(t, 1, len(smm.handlers[stateNew]), "Unexpected size")
@@ -67,7 +67,7 @@ func TestStateMachineManager_addEventHandler(t *testing.T) {
assert.Equal(t, stateScheduled, state, "Unexpected state")
// Add second handler to the same event
- smm.addEventHandler(eventTick, stateSent, func(m *stateMachine, i interface{}) (id stateID, err error) {
+ smm.addEventHandler(eventTick, stateSent, func(m *stateMachine, i any) (id stateID, err error) {
return stateDataParsed, nil
})
assert.Equal(t, 1, len(smm.handlers[stateSent]), "Unexpected size")
@@ -76,7 +76,7 @@ func TestStateMachineManager_addEventHandler(t *testing.T) {
assert.Equal(t, stateDataParsed, state, "Unexpected state")
// Add another handler to existing event/state pair. Should have no effect.
- smm.addEventHandler(eventTick, stateSent, func(m *stateMachine, i interface{}) (id stateID, err error) {
+ smm.addEventHandler(eventTick, stateSent, func(m *stateMachine, i any) (id stateID, err error) {
return stateSkipped, nil
})
assert.Equal(t, 1, len(smm.handlers[stateSent]), "Unexpected size")
@@ -97,7 +97,7 @@ func TestStateMachine_trigger(t *testing.T) {
name eventID
returnState stateID
epoch primitives.Epoch
- data interface{}
+ data any
}
tests := []struct {
name string
@@ -157,7 +157,7 @@ func TestStateMachine_trigger(t *testing.T) {
},
}
fn := func(e event) eventHandlerFn {
- return func(m *stateMachine, in interface{}) (stateID, error) {
+ return func(m *stateMachine, in any) (stateID, error) {
if e.err {
return m.state, errors.New("invalid")
}
@@ -198,19 +198,19 @@ func TestStateMachine_trigger(t *testing.T) {
func TestStateMachineManager_QueueLoop(t *testing.T) {
smm := newStateMachineManager()
- smm.addEventHandler(eventTick, stateNew, func(m *stateMachine, data interface{}) (stateID, error) {
+ smm.addEventHandler(eventTick, stateNew, func(m *stateMachine, data any) (stateID, error) {
return stateScheduled, nil
})
- smm.addEventHandler(eventTick, stateScheduled, func(m *stateMachine, data interface{}) (stateID, error) {
+ smm.addEventHandler(eventTick, stateScheduled, func(m *stateMachine, data any) (stateID, error) {
if m.start < 256 {
return stateDataParsed, nil
}
return stateSkipped, nil
})
- smm.addEventHandler(eventTick, stateDataParsed, func(m *stateMachine, data interface{}) (stateID, error) {
+ smm.addEventHandler(eventTick, stateDataParsed, func(m *stateMachine, data any) (stateID, error) {
return stateSent, nil
})
- smm.addEventHandler(eventTick, stateSkipped, func(m *stateMachine, data interface{}) (stateID, error) {
+ smm.addEventHandler(eventTick, stateSkipped, func(m *stateMachine, data any) (stateID, error) {
dataParsed, ok := data.(int)
if !ok {
return m.state, errors.New("invalid data type")
diff --git a/beacon-chain/sync/initial-sync/initial_sync_test.go b/beacon-chain/sync/initial-sync/initial_sync_test.go
index 08460cf85f..27f4a84a41 100644
--- a/beacon-chain/sync/initial-sync/initial_sync_test.go
+++ b/beacon-chain/sync/initial-sync/initial_sync_test.go
@@ -234,7 +234,7 @@ func connectPeer(t *testing.T, host *p2pt.TestP2P, datum *peerData, peerStatus *
peerStatus.SetConnectionState(p.PeerID(), peers.Connected)
peerStatus.SetChainState(p.PeerID(), ðpb.StatusV2{
ForkDigest: params.BeaconConfig().GenesisForkVersion,
- FinalizedRoot: []byte(fmt.Sprintf("finalized_root %d", datum.finalizedEpoch)),
+ FinalizedRoot: fmt.Appendf(nil, "finalized_root %d", datum.finalizedEpoch),
FinalizedEpoch: datum.finalizedEpoch,
HeadRoot: bytesutil.PadTo([]byte("head_root"), 32),
HeadSlot: datum.headSlot,
@@ -333,7 +333,7 @@ func connectPeerHavingBlocks(
peerStatus.SetConnectionState(p.PeerID(), peers.Connected)
peerStatus.SetChainState(p.PeerID(), ðpb.StatusV2{
ForkDigest: params.BeaconConfig().GenesisForkVersion,
- FinalizedRoot: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
+ FinalizedRoot: fmt.Appendf(nil, "finalized_root %d", finalizedEpoch),
FinalizedEpoch: finalizedEpoch,
HeadRoot: headRoot[:],
HeadSlot: blks[len(blks)-1].Block.Slot,
diff --git a/beacon-chain/sync/initial-sync/service.go b/beacon-chain/sync/initial-sync/service.go
index 8c6aea4a08..90de787e9a 100644
--- a/beacon-chain/sync/initial-sync/service.go
+++ b/beacon-chain/sync/initial-sync/service.go
@@ -312,10 +312,7 @@ func (s *Service) Resync() error {
}
func (s *Service) waitForMinimumPeers() ([]peer.ID, error) {
- required := params.BeaconConfig().MaxPeersToSync
- if flags.Get().MinimumSyncPeers < required {
- required = flags.Get().MinimumSyncPeers
- }
+ required := min(flags.Get().MinimumSyncPeers, params.BeaconConfig().MaxPeersToSync)
for {
if s.ctx.Err() != nil {
return nil, s.ctx.Err()
diff --git a/beacon-chain/sync/initial-sync/service_test.go b/beacon-chain/sync/initial-sync/service_test.go
index a10aca944d..88175537b9 100644
--- a/beacon-chain/sync/initial-sync/service_test.go
+++ b/beacon-chain/sync/initial-sync/service_test.go
@@ -182,11 +182,9 @@ func TestService_InitStartStop(t *testing.T) {
}
wg := &sync.WaitGroup{}
- wg.Add(1)
- go func() {
+ wg.Go(func() {
s.Start()
- wg.Done()
- }()
+ })
go func() {
// Allow to exit from test (on no head loop waiting for head is started).
@@ -228,11 +226,9 @@ func TestService_waitForStateInitialization(t *testing.T) {
s, _ := newService(ctx, &mock.ChainService{Genesis: time.Now(), ValidatorsRoot: [32]byte{}})
wg := &sync.WaitGroup{}
- wg.Add(1)
- go func() {
+ wg.Go(func() {
s.Start()
- wg.Done()
- }()
+ })
go func() {
time.AfterFunc(500*time.Millisecond, func() {
cancel()
@@ -259,11 +255,9 @@ func TestService_waitForStateInitialization(t *testing.T) {
expectedGenesisTime := gt
wg := &sync.WaitGroup{}
- wg.Add(1)
- go func() {
+ wg.Go(func() {
s.Start()
- wg.Done()
- }()
+ })
rg := func() time.Time { return gt.Add(time.Second * 12) }
go func() {
time.AfterFunc(200*time.Millisecond, func() {
@@ -290,15 +284,13 @@ func TestService_waitForStateInitialization(t *testing.T) {
expectedGenesisTime := time.Now().Add(60 * time.Second)
wg := &sync.WaitGroup{}
- wg.Add(1)
- go func() {
+ wg.Go(func() {
time.AfterFunc(500*time.Millisecond, func() {
var vr [32]byte
require.NoError(t, gs.SetClock(startup.NewClock(expectedGenesisTime, vr)))
})
s.Start()
- wg.Done()
- }()
+ })
if util.WaitTimeout(wg, time.Second*5) {
t.Fatalf("Test should have exited by now, timed out")
diff --git a/beacon-chain/sync/kzg_batch_verifier_test.go b/beacon-chain/sync/kzg_batch_verifier_test.go
index 5929b139bd..554ab34567 100644
--- a/beacon-chain/sync/kzg_batch_verifier_test.go
+++ b/beacon-chain/sync/kzg_batch_verifier_test.go
@@ -53,8 +53,7 @@ func TestValidateWithKzgBatchVerifier(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
service := &Service{
ctx: ctx,
@@ -79,8 +78,7 @@ func TestVerifierRoutine(t *testing.T) {
require.NoError(t, err)
t.Run("processes single request", func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
service := &Service{
ctx: ctx,
@@ -101,8 +99,7 @@ func TestVerifierRoutine(t *testing.T) {
})
t.Run("batches multiple requests", func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
service := &Service{
ctx: ctx,
@@ -200,8 +197,7 @@ func TestKzgBatchVerifierConcurrency(t *testing.T) {
err := kzg.Start()
require.NoError(t, err)
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
service := &Service{
ctx: ctx,
@@ -237,8 +233,7 @@ func TestKzgBatchVerifierFallback(t *testing.T) {
require.NoError(t, err)
t.Run("fallback handles mixed valid/invalid batch correctly", func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
service := &Service{
ctx: ctx,
@@ -259,8 +254,7 @@ func TestKzgBatchVerifierFallback(t *testing.T) {
})
t.Run("empty data columns fallback", func(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
+ ctx := t.Context()
service := &Service{
ctx: ctx,
diff --git a/beacon-chain/sync/metrics.go b/beacon-chain/sync/metrics.go
index 2f12deedba..226f3df42a 100644
--- a/beacon-chain/sync/metrics.go
+++ b/beacon-chain/sync/metrics.go
@@ -256,8 +256,8 @@ func (s *Service) updateMetrics() {
}
indices := aggregatorSubnetIndices(s.cfg.clock.CurrentSlot())
syncIndices := cache.SyncSubnetIDs.GetAllSubnets(slots.ToEpoch(s.cfg.clock.CurrentSlot()))
- attTopic := p2p.GossipTypeMapping[reflect.TypeOf(&pb.Attestation{})]
- syncTopic := p2p.GossipTypeMapping[reflect.TypeOf(&pb.SyncCommitteeMessage{})]
+ attTopic := p2p.GossipTypeMapping[reflect.TypeFor[*pb.Attestation]()]
+ syncTopic := p2p.GossipTypeMapping[reflect.TypeFor[*pb.SyncCommitteeMessage]()]
attTopic += s.cfg.p2p.Encoding().ProtocolSuffix()
syncTopic += s.cfg.p2p.Encoding().ProtocolSuffix()
if flags.Get().SubscribeToAllSubnets {
diff --git a/beacon-chain/sync/pending_attestations_queue_test.go b/beacon-chain/sync/pending_attestations_queue_test.go
index 89db1a4803..19fc363494 100644
--- a/beacon-chain/sync/pending_attestations_queue_test.go
+++ b/beacon-chain/sync/pending_attestations_queue_test.go
@@ -175,9 +175,7 @@ func TestProcessPendingAtts_HasBlockSaveUnaggregatedAtt(t *testing.T) {
require.NoError(t, r.processPendingAttsForBlock(t.Context(), root))
var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
for {
select {
case received := <-done:
@@ -188,7 +186,7 @@ func TestProcessPendingAtts_HasBlockSaveUnaggregatedAtt(t *testing.T) {
return
}
}
- }()
+ })
atts := r.cfg.attPool.UnaggregatedAttestations()
assert.Equal(t, 1, len(atts), "Did not save unaggregated att")
assert.DeepEqual(t, att, atts[0], "Incorrect saved att")
@@ -268,9 +266,7 @@ func TestProcessPendingAtts_HasBlockSaveUnaggregatedAttElectra(t *testing.T) {
r.blkRootToPendingAtts[root] = []any{att}
require.NoError(t, r.processPendingAttsForBlock(t.Context(), root))
var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
for {
select {
case received := <-done:
@@ -281,7 +277,7 @@ func TestProcessPendingAtts_HasBlockSaveUnaggregatedAttElectra(t *testing.T) {
return
}
}
- }()
+ })
atts := r.cfg.attPool.UnaggregatedAttestations()
require.Equal(t, 1, len(atts), "Did not save unaggregated att")
assert.DeepEqual(t, att.ToAttestationElectra(committee), atts[0], "Incorrect saved att")
@@ -395,9 +391,7 @@ func TestProcessPendingAtts_HasBlockSaveUnAggregatedAttElectra_VerifyAlreadySeen
// Verify that the event feed receives the expected attestation.
var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
for {
select {
case received := <-done:
@@ -408,7 +402,7 @@ func TestProcessPendingAtts_HasBlockSaveUnAggregatedAttElectra_VerifyAlreadySeen
return
}
}
- }()
+ })
// Verify unaggregated attestations are saved correctly.
atts := r.cfg.attPool.UnaggregatedAttestations()
@@ -831,7 +825,7 @@ func TestValidatePendingAtts_CanPruneOldAtts(t *testing.T) {
r2 := [32]byte{'B'}
r3 := [32]byte{'C'}
- for i := primitives.Slot(0); i < 100; i++ {
+ for i := range primitives.Slot(100) {
s.savePendingAtt(ðpb.Attestation{Data: ðpb.AttestationData{Slot: i, BeaconBlockRoot: r1[:]}})
s.savePendingAtt(ðpb.Attestation{Data: ðpb.AttestationData{Slot: i, BeaconBlockRoot: r2[:]}})
s.savePendingAtt(ðpb.Attestation{Data: ðpb.AttestationData{Slot: i, BeaconBlockRoot: r3[:]}})
@@ -877,7 +871,7 @@ func TestSavePendingAtts_BeyondLimit(t *testing.T) {
blkRootToPendingAtts: make(map[[32]byte][]any),
}
- for i := 0; i < pendingAttsLimit; i++ {
+ for i := range pendingAttsLimit {
s.savePendingAtt(ðpb.Attestation{Data: ðpb.AttestationData{Slot: 1, BeaconBlockRoot: bytesutil.Bytes32(uint64(i))}})
}
r1 := [32]byte(bytesutil.Bytes32(0))
diff --git a/beacon-chain/sync/pending_blocks_queue.go b/beacon-chain/sync/pending_blocks_queue.go
index 213a97e6c9..af3f5bfa1c 100644
--- a/beacon-chain/sync/pending_blocks_queue.go
+++ b/beacon-chain/sync/pending_blocks_queue.go
@@ -4,7 +4,7 @@ import (
"context"
"encoding/hex"
"fmt"
- "sort"
+ "slices"
"sync"
"time"
@@ -452,9 +452,7 @@ func (s *Service) sortedPendingSlots() []primitives.Slot {
slot := cacheKeyToSlot(k)
ss = append(ss, slot)
}
- sort.Slice(ss, func(i, j int) bool {
- return ss[i] < ss[j]
- })
+ slices.Sort(ss)
return ss
}
diff --git a/beacon-chain/sync/rate_limiter_test.go b/beacon-chain/sync/rate_limiter_test.go
index 9d10390a10..f0400e846a 100644
--- a/beacon-chain/sync/rate_limiter_test.go
+++ b/beacon-chain/sync/rate_limiter_test.go
@@ -84,7 +84,7 @@ func TestRateLimiter_ExceedRawCapacity(t *testing.T) {
stream, err := p1.BHost.NewStream(t.Context(), p2.PeerID(), protocol.ID(topic))
require.NoError(t, err, "could not create stream")
- for i := 0; i < 2*defaultBurstLimit; i++ {
+ for range 2 * defaultBurstLimit {
err = rlimiter.validateRawRpcRequest(stream, 1)
rlimiter.addRawStream(stream)
require.NoError(t, err, "could not validate incoming request")
@@ -93,7 +93,7 @@ func TestRateLimiter_ExceedRawCapacity(t *testing.T) {
assert.ErrorContains(t, p2ptypes.ErrRateLimited.Error(), rlimiter.validateRawRpcRequest(stream, 1))
// Make Peer bad.
- for i := 0; i < defaultBurstLimit; i++ {
+ for range defaultBurstLimit {
assert.ErrorContains(t, p2ptypes.ErrRateLimited.Error(), rlimiter.validateRawRpcRequest(stream, 1))
}
assert.NotNil(t, p1.Peers().IsBad(p2.PeerID()), "peer is not marked as a bad peer")
diff --git a/beacon-chain/sync/rpc.go b/beacon-chain/sync/rpc.go
index 66156895c1..669bf7fd2e 100644
--- a/beacon-chain/sync/rpc.go
+++ b/beacon-chain/sync/rpc.go
@@ -34,7 +34,7 @@ var (
// rpcHandler is responsible for handling and responding to any incoming message.
// This method may return an error to internal monitoring, but the error will
// not be relayed to the peer.
-type rpcHandler func(context.Context, interface{}, libp2pcore.Stream) error
+type rpcHandler func(context.Context, any, libp2pcore.Stream) error
// rpcHandlerByTopicFromFork returns the RPC handlers for a given fork index.
func (s *Service) rpcHandlerByTopicFromFork(forkIndex int) (map[string]rpcHandler, error) {
diff --git a/beacon-chain/sync/rpc_beacon_blocks_by_range.go b/beacon-chain/sync/rpc_beacon_blocks_by_range.go
index fd668fe405..9281dadae1 100644
--- a/beacon-chain/sync/rpc_beacon_blocks_by_range.go
+++ b/beacon-chain/sync/rpc_beacon_blocks_by_range.go
@@ -21,7 +21,7 @@ import (
)
// beaconBlocksByRangeRPCHandler looks up the request blocks from the database from a given start block.
-func (s *Service) beaconBlocksByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) beaconBlocksByRangeRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.BeaconBlocksByRangeHandler")
defer span.End()
ctx, cancel := context.WithTimeout(ctx, respTimeout)
@@ -144,10 +144,7 @@ func validateRangeRequest(r *pb.BeaconBlocksByRangeRequest, current primitives.S
return rangeParams{}, p2ptypes.ErrInvalidRequest
}
- limit := uint64(flags.Get().BlockBatchLimit)
- if limit > maxRequest {
- limit = maxRequest
- }
+ limit := min(uint64(flags.Get().BlockBatchLimit), maxRequest)
if rp.size > limit {
rp.size = limit
}
diff --git a/beacon-chain/sync/rpc_beacon_blocks_by_root.go b/beacon-chain/sync/rpc_beacon_blocks_by_root.go
index c9afe0a9b7..d2ca52c879 100644
--- a/beacon-chain/sync/rpc_beacon_blocks_by_root.go
+++ b/beacon-chain/sync/rpc_beacon_blocks_by_root.go
@@ -174,7 +174,7 @@ func (s *Service) requestAndSaveMissingBlobSidecars(block interfaces.ReadOnlySig
}
// beaconBlocksRootRPCHandler looks up the request blocks from the database from the given block roots.
-func (s *Service) beaconBlocksRootRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) beaconBlocksRootRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, cancel := context.WithTimeout(ctx, ttfbTimeout)
defer cancel()
SetRPCStreamDeadlines(stream)
diff --git a/beacon-chain/sync/rpc_blob_sidecars_by_range.go b/beacon-chain/sync/rpc_blob_sidecars_by_range.go
index 7b4f0f3838..041fe5a5b5 100644
--- a/beacon-chain/sync/rpc_blob_sidecars_by_range.go
+++ b/beacon-chain/sync/rpc_blob_sidecars_by_range.go
@@ -60,7 +60,7 @@ func (s *Service) streamBlobBatch(ctx context.Context, batch blockBatch, wQuota
var blobRpcThrottleInterval = time.Second
// blobsSidecarsByRangeRPCHandler looks up the request blobs from the database from a given start slot index
-func (s *Service) blobSidecarsByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) blobSidecarsByRangeRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
var err error
ctx, span := trace.StartSpan(ctx, "sync.BlobsSidecarsByRangeHandler")
defer span.End()
@@ -209,10 +209,7 @@ func validateBlobsByRange(r *pb.BlobSidecarsByRangeRequest, current primitives.S
rp.end = rp.start
}
- limit := blobBatchLimit(current)
- if limit > maxRequest {
- limit = maxRequest
- }
+ limit := min(blobBatchLimit(current), maxRequest)
if rp.size > limit {
rp.size = limit
}
diff --git a/beacon-chain/sync/rpc_blob_sidecars_by_range_test.go b/beacon-chain/sync/rpc_blob_sidecars_by_range_test.go
index 637d37bb03..5655f5916a 100644
--- a/beacon-chain/sync/rpc_blob_sidecars_by_range_test.go
+++ b/beacon-chain/sync/rpc_blob_sidecars_by_range_test.go
@@ -16,15 +16,12 @@ import (
func (c *blobsTestCase) defaultOldestSlotByRange(t *testing.T) types.Slot {
currentEpoch := c.clock.CurrentEpoch()
- oldestEpoch := currentEpoch - params.BeaconConfig().MinEpochsForBlobsSidecarsRequest
- if oldestEpoch < params.BeaconConfig().DenebForkEpoch {
- oldestEpoch = params.BeaconConfig().DenebForkEpoch
- }
+ oldestEpoch := max(currentEpoch-params.BeaconConfig().MinEpochsForBlobsSidecarsRequest, params.BeaconConfig().DenebForkEpoch)
oldestSlot := util.SlotAtEpoch(t, oldestEpoch)
return oldestSlot
}
-func blobRangeRequestFromSidecars(scs []blocks.ROBlob) interface{} {
+func blobRangeRequestFromSidecars(scs []blocks.ROBlob) any {
maxBlobs := params.BeaconConfig().MaxBlobsPerBlock(scs[0].Slot())
count := uint64(len(scs) / maxBlobs)
return ðpb.BlobSidecarsByRangeRequest{
@@ -33,7 +30,7 @@ func blobRangeRequestFromSidecars(scs []blocks.ROBlob) interface{} {
}
}
-func (c *blobsTestCase) filterExpectedByRange(t *testing.T, scs []blocks.ROBlob, req interface{}) []*expectedBlobChunk {
+func (c *blobsTestCase) filterExpectedByRange(t *testing.T, scs []blocks.ROBlob, req any) []*expectedBlobChunk {
var expect []*expectedBlobChunk
blockOffset := 0
lastRoot := scs[0].BlockRoot()
@@ -103,7 +100,7 @@ func TestBlobByRangeOK(t *testing.T) {
{
name: "10 slots before window, 10 slots after, count = 20",
nblocks: 10,
- requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
+ requestFromSidecars: func(scs []blocks.ROBlob) any {
return ðpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot() - 10,
Count: 20,
@@ -113,7 +110,7 @@ func TestBlobByRangeOK(t *testing.T) {
{
name: "request before window, empty response",
nblocks: 10,
- requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
+ requestFromSidecars: func(scs []blocks.ROBlob) any {
return ðpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot() - 10,
Count: 10,
@@ -124,7 +121,7 @@ func TestBlobByRangeOK(t *testing.T) {
{
name: "10 blocks * 4 blobs = 40",
nblocks: 10,
- requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
+ requestFromSidecars: func(scs []blocks.ROBlob) any {
return ðpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot() - 10,
Count: 20,
@@ -135,7 +132,7 @@ func TestBlobByRangeOK(t *testing.T) {
{
name: "when request count > MAX_REQUEST_BLOCKS_DENEB, MAX_REQUEST_BLOBS_SIDECARS sidecars in response",
nblocks: int(params.BeaconConfig().MaxRequestBlocksDeneb) + 1,
- requestFromSidecars: func(scs []blocks.ROBlob) interface{} {
+ requestFromSidecars: func(scs []blocks.ROBlob) any {
return ðpb.BlobSidecarsByRangeRequest{
StartSlot: scs[0].Slot(),
Count: params.BeaconConfig().MaxRequestBlocksDeneb + 1,
diff --git a/beacon-chain/sync/rpc_blob_sidecars_by_root.go b/beacon-chain/sync/rpc_blob_sidecars_by_root.go
index a8415d8849..e401250119 100644
--- a/beacon-chain/sync/rpc_blob_sidecars_by_root.go
+++ b/beacon-chain/sync/rpc_blob_sidecars_by_root.go
@@ -24,7 +24,7 @@ import (
// blobSidecarByRootRPCHandler handles the /eth2/beacon_chain/req/blob_sidecars_by_root/1/ RPC request.
// spec: https://github.com/ethereum/consensus-specs/blob/a7e45db9ac2b60a33e144444969ad3ac0aae3d4c/specs/deneb/p2p-interface.md#blobsidecarsbyroot-v1
-func (s *Service) blobSidecarByRootRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) blobSidecarByRootRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.blobSidecarByRootRPCHandler")
defer span.End()
ctx, cancel := context.WithTimeout(ctx, ttfbTimeout)
diff --git a/beacon-chain/sync/rpc_blob_sidecars_by_root_test.go b/beacon-chain/sync/rpc_blob_sidecars_by_root_test.go
index e1888f101b..ac75e45722 100644
--- a/beacon-chain/sync/rpc_blob_sidecars_by_root_test.go
+++ b/beacon-chain/sync/rpc_blob_sidecars_by_root_test.go
@@ -25,7 +25,7 @@ func (c *blobsTestCase) defaultOldestSlotByRoot(t *testing.T) types.Slot {
return oldest
}
-func blobRootRequestFromSidecars(scs []blocks.ROBlob) interface{} {
+func blobRootRequestFromSidecars(scs []blocks.ROBlob) any {
req := make(p2pTypes.BlobSidecarsByRootReq, 0)
for i := range scs {
sc := scs[i]
@@ -34,7 +34,7 @@ func blobRootRequestFromSidecars(scs []blocks.ROBlob) interface{} {
return &req
}
-func (c *blobsTestCase) filterExpectedByRoot(t *testing.T, scs []blocks.ROBlob, r interface{}) []*expectedBlobChunk {
+func (c *blobsTestCase) filterExpectedByRoot(t *testing.T, scs []blocks.ROBlob, r any) []*expectedBlobChunk {
rp, ok := r.(*p2pTypes.BlobSidecarsByRootReq)
if !ok {
panic("unexpected request type in filterExpectedByRoot")
@@ -278,7 +278,7 @@ func TestValidateBlobByRootRequest(t *testing.T) {
// Helper function to create blob identifiers
createBlobIdents := func(count int) p2pTypes.BlobSidecarsByRootReq {
idents := make([]*ethpb.BlobIdentifier, count)
- for i := 0; i < count; i++ {
+ for i := range count {
idents[i] = ðpb.BlobIdentifier{
BlockRoot: make([]byte, 32),
Index: uint64(i),
diff --git a/beacon-chain/sync/rpc_data_column_sidecars_by_range.go b/beacon-chain/sync/rpc_data_column_sidecars_by_range.go
index 90e9eee2a4..4d1e42cab6 100644
--- a/beacon-chain/sync/rpc_data_column_sidecars_by_range.go
+++ b/beacon-chain/sync/rpc_data_column_sidecars_by_range.go
@@ -24,7 +24,7 @@ const rateLimitingAmount = 1
var notDataColumnsByRangeIdentifiersError = errors.New("not data columns by range identifiers")
// dataColumnSidecarsByRangeRPCHandler looks up the request data columns from the database from a given start slot index
-func (s *Service) dataColumnSidecarsByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) dataColumnSidecarsByRangeRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.DataColumnSidecarsByRangeHandler")
defer span.End()
diff --git a/beacon-chain/sync/rpc_data_column_sidecars_by_root.go b/beacon-chain/sync/rpc_data_column_sidecars_by_root.go
index 28fb61f233..7d198d21f7 100644
--- a/beacon-chain/sync/rpc_data_column_sidecars_by_root.go
+++ b/beacon-chain/sync/rpc_data_column_sidecars_by_root.go
@@ -29,7 +29,7 @@ var (
// dataColumnSidecarByRootRPCHandler handles the data column sidecars by root RPC request.
// https://github.com/ethereum/consensus-specs/blob/master/specs/fulu/p2p-interface.md#datacolumnsidecarsbyroot-v1
-func (s *Service) dataColumnSidecarByRootRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) dataColumnSidecarByRootRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.dataColumnSidecarByRootRPCHandler")
defer span.End()
diff --git a/beacon-chain/sync/rpc_goodbye.go b/beacon-chain/sync/rpc_goodbye.go
index 63c2f0f920..a61286ba31 100644
--- a/beacon-chain/sync/rpc_goodbye.go
+++ b/beacon-chain/sync/rpc_goodbye.go
@@ -35,7 +35,7 @@ var backOffTime = map[primitives.SSZUint64]time.Duration{
}
// goodbyeRPCHandler reads the incoming goodbye rpc message from the peer.
-func (s *Service) goodbyeRPCHandler(_ context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) goodbyeRPCHandler(_ context.Context, msg any, stream libp2pcore.Stream) error {
const amount = 1
SetRPCStreamDeadlines(stream)
peerID := stream.Conn().RemotePeer()
diff --git a/beacon-chain/sync/rpc_handler_test.go b/beacon-chain/sync/rpc_handler_test.go
index d020a8514b..b83467ae42 100644
--- a/beacon-chain/sync/rpc_handler_test.go
+++ b/beacon-chain/sync/rpc_handler_test.go
@@ -20,7 +20,7 @@ type rpcHandlerTest struct {
s *Service
}
-func (rt *rpcHandlerTest) testHandler(streamHandler network.StreamHandler, rpcHandler rpcHandler, message interface{}) {
+func (rt *rpcHandlerTest) testHandler(streamHandler network.StreamHandler, rpcHandler rpcHandler, message any) {
ctx, cancel := context.WithTimeout(context.Background(), rt.timeout)
defer func() {
cancel()
diff --git a/beacon-chain/sync/rpc_light_client.go b/beacon-chain/sync/rpc_light_client.go
index c31f7d845f..39f7091d26 100644
--- a/beacon-chain/sync/rpc_light_client.go
+++ b/beacon-chain/sync/rpc_light_client.go
@@ -16,7 +16,7 @@ import (
)
// lightClientBootstrapRPCHandler handles the /eth2/beacon_chain/req/light_client_bootstrap/1/ RPC request.
-func (s *Service) lightClientBootstrapRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) lightClientBootstrapRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.lightClientBootstrapRPCHandler")
defer span.End()
ctx, cancel := context.WithTimeout(ctx, ttfbTimeout)
@@ -67,7 +67,7 @@ func (s *Service) lightClientBootstrapRPCHandler(ctx context.Context, msg interf
}
// lightClientUpdatesByRangeRPCHandler handles the /eth2/beacon_chain/req/light_client_updates_by_range/1/ RPC request.
-func (s *Service) lightClientUpdatesByRangeRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) lightClientUpdatesByRangeRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.lightClientUpdatesByRangeRPCHandler")
defer span.End()
ctx, cancel := context.WithTimeout(ctx, ttfbTimeout)
@@ -153,7 +153,7 @@ func (s *Service) lightClientUpdatesByRangeRPCHandler(ctx context.Context, msg i
}
// lightClientFinalityUpdateRPCHandler handles the /eth2/beacon_chain/req/light_client_finality_update/1/ RPC request.
-func (s *Service) lightClientFinalityUpdateRPCHandler(ctx context.Context, _ interface{}, stream libp2pcore.Stream) error {
+func (s *Service) lightClientFinalityUpdateRPCHandler(ctx context.Context, _ any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.lightClientFinalityUpdateRPCHandler")
defer span.End()
_, cancel := context.WithTimeout(ctx, ttfbTimeout)
@@ -189,7 +189,7 @@ func (s *Service) lightClientFinalityUpdateRPCHandler(ctx context.Context, _ int
}
// lightClientOptimisticUpdateRPCHandler handles the /eth2/beacon_chain/req/light_client_optimistic_update/1/ RPC request.
-func (s *Service) lightClientOptimisticUpdateRPCHandler(ctx context.Context, _ interface{}, stream libp2pcore.Stream) error {
+func (s *Service) lightClientOptimisticUpdateRPCHandler(ctx context.Context, _ any, stream libp2pcore.Stream) error {
ctx, span := trace.StartSpan(ctx, "sync.lightClientOptimisticUpdateRPCHandler")
defer span.End()
_, cancel := context.WithTimeout(ctx, ttfbTimeout)
diff --git a/beacon-chain/sync/rpc_light_client_test.go b/beacon-chain/sync/rpc_light_client_test.go
index e8cc016821..b85aa9a1a1 100644
--- a/beacon-chain/sync/rpc_light_client_test.go
+++ b/beacon-chain/sync/rpc_light_client_test.go
@@ -431,7 +431,7 @@ func TestRPC_LightClientUpdatesByRange(t *testing.T) {
for i := 1; i <= 5; i++ {
t.Run(version.String(i), func(t *testing.T) {
- for j := 0; j < 5; j++ {
+ for j := range 5 {
l := util.NewTestLightClient(t, i, util.WithIncreasedAttestedSlot(uint64(j)))
update, err := lightClient.NewLightClientUpdateFromBeaconState(ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
diff --git a/beacon-chain/sync/rpc_metadata.go b/beacon-chain/sync/rpc_metadata.go
index 52b7eefa05..56550f48bf 100644
--- a/beacon-chain/sync/rpc_metadata.go
+++ b/beacon-chain/sync/rpc_metadata.go
@@ -18,7 +18,7 @@ import (
)
// metaDataHandler reads the incoming metadata RPC request from the peer.
-func (s *Service) metaDataHandler(_ context.Context, _ interface{}, stream libp2pcore.Stream) error {
+func (s *Service) metaDataHandler(_ context.Context, _ any, stream libp2pcore.Stream) error {
SetRPCStreamDeadlines(stream)
// Validate the incoming request regarding rate limiting.
@@ -161,7 +161,7 @@ func (s *Service) sendMetaDataRequest(ctx context.Context, peerID peer.ID) (meta
}
// Send the METADATA request to the peer.
- message := new(interface{})
+ message := new(any)
stream, err := s.cfg.p2p.Send(ctx, message, topic, peerID)
if err != nil {
return nil, errors.Wrap(err, "send metadata request")
diff --git a/beacon-chain/sync/rpc_metadata_test.go b/beacon-chain/sync/rpc_metadata_test.go
index 3c1192a579..f6b8b3453b 100644
--- a/beacon-chain/sync/rpc_metadata_test.go
+++ b/beacon-chain/sync/rpc_metadata_test.go
@@ -67,7 +67,7 @@ func TestMetaDataRPCHandler_ReceivesMetadata(t *testing.T) {
stream1, err := p1.BHost.NewStream(t.Context(), p2.BHost.ID(), pcl)
require.NoError(t, err)
- assert.NoError(t, r.metaDataHandler(t.Context(), new(interface{}), stream1))
+ assert.NoError(t, r.metaDataHandler(t.Context(), new(any), stream1))
if util.WaitTimeout(&wg, 1*time.Second) {
t.Fatal("Did not receive stream within 1 sec")
@@ -295,7 +295,7 @@ func TestMetadataRPCHandler_SendMetadataRequest(t *testing.T) {
wg.Add(1)
peer2.BHost.SetStreamHandler(protocolID, func(stream network.Stream) {
defer wg.Done()
- err := servicePeer2.metaDataHandler(ctx, new(interface{}), stream)
+ err := servicePeer2.metaDataHandler(ctx, new(any), stream)
require.NoError(t, err)
})
@@ -368,7 +368,7 @@ func TestMetadataRPCHandler_SendsMetadataQUIC(t *testing.T) {
wg.Add(1)
p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) {
defer wg.Done()
- err := r2.metaDataHandler(t.Context(), new(interface{}), stream)
+ err := r2.metaDataHandler(t.Context(), new(any), stream)
assert.NoError(t, err)
})
@@ -389,7 +389,7 @@ func TestMetadataRPCHandler_SendsMetadataQUIC(t *testing.T) {
wg.Add(1)
p2.BHost.SetStreamHandler(pcl, func(stream network.Stream) {
defer wg.Done()
- assert.NoError(t, r2.metaDataHandler(t.Context(), new(interface{}), stream))
+ assert.NoError(t, r2.metaDataHandler(t.Context(), new(any), stream))
})
md, err := r.sendMetaDataRequest(t.Context(), p2.BHost.ID())
diff --git a/beacon-chain/sync/rpc_ping.go b/beacon-chain/sync/rpc_ping.go
index 7a4eb51831..01af32bb64 100644
--- a/beacon-chain/sync/rpc_ping.go
+++ b/beacon-chain/sync/rpc_ping.go
@@ -20,7 +20,7 @@ import (
// If the peer's sequence number is higher than the one stored locally,
// a METADATA request is sent to the peer to retrieve and update the latest metadata.
// Note: This function is misnamed, as it performs more than just reading a ping message.
-func (s *Service) pingHandler(_ context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) pingHandler(_ context.Context, msg any, stream libp2pcore.Stream) error {
SetRPCStreamDeadlines(stream)
// Convert the message to SSW Uint64 type.
diff --git a/beacon-chain/sync/rpc_send_request.go b/beacon-chain/sync/rpc_send_request.go
index be44e4f881..cbed387b37 100644
--- a/beacon-chain/sync/rpc_send_request.go
+++ b/beacon-chain/sync/rpc_send_request.go
@@ -485,7 +485,7 @@ func SendDataColumnSidecarsByRangeRequest(
}
// Build the logs.
- var columnsLog interface{} = "all"
+ var columnsLog any = "all"
if columnsCount < numberOfColumns {
columns := request.Columns
slices.Sort(columns)
diff --git a/beacon-chain/sync/rpc_send_request_test.go b/beacon-chain/sync/rpc_send_request_test.go
index 67c6359556..2325cfae75 100644
--- a/beacon-chain/sync/rpc_send_request_test.go
+++ b/beacon-chain/sync/rpc_send_request_test.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
+ "slices"
"sync"
"testing"
"time"
@@ -51,7 +52,7 @@ func TestSendRequest_SendBeaconBlocksByRangeRequest(t *testing.T) {
genesisBlkRoot, err := genesisBlk.Block.HashTreeRoot()
require.NoError(t, err)
parentRoot := genesisBlkRoot
- for i := 0; i < 255; i++ {
+ for i := range 255 {
blk := util.NewBeaconBlock()
blk.Block.Slot = primitives.Slot(i)
blk.Block.ParentRoot = parentRoot[:]
@@ -309,7 +310,7 @@ func TestSendRequest_SendBeaconBlocksByRootRequest(t *testing.T) {
knownBlocks := make(map[[32]byte]*ethpb.SignedBeaconBlock)
knownRoots := make([][32]byte, 0)
- for i := 0; i < 5; i++ {
+ for range 5 {
blk := util.NewBeaconBlock()
blkRoot, err := blk.Block.HashTreeRoot()
require.NoError(t, err)
@@ -635,7 +636,7 @@ func TestSeqBlobValid(t *testing.T) {
}{
{
name: "all valid",
- seq: append(append([]blocks.ROBlob{}, oneBlobs...), twoBlobs...),
+ seq: slices.Concat(oneBlobs, twoBlobs),
},
{
name: "idx out of bounds",
@@ -660,7 +661,7 @@ func TestSeqBlobValid(t *testing.T) {
},
{
name: "slots not ascending",
- seq: append(append([]blocks.ROBlob{}, twoBlobs...), oops...),
+ seq: slices.Concat(twoBlobs, oops),
err: errChunkResponseSlotNotAsc,
errAt: len(twoBlobs),
},
@@ -790,7 +791,7 @@ func TestSendBlobsByRangeRequest(t *testing.T) {
require.NoError(t, err)
prevRoot = bRoot
// Send the maximum possible blobs per slot.
- for j := 0; j < maxBlobsForSlot; j++ {
+ for j := range maxBlobsForSlot {
b := util.HydrateBlobSidecar(ðpb.BlobSidecar{})
b.SignedBlockHeader = header
b.Index = uint64(j)
@@ -858,7 +859,7 @@ func TestSendBlobsByRangeRequest(t *testing.T) {
require.NoError(t, err)
prevRoot = bRoot
// Send the maximum possible blobs per slot.
- for j := 0; j < maxBlobsForSlot; j++ {
+ for j := range maxBlobsForSlot {
b := util.HydrateBlobSidecar(ðpb.BlobSidecar{})
b.SignedBlockHeader = header
b.Index = uint64(j)
diff --git a/beacon-chain/sync/rpc_status.go b/beacon-chain/sync/rpc_status.go
index fdb031a558..518eb3b956 100644
--- a/beacon-chain/sync/rpc_status.go
+++ b/beacon-chain/sync/rpc_status.go
@@ -236,7 +236,7 @@ func (s *Service) reValidatePeer(ctx context.Context, id peer.ID) error {
// statusRPCHandler reads the incoming Status RPC from the peer and responds with our version of a status message.
// This handler will disconnect any peer that does not match our fork version.
-func (s *Service) statusRPCHandler(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+func (s *Service) statusRPCHandler(ctx context.Context, msg any, stream libp2pcore.Stream) error {
ctx, cancel := context.WithTimeout(ctx, ttfbTimeout)
defer cancel()
SetRPCStreamDeadlines(stream)
@@ -424,7 +424,7 @@ func (s *Service) buildStatusFromEpoch(
return status, nil
}
-func (s *Service) validateStatusMessage(ctx context.Context, genericMsg interface{}) error {
+func (s *Service) validateStatusMessage(ctx context.Context, genericMsg any) error {
msg, err := statusV2(genericMsg)
if err != nil {
return errors.Wrap(err, "status data")
@@ -500,7 +500,7 @@ func (s *Service) validateStatusMessage(ctx context.Context, genericMsg interfac
return p2ptypes.ErrInvalidEpoch
}
-func statusV2(msg interface{}) (*pb.StatusV2, error) {
+func statusV2(msg any) (*pb.StatusV2, error) {
if status, ok := msg.(*pb.StatusV2); ok {
return status, nil
}
diff --git a/beacon-chain/sync/rpc_test.go b/beacon-chain/sync/rpc_test.go
index 242c09dc1b..85567d7113 100644
--- a/beacon-chain/sync/rpc_test.go
+++ b/beacon-chain/sync/rpc_test.go
@@ -59,7 +59,7 @@ func TestRegisterRPC_ReceivesValidMessage(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
topic := "/testing/foobar/1"
- handler := func(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+ handler := func(ctx context.Context, msg any, stream libp2pcore.Stream) error {
m, ok := msg.(*ethpb.Fork)
if !ok {
t.Error("Object is not of type *pb.TestSimpleMessage")
@@ -95,7 +95,7 @@ func TestRPC_ReceivesInvalidMessage(t *testing.T) {
}
topic := "/testing/foobar/1"
- handler := func(ctx context.Context, msg interface{}, stream libp2pcore.Stream) error {
+ handler := func(ctx context.Context, msg any, stream libp2pcore.Stream) error {
m, ok := msg.(*ethpb.Fork)
if !ok {
t.Error("Object is not of type *pb.Fork")
diff --git a/beacon-chain/sync/service.go b/beacon-chain/sync/service.go
index 1b389382e4..b4b74f71d3 100644
--- a/beacon-chain/sync/service.go
+++ b/beacon-chain/sync/service.go
@@ -212,7 +212,7 @@ func NewService(ctx context.Context, opts ...Option) *Service {
r.kzgChan = make(chan *kzgVerifier, 100)
// Correctly remove it from our seen pending block map.
// The eviction method always assumes that the mutex is held.
- r.slotToPendingBlocks.OnEvicted(func(s string, i interface{}) {
+ r.slotToPendingBlocks.OnEvicted(func(s string, i any) {
if !mutexasserts.RWMutexLocked(&r.pendingQueueLock) {
log.Errorf("Mutex is not locked during cache eviction of values")
// Continue on to allow elements to be properly removed.
diff --git a/beacon-chain/sync/service_test.go b/beacon-chain/sync/service_test.go
index 3bf8583c83..fefc0dd7f2 100644
--- a/beacon-chain/sync/service_test.go
+++ b/beacon-chain/sync/service_test.go
@@ -381,7 +381,7 @@ func TestService_Stop_ConcurrentGoodbyeMessages(t *testing.T) {
testPeers := make([]*p2ptest.TestP2P, numPeers)
// Create and connect multiple peers
- for i := 0; i < numPeers; i++ {
+ for i := range numPeers {
testPeers[i] = p2ptest.NewTestP2P(t)
p1.Connect(testPeers[i])
// Register peer in the peer status
@@ -419,7 +419,7 @@ func TestService_Stop_ConcurrentGoodbyeMessages(t *testing.T) {
var wg sync.WaitGroup
wg.Add(numPeers)
- for i := 0; i < numPeers; i++ {
+ for i := range numPeers {
idx := i // capture loop variable
testPeers[idx].BHost.SetStreamHandler(pcl, func(stream network.Stream) {
defer wg.Done()
diff --git a/beacon-chain/sync/slot_aware_cache.go b/beacon-chain/sync/slot_aware_cache.go
index d3d2b52a3d..4e55662ede 100644
--- a/beacon-chain/sync/slot_aware_cache.go
+++ b/beacon-chain/sync/slot_aware_cache.go
@@ -28,14 +28,14 @@ func newSlotAwareCache(size int) *slotAwareCache {
}
// Get retrieves a value from the cache.
-func (c *slotAwareCache) Get(key string) (interface{}, bool) {
+func (c *slotAwareCache) Get(key string) (any, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
return c.cache.Get(key)
}
// Add adds a value to the cache associated with a specific slot.
-func (c *slotAwareCache) Add(slot primitives.Slot, key string, value interface{}) {
+func (c *slotAwareCache) Add(slot primitives.Slot, key string, value any) {
c.mu.Lock()
defer c.mu.Unlock()
diff --git a/beacon-chain/sync/slot_aware_cache_test.go b/beacon-chain/sync/slot_aware_cache_test.go
index d1101486fe..45e75cc4ce 100644
--- a/beacon-chain/sync/slot_aware_cache_test.go
+++ b/beacon-chain/sync/slot_aware_cache_test.go
@@ -109,7 +109,7 @@ func TestSlotAwareCache(t *testing.T) {
cache := newSlotAwareCache(200000) // Large cache to avoid LRU eviction
// Add entries for 1005 slots, each with one key
- for i := 0; i < 1005; i++ {
+ for i := range 1005 {
slot := primitives.Slot(i)
key := fmt.Sprintf("key%d", i)
cache.Add(slot, key, fmt.Sprintf("value%d", i))
diff --git a/beacon-chain/sync/subscriber.go b/beacon-chain/sync/subscriber.go
index 9b53ae4e57..7882a81cb5 100644
--- a/beacon-chain/sync/subscriber.go
+++ b/beacon-chain/sync/subscriber.go
@@ -749,7 +749,7 @@ func (s *Service) filterNeededPeers(pids []peer.ID) []peer.ID {
wantedSubnets[subnet] = true
}
- topic := p2p.GossipTypeMapping[reflect.TypeOf(ðpb.Attestation{})]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.Attestation]()]
// Map of peers in subnets
peerMap := make(map[peer.ID]bool)
diff --git a/beacon-chain/sync/subscriber_beacon_blocks.go b/beacon-chain/sync/subscriber_beacon_blocks.go
index 44fde992f2..533d34b346 100644
--- a/beacon-chain/sync/subscriber_beacon_blocks.go
+++ b/beacon-chain/sync/subscriber_beacon_blocks.go
@@ -165,7 +165,7 @@ func (s *Service) processBlobSidecarsFromExecution(ctx context.Context, block in
// builds corresponding sidecars, save them to the storage, and broadcasts them over P2P if necessary.
func (s *Service) processDataColumnSidecarsFromExecution(ctx context.Context, source peerdas.ConstructionPopulator) error {
key := fmt.Sprintf("%#x", source.Root())
- if _, err, _ := s.columnSidecarsExecSingleFlight.Do(key, func() (interface{}, error) {
+ if _, err, _ := s.columnSidecarsExecSingleFlight.Do(key, func() (any, error) {
const delay = 250 * time.Millisecond
secondsPerHalfSlot := time.Duration(params.BeaconConfig().SecondsPerSlot/2) * time.Second
diff --git a/beacon-chain/sync/subscriber_test.go b/beacon-chain/sync/subscriber_test.go
index 3875dec4ef..9123958bd6 100644
--- a/beacon-chain/sync/subscriber_test.go
+++ b/beacon-chain/sync/subscriber_test.go
@@ -262,7 +262,7 @@ func TestSubscribe_HandlesPanic(t *testing.T) {
nse := params.GetNetworkScheduleEntry(r.cfg.clock.CurrentEpoch())
p.Digest = nse.ForkDigest
- topic := p2p.GossipTypeMapping[reflect.TypeOf(&pb.SignedVoluntaryExit{})]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*pb.SignedVoluntaryExit]()]
var wg sync.WaitGroup
wg.Add(1)
diff --git a/beacon-chain/sync/sync_fuzz_test.go b/beacon-chain/sync/sync_fuzz_test.go
index db23ebc154..6d11ce41bb 100644
--- a/beacon-chain/sync/sync_fuzz_test.go
+++ b/beacon-chain/sync/sync_fuzz_test.go
@@ -79,7 +79,7 @@ func FuzzValidateBeaconBlockPubSub_Phase0(f *testing.F) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(f, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(f, err)
topic = r.addDigestToTopic(topic, digest)
@@ -163,7 +163,7 @@ func FuzzValidateBeaconBlockPubSub_Altair(f *testing.F) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(f, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(f, err)
topic = r.addDigestToTopic(topic, digest)
@@ -247,7 +247,7 @@ func FuzzValidateBeaconBlockPubSub_Bellatrix(f *testing.F) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(f, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(f, err)
topic = r.addDigestToTopic(topic, digest)
diff --git a/beacon-chain/sync/validate_aggregate_proof_test.go b/beacon-chain/sync/validate_aggregate_proof_test.go
index bdb82a549a..7fa4fdddea 100644
--- a/beacon-chain/sync/validate_aggregate_proof_test.go
+++ b/beacon-chain/sync/validate_aggregate_proof_test.go
@@ -230,7 +230,7 @@ func TestValidateAggregateAndProof_NoBlock(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -300,7 +300,7 @@ func TestValidateAggregateAndProof_NotWithinSlotRange(t *testing.T) {
_, err = p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -382,7 +382,7 @@ func TestValidateAggregateAndProof_ExistedInPool(t *testing.T) {
_, err = p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -485,7 +485,7 @@ func TestValidateAggregateAndProof_CanValidate(t *testing.T) {
_, err = p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -588,7 +588,7 @@ func TestVerifyIndexInCommittee_SeenAggregatorEpoch(t *testing.T) {
_, err = p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -700,7 +700,7 @@ func TestValidateAggregateAndProof_BadBlock(t *testing.T) {
_, err = p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -790,7 +790,7 @@ func TestValidateAggregateAndProof_RejectWhenAttEpochDoesntEqualTargetEpoch(t *t
_, err = p.Encoding().EncodeGossip(buf, signedAggregateAndProof)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(signedAggregateAndProof)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedAggregateAttestationAndProof]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
diff --git a/beacon-chain/sync/validate_attester_slashing_test.go b/beacon-chain/sync/validate_attester_slashing_test.go
index 48a8de8e88..64839764bb 100644
--- a/beacon-chain/sync/validate_attester_slashing_test.go
+++ b/beacon-chain/sync/validate_attester_slashing_test.go
@@ -98,7 +98,7 @@ func TestValidateAttesterSlashing_ValidSlashing(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashing]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -143,7 +143,7 @@ func TestValidateAttesterSlashing_ValidOldSlashing(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashing]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -188,7 +188,7 @@ func TestValidateAttesterSlashing_InvalidSlashing_WithdrawableEpoch(t *testing.T
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashing]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -237,7 +237,7 @@ func TestValidateAttesterSlashing_CanFilter(t *testing.T) {
r.setAttesterSlashingIndicesSeen([]uint64{1, 2, 3, 4}, []uint64{3, 4, 5, 6})
// The below attestations should be filtered hence bad signature is ok.
- topic := p2p.GossipTypeMapping[reflect.TypeOf(ðpb.AttesterSlashing{})]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashing]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -308,7 +308,7 @@ func TestValidateAttesterSlashing_ContextTimeout(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashing]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -339,7 +339,7 @@ func TestValidateAttesterSlashing_Syncing(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.AttesterSlashing]()]
msg := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
diff --git a/beacon-chain/sync/validate_beacon_attestation.go b/beacon-chain/sync/validate_beacon_attestation.go
index 2384bd7773..e8daa29b62 100644
--- a/beacon-chain/sync/validate_beacon_attestation.go
+++ b/beacon-chain/sync/validate_beacon_attestation.go
@@ -156,7 +156,7 @@ func (s *Service) validateCommitteeIndexBeaconAttestation(
var (
attForValidation eth.Att // what we'll pass to further validation
eventType feed.EventType
- eventData interface{}
+ eventData any
)
if att.Version() >= version.Electra {
@@ -240,7 +240,7 @@ func (s *Service) validateUnaggregatedAttTopic(ctx context.Context, a eth.Att, b
return result, err
}
subnet := helpers.ComputeSubnetForAttestation(valCount, a)
- format := p2p.GossipTypeMapping[reflect.TypeOf(ð.Attestation{})]
+ format := p2p.GossipTypeMapping[reflect.TypeFor[*eth.Attestation]()]
digest, err := s.currentForkDigest()
if err != nil {
tracing.AnnotateError(span, err)
diff --git a/beacon-chain/sync/validate_beacon_blocks_test.go b/beacon-chain/sync/validate_beacon_blocks_test.go
index 00db86133c..b01e965451 100644
--- a/beacon-chain/sync/validate_beacon_blocks_test.go
+++ b/beacon-chain/sync/validate_beacon_blocks_test.go
@@ -94,7 +94,7 @@ func TestValidateBeaconBlockPubSub_InvalidSignature(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -166,7 +166,7 @@ func TestValidateBeaconBlockPubSub_InvalidSignature_MarksBlockAsBad(t *testing.T
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -213,7 +213,7 @@ func TestValidateBeaconBlockPubSub_BlockAlreadyPresentInDB(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -276,7 +276,7 @@ func TestValidateBeaconBlockPubSub_CanRecoverStateSummary(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -342,7 +342,7 @@ func TestValidateBeaconBlockPubSub_IsInCache(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -408,7 +408,7 @@ func TestValidateBeaconBlockPubSub_ValidProposerSignature(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -477,7 +477,7 @@ func TestValidateBeaconBlockPubSub_WithLookahead(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -545,7 +545,7 @@ func TestValidateBeaconBlockPubSub_AdvanceEpochsForState(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -591,7 +591,7 @@ func TestValidateBeaconBlockPubSub_Syncing(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -654,7 +654,7 @@ func TestValidateBeaconBlockPubSub_IgnoreAndQueueBlocksFromNearFuture(t *testing
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -705,7 +705,7 @@ func TestValidateBeaconBlockPubSub_RejectBlocksFromFuture(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -756,7 +756,7 @@ func TestValidateBeaconBlockPubSub_RejectBlocksFromThePast(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -835,7 +835,7 @@ func TestValidateBeaconBlockPubSub_SeenProposerSlot(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msgClone)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msgClone)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -895,7 +895,7 @@ func TestValidateBeaconBlockPubSub_FilterByFinalizedEpoch(t *testing.T) {
require.NoError(t, err)
digest, err := signing.ComputeForkDigest(params.BeaconConfig().GenesisForkVersion, params.BeaconConfig().GenesisValidatorsRoot[:])
assert.NoError(t, err)
- topic := fmt.Sprintf(p2p.GossipTypeMapping[reflect.TypeOf(b)], digest)
+ topic := fmt.Sprintf(p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()], digest)
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -976,7 +976,7 @@ func TestValidateBeaconBlockPubSub_ParentNotFinalizedDescendant(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -1042,7 +1042,7 @@ func TestValidateBeaconBlockPubSub_InvalidParentBlock(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -1136,7 +1136,7 @@ func TestValidateBeaconBlockPubSub_InsertValidPendingBlock(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -1221,7 +1221,7 @@ func TestValidateBeaconBlockPubSub_RejectBlocksFromBadParent(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlock]()]
digest, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, digest)
@@ -1326,7 +1326,7 @@ func TestValidateBeaconBlockPubSub_ValidExecutionPayload(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockBellatrix]()]
genesisValidatorsRoot := r.cfg.clock.GenesisValidatorsRoot()
BellatrixDigest, err := signing.ComputeForkDigest(params.BeaconConfig().BellatrixForkVersion, genesisValidatorsRoot[:])
require.NoError(t, err)
@@ -1398,7 +1398,7 @@ func TestValidateBeaconBlockPubSub_InvalidPayloadTimestamp(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockBellatrix]()]
genesisValidatorsRoot := r.cfg.clock.GenesisValidatorsRoot()
BellatrixDigest, err := signing.ComputeForkDigest(params.BeaconConfig().BellatrixForkVersion, genesisValidatorsRoot[:])
assert.NoError(t, err)
@@ -1564,7 +1564,7 @@ func Test_validateBeaconBlockProcessingWhenParentIsOptimistic(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockBellatrix]()]
genesisValidatorsRoot := r.cfg.clock.GenesisValidatorsRoot()
BellatrixDigest, err := signing.ComputeForkDigest(params.BeaconConfig().BellatrixForkVersion, genesisValidatorsRoot[:])
require.NoError(t, err)
diff --git a/beacon-chain/sync/validate_blob_test.go b/beacon-chain/sync/validate_blob_test.go
index db1ccccbcd..aa8f98ceb2 100644
--- a/beacon-chain/sync/validate_blob_test.go
+++ b/beacon-chain/sync/validate_blob_test.go
@@ -67,7 +67,7 @@ func TestValidateBlob_InvalidMessageType(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*eth.SignedBeaconBlock]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestToTopic(topic, digest)
@@ -126,7 +126,7 @@ func TestValidateBlob_AlreadySeenInCache(t *testing.T) {
_, err = p.Encoding().EncodeGossip(buf, b)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(b)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*eth.BlobSidecar]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestAndIndexToTopic(topic, digest, 0)
@@ -156,7 +156,7 @@ func TestValidateBlob_InvalidTopicIndex(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*eth.BlobSidecar]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestAndIndexToTopic(topic, digest, 1)
@@ -271,7 +271,7 @@ func TestValidateBlob_ErrorPathsWithMock(t *testing.T) {
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(msg)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*eth.BlobSidecar]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestAndIndexToTopic(topic, digest, 0)
diff --git a/beacon-chain/sync/validate_proposer_slashing_test.go b/beacon-chain/sync/validate_proposer_slashing_test.go
index 0d794435b0..70d0827874 100644
--- a/beacon-chain/sync/validate_proposer_slashing_test.go
+++ b/beacon-chain/sync/validate_proposer_slashing_test.go
@@ -31,7 +31,7 @@ import (
func setupValidProposerSlashing(t *testing.T) (*ethpb.ProposerSlashing, state.BeaconState) {
validators := make([]*ethpb.Validator, 100)
- for i := 0; i < len(validators); i++ {
+ for i := range validators {
validators[i] = ðpb.Validator{
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
Slashed: false,
@@ -41,7 +41,7 @@ func setupValidProposerSlashing(t *testing.T) (*ethpb.ProposerSlashing, state.Be
}
}
validatorBalances := make([]uint64, len(validators))
- for i := 0; i < len(validatorBalances); i++ {
+ for i := range validatorBalances {
validatorBalances[i] = params.BeaconConfig().MaxEffectiveBalance
}
@@ -129,7 +129,7 @@ func TestValidateProposerSlashing_ValidSlashing(t *testing.T) {
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.ProposerSlashing]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -172,7 +172,7 @@ func TestValidateProposerSlashing_ValidOldSlashing(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.ProposerSlashing]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -213,7 +213,7 @@ func TestValidateProposerSlashing_ContextTimeout(t *testing.T) {
buf := new(bytes.Buffer)
_, err = p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.ProposerSlashing]()]
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -243,7 +243,7 @@ func TestValidateProposerSlashing_Syncing(t *testing.T) {
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, slashing)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(slashing)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.ProposerSlashing]()]
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
diff --git a/beacon-chain/sync/validate_sync_committee_message.go b/beacon-chain/sync/validate_sync_committee_message.go
index 9c0ba0dff6..a8990ccb86 100644
--- a/beacon-chain/sync/validate_sync_committee_message.go
+++ b/beacon-chain/sync/validate_sync_committee_message.go
@@ -185,7 +185,7 @@ func (s *Service) rejectIncorrectSyncCommittee(
return pubsub.ValidationIgnore, err
}
- format := p2p.GossipTypeMapping[reflect.TypeOf(ðpb.SyncCommitteeMessage{})]
+ format := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SyncCommitteeMessage]()]
// Validate that the validator is in the correct committee.
subCommitteeSize := params.BeaconConfig().SyncCommitteeSize / params.BeaconConfig().SyncCommitteeSubnetCount
for _, idx := range committeeIndices {
diff --git a/beacon-chain/sync/validate_sync_committee_message_test.go b/beacon-chain/sync/validate_sync_committee_message_test.go
index c0dbafb499..b9ebe73247 100644
--- a/beacon-chain/sync/validate_sync_committee_message_test.go
+++ b/beacon-chain/sync/validate_sync_committee_message_test.go
@@ -425,7 +425,7 @@ func TestService_ValidateSyncCommitteeMessage(t *testing.T) {
ReceivedFrom: "",
ValidatorData: nil,
}
- for i := 0; i < 10; i++ {
+ for range 10 {
if !svc.chainIsStarted() {
time.Sleep(100 * time.Millisecond)
}
@@ -522,7 +522,7 @@ func TestService_rejectIncorrectSyncCommittee(t *testing.T) {
},
committeeIndices: []primitives.CommitteeIndex{0},
setupTopic: func(s *Service) string {
- format := p2p.GossipTypeMapping[reflect.TypeOf(ðpb.SyncCommitteeMessage{})]
+ format := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SyncCommitteeMessage]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
diff --git a/beacon-chain/sync/validate_sync_contribution_proof_test.go b/beacon-chain/sync/validate_sync_contribution_proof_test.go
index 33c6ad0e12..7e01b7f7b8 100644
--- a/beacon-chain/sync/validate_sync_contribution_proof_test.go
+++ b/beacon-chain/sync/validate_sync_contribution_proof_test.go
@@ -872,7 +872,7 @@ func TestService_ValidateSyncContributionAndProof(t *testing.T) {
// a lot happens in the chain service after SetClock is called,
// give it a moment before calling internal methods that would typically
// only execute after waitFor
- for i := 0; i < 10; i++ {
+ for range 10 {
if !svc.chainIsStarted() {
time.Sleep(100 * time.Millisecond)
}
diff --git a/beacon-chain/sync/validate_voluntary_exit_test.go b/beacon-chain/sync/validate_voluntary_exit_test.go
index 55cc06f1f5..1b6b15efc4 100644
--- a/beacon-chain/sync/validate_voluntary_exit_test.go
+++ b/beacon-chain/sync/validate_voluntary_exit_test.go
@@ -101,7 +101,7 @@ func TestValidateVoluntaryExit_ValidExit(t *testing.T) {
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, exit)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(exit)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedVoluntaryExit]()]
d, err := r.currentForkDigest()
assert.NoError(t, err)
topic = r.addDigestToTopic(topic, d)
@@ -158,7 +158,7 @@ func TestValidateVoluntaryExit_InvalidExitSlot(t *testing.T) {
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, exit)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(exit)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedVoluntaryExit]()]
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
@@ -189,7 +189,7 @@ func TestValidateVoluntaryExit_ValidExit_Syncing(t *testing.T) {
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, exit)
require.NoError(t, err)
- topic := p2p.GossipTypeMapping[reflect.TypeOf(exit)]
+ topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.SignedVoluntaryExit]()]
m := &pubsub.Message{
Message: &pubsubpb.Message{
Data: buf.Bytes(),
diff --git a/beacon-chain/verification/blob_test.go b/beacon-chain/verification/blob_test.go
index 88f8fe8790..441dda9a54 100644
--- a/beacon-chain/verification/blob_test.go
+++ b/beacon-chain/verification/blob_test.go
@@ -694,10 +694,7 @@ func sbrForValOverride(idx primitives.ValidatorIndex, val *ethpb.Validator) *moc
func sbrForValOverrideWithT(t testing.TB, idx primitives.ValidatorIndex, val *ethpb.Validator) *mockStateByRooter {
return &mockStateByRooter{sbr: func(_ context.Context, root [32]byte) (state.BeaconState, error) {
// Use a real deterministic state so that helpers.BeaconProposerIndexAtSlot works correctly
- numValidators := uint64(idx + 1)
- if numValidators < 64 {
- numValidators = 64
- }
+ numValidators := max(uint64(idx+1), 64)
var st state.BeaconState
var err error
@@ -769,10 +766,7 @@ func (v *validxStateOverride) Validators() []*ethpb.Validator {
}
}
// Ensure we have at least 64 validators for a valid beacon state
- numValidators := maxIdx + 1
- if numValidators < 64 {
- numValidators = 64
- }
+ numValidators := max(maxIdx+1, 64)
validators := make([]*ethpb.Validator, numValidators)
for i := range validators {
if val, ok := v.vals[primitives.ValidatorIndex(i)]; ok {
diff --git a/cache/lru/lru_wrpr.go b/cache/lru/lru_wrpr.go
index 5f261a2384..2cf965ef0e 100644
--- a/cache/lru/lru_wrpr.go
+++ b/cache/lru/lru_wrpr.go
@@ -17,7 +17,7 @@ func New(size int) *lru.Cache {
// NewWithEvict constructs a fixed size cache with the given eviction
// callback.
-func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) *lru.Cache {
+func NewWithEvict(size int, onEvicted func(key any, value any)) *lru.Cache {
cache, err := lru.NewWithEvict(size, onEvicted)
if err != nil {
panic(fmt.Errorf("lru new with evict failed: %w", err)) // lint:nopanic -- This should never panic.
diff --git a/cache/lru/lru_wrpr_test.go b/cache/lru/lru_wrpr_test.go
index 7e484e746d..a1ce199fa7 100644
--- a/cache/lru/lru_wrpr_test.go
+++ b/cache/lru/lru_wrpr_test.go
@@ -23,15 +23,15 @@ func TestNew_ZeroOrNegativeSize(t *testing.T) {
func TestNewWithEvict(t *testing.T) {
assert.NotPanics(t, func() {
- NewWithEvict(10, func(key interface{}, value interface{}) {})
+ NewWithEvict(10, func(key any, value any) {})
})
}
func TestNewWithEvict_ZeroOrNegativeSize(t *testing.T) {
assert.Panics(t, func() {
- NewWithEvict(0, func(key interface{}, value interface{}) {})
+ NewWithEvict(0, func(key any, value any) {})
})
assert.Panics(t, func() {
- NewWithEvict(-1, func(key interface{}, value interface{}) {})
+ NewWithEvict(-1, func(key any, value any) {})
})
}
diff --git a/cache/nonblocking/lru.go b/cache/nonblocking/lru.go
index e37f1022e7..79e5856a55 100644
--- a/cache/nonblocking/lru.go
+++ b/cache/nonblocking/lru.go
@@ -100,11 +100,8 @@ func (c *LRU[K, V]) Len() int {
// Resize changes the cache size.
func (c *LRU[K, V]) Resize(size int) (evicted int) {
- diff := c.Len() - size
- if diff < 0 {
- diff = 0
- }
- for i := 0; i < diff; i++ {
+ diff := max(c.Len()-size, 0)
+ for range diff {
c.removeOldest()
}
c.size = size
diff --git a/cache/nonblocking/lru_test.go b/cache/nonblocking/lru_test.go
index f909bf04c8..b5f56a9fd1 100644
--- a/cache/nonblocking/lru_test.go
+++ b/cache/nonblocking/lru_test.go
@@ -18,7 +18,7 @@ func TestLRU_Concurrency(t *testing.T) {
}
ctx, cancel := context.WithTimeout(t.Context(), time.Second*2)
defer cancel()
- for i := 0; i < 100; i++ {
+ for i := range 100 {
go func(j int) {
for {
if ctx.Err() != nil {
@@ -43,7 +43,7 @@ func TestLRU_Eviction(t *testing.T) {
if err != nil {
t.Fatalf("err: %v", err)
}
- for i := 0; i < 20; i++ {
+ for i := range 20 {
cache.Add(i, i)
cache.Get(i)
}
diff --git a/changelog/pvl-modernize.md b/changelog/pvl-modernize.md
new file mode 100644
index 0000000000..029ccdd1cd
--- /dev/null
+++ b/changelog/pvl-modernize.md
@@ -0,0 +1,4 @@
+### Ignored
+
+- Updated golang.org/x/tools
+- Introduced modernize static analyzers to nogo
diff --git a/cmd/beacon-chain/flags/api_module.go b/cmd/beacon-chain/flags/api_module.go
index 31dfe70765..2afad7f5d1 100644
--- a/cmd/beacon-chain/flags/api_module.go
+++ b/cmd/beacon-chain/flags/api_module.go
@@ -14,7 +14,7 @@ func EnableHTTPEthAPI(httpModules string) bool {
}
func enableAPI(httpModules, api string) bool {
- for _, m := range strings.Split(httpModules, ",") {
+ for m := range strings.SplitSeq(httpModules, ",") {
if strings.EqualFold(m, api) {
return true
}
diff --git a/cmd/beacon-chain/storage/options.go b/cmd/beacon-chain/storage/options.go
index a1134d444c..416ea30206 100644
--- a/cmd/beacon-chain/storage/options.go
+++ b/cmd/beacon-chain/storage/options.go
@@ -146,10 +146,8 @@ func detectLayout(dir string, c stringFlagGetter) (string, error) {
}
return "", errors.Wrap(err, "reading blob storage directory")
}
- for _, entry := range entries {
- if filesystem.IsBlockRootDir(entry) {
- return filesystem.LayoutNameFlat, nil
- }
+ if slices.ContainsFunc(entries, filesystem.IsBlockRootDir) {
+ return filesystem.LayoutNameFlat, nil
}
return filesystem.LayoutNameByEpoch, nil
}
diff --git a/cmd/beacon-chain/usage.go b/cmd/beacon-chain/usage.go
index af8b89400f..97c05c1efe 100644
--- a/cmd/beacon-chain/usage.go
+++ b/cmd/beacon-chain/usage.go
@@ -235,12 +235,12 @@ func init() {
cli.AppHelpTemplate = appHelpTemplate
type helpData struct {
- App interface{}
+ App any
FlagGroups []flagGroup
}
originalHelpPrinter := cli.HelpPrinter
- cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
+ cli.HelpPrinter = func(w io.Writer, tmpl string, data any) {
if tmpl == appHelpTemplate {
for _, group := range appHelpFlagGroups {
sort.Sort(cli.FlagsByName(group.Flags))
diff --git a/cmd/client-stats/usage.go b/cmd/client-stats/usage.go
index eefb511f71..062c5cda54 100644
--- a/cmd/client-stats/usage.go
+++ b/cmd/client-stats/usage.go
@@ -63,12 +63,12 @@ func init() {
cli.AppHelpTemplate = appHelpTemplate
type helpData struct {
- App interface{}
+ App any
FlagGroups []flagGroup
}
originalHelpPrinter := cli.HelpPrinter
- cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
+ cli.HelpPrinter = func(w io.Writer, tmpl string, data any) {
if tmpl == appHelpTemplate {
for _, group := range appHelpFlagGroups {
sort.Sort(cli.FlagsByName(group.Flags))
diff --git a/cmd/prysmctl/db/span.go b/cmd/prysmctl/db/span.go
index a855ea7043..87c84f7a65 100644
--- a/cmd/prysmctl/db/span.go
+++ b/cmd/prysmctl/db/span.go
@@ -164,10 +164,7 @@ func spanAction(cliCtx *cli.Context) error {
b := chunk.Chunk()
c := uint64(0)
for z := uint64(0); z < uint64(len(b)); z += params.ChunkSize() {
- end := z + params.ChunkSize()
- if end > uint64(len(b)) {
- end = uint64(len(b))
- }
+ end := min(z+params.ChunkSize(), uint64(len(b)))
subChunk := b[z:end]
row := make(table.Row, params.ChunkSize()+1)
@@ -191,10 +188,7 @@ func spanAction(cliCtx *cli.Context) error {
b := chunk.Chunk()
c := uint64(0)
for z := uint64(0); z < uint64(len(b)); z += params.ChunkSize() {
- end := z + params.ChunkSize()
- if end > uint64(len(b)) {
- end = uint64(len(b))
- }
+ end := min(z+params.ChunkSize(), uint64(len(b)))
subChunk := b[z:end]
row := make(table.Row, 2)
diff --git a/cmd/prysmctl/p2p/client.go b/cmd/prysmctl/p2p/client.go
index 808bc8d95e..b6c4833837 100644
--- a/cmd/prysmctl/p2p/client.go
+++ b/cmd/prysmctl/p2p/client.go
@@ -107,7 +107,7 @@ func (c *client) MetadataSeq() uint64 {
// When done, the caller must Close() or Reset() on the stream.
func (c *client) Send(
ctx context.Context,
- message interface{},
+ message any,
baseTopic string,
pid peer.ID,
) (corenet.Stream, error) {
@@ -154,7 +154,7 @@ func (c *client) retrievePeerAddressesViaRPC(ctx context.Context, beaconEndpoint
return nil, errors.New("no beacon RPC endpoints specified")
}
peers := make([]string, 0)
- for i := 0; i < len(beaconEndpoints); i++ {
+ for i := range beaconEndpoints {
conn, err := grpc.Dial(beaconEndpoints[i], grpc.WithInsecure())
if err != nil {
return nil, err
diff --git a/cmd/prysmctl/p2p/handler.go b/cmd/prysmctl/p2p/handler.go
index 3ad525de7d..668b713ed9 100644
--- a/cmd/prysmctl/p2p/handler.go
+++ b/cmd/prysmctl/p2p/handler.go
@@ -15,7 +15,7 @@ import (
ssz "github.com/prysmaticlabs/fastssz"
)
-type rpcHandler func(context.Context, interface{}, libp2pcore.Stream) error
+type rpcHandler func(context.Context, any, libp2pcore.Stream) error
// registerRPC for a given topic with an expected protobuf message type.
func (c *client) registerRPCHandler(baseTopic string, handle rpcHandler) {
diff --git a/cmd/prysmctl/p2p/handshake.go b/cmd/prysmctl/p2p/handshake.go
index 7d38e7f0f9..a9d05f3480 100644
--- a/cmd/prysmctl/p2p/handshake.go
+++ b/cmd/prysmctl/p2p/handshake.go
@@ -22,7 +22,7 @@ func (c *client) registerHandshakeHandlers() {
}
// pingHandler reads the incoming ping rpc message from the peer.
-func (c *client) pingHandler(_ context.Context, _ interface{}, stream libp2pcore.Stream) error {
+func (c *client) pingHandler(_ context.Context, _ any, stream libp2pcore.Stream) error {
defer closeStream(stream)
if _, err := stream.Write([]byte{responseCodeSuccess}); err != nil {
return err
@@ -34,13 +34,13 @@ func (c *client) pingHandler(_ context.Context, _ interface{}, stream libp2pcore
return nil
}
-func (c *client) goodbyeHandler(_ context.Context, _ interface{}, _ libp2pcore.Stream) error {
+func (c *client) goodbyeHandler(_ context.Context, _ any, _ libp2pcore.Stream) error {
return nil
}
// statusRPCHandler reads the incoming Status RPC from the peer and responds with our version of a status message.
// This handler will disconnect any peer that does not match our fork version.
-func (c *client) statusRPCHandler(ctx context.Context, _ interface{}, stream libp2pcore.Stream) error {
+func (c *client) statusRPCHandler(ctx context.Context, _ any, stream libp2pcore.Stream) error {
defer closeStream(stream)
chainHead, err := c.beaconClient.GetChainHead(ctx, &emptypb.Empty{})
if err != nil {
diff --git a/cmd/prysmctl/p2p/request_blobs.go b/cmd/prysmctl/p2p/request_blobs.go
index 065f2c8b54..c90e0cfd45 100644
--- a/cmd/prysmctl/p2p/request_blobs.go
+++ b/cmd/prysmctl/p2p/request_blobs.go
@@ -125,7 +125,7 @@ func cliActionRequestBlobs(cliCtx *cli.Context) error {
c.registerHandshakeHandlers()
c.registerRPCHandler(p2p.RPCBlobSidecarsByRangeTopicV1, func(
- ctx context.Context, i interface{}, stream libp2pcore.Stream,
+ ctx context.Context, i any, stream libp2pcore.Stream,
) error {
return nil
})
diff --git a/cmd/prysmctl/p2p/request_blocks.go b/cmd/prysmctl/p2p/request_blocks.go
index a153664290..566374215e 100644
--- a/cmd/prysmctl/p2p/request_blocks.go
+++ b/cmd/prysmctl/p2p/request_blocks.go
@@ -159,12 +159,12 @@ func cliActionRequestBlocks(cliCtx *cli.Context) error {
c.registerHandshakeHandlers()
c.registerRPCHandler(p2p.RPCBlocksByRangeTopicV1, func(
- ctx context.Context, i interface{}, stream libp2pcore.Stream,
+ ctx context.Context, i any, stream libp2pcore.Stream,
) error {
return nil
})
c.registerRPCHandler(p2p.RPCBlocksByRangeTopicV2, func(
- ctx context.Context, i interface{}, stream libp2pcore.Stream,
+ ctx context.Context, i any, stream libp2pcore.Stream,
) error {
return nil
})
diff --git a/cmd/prysmctl/testnet/generate_genesis.go b/cmd/prysmctl/testnet/generate_genesis.go
index d491f83018..3508c7ab01 100644
--- a/cmd/prysmctl/testnet/generate_genesis.go
+++ b/cmd/prysmctl/testnet/generate_genesis.go
@@ -188,7 +188,7 @@ func cliActionGenerateGenesisState(cliCtx *cli.Context) error {
type MinimumSSZMarshal interface {
MarshalSSZ() ([]byte, error)
}
- marshalFn := func(o interface{}) ([]byte, error) {
+ marshalFn := func(o any) ([]byte, error) {
marshaler, ok := o.(MinimumSSZMarshal)
if !ok {
return nil, errors.New("not a marshaler")
@@ -397,8 +397,8 @@ func depositJSONToDepositData(input *depositDataJSON) ([]byte, *ethpb.Deposit_Da
func writeToOutputFile(
fPath string,
- data interface{},
- marshalFn func(o interface{}) ([]byte, error),
+ data any,
+ marshalFn func(o any) ([]byte, error),
) error {
encoded, err := marshalFn(data)
if err != nil {
diff --git a/cmd/prysmctl/testnet/generate_genesis_test.go b/cmd/prysmctl/testnet/generate_genesis_test.go
index b0d7312b9c..87763b538d 100644
--- a/cmd/prysmctl/testnet/generate_genesis_test.go
+++ b/cmd/prysmctl/testnet/generate_genesis_test.go
@@ -33,7 +33,7 @@ func Test_genesisStateFromJSONValidators(t *testing.T) {
func createGenesisDepositData(t *testing.T, numKeys int) []*depositDataJSON {
pubKeys := make([]bls.PublicKey, numKeys)
privKeys := make([]bls.SecretKey, numKeys)
- for i := 0; i < numKeys; i++ {
+ for i := range numKeys {
randKey, err := bls.RandKey()
require.NoError(t, err)
privKeys[i] = randKey
@@ -42,7 +42,7 @@ func createGenesisDepositData(t *testing.T, numKeys int) []*depositDataJSON {
dataList, _, err := interop.DepositDataFromKeys(privKeys, pubKeys)
require.NoError(t, err)
jsonData := make([]*depositDataJSON, numKeys)
- for i := 0; i < numKeys; i++ {
+ for i := range numKeys {
dataRoot, err := dataList[i].HashTreeRoot()
require.NoError(t, err)
jsonData[i] = &depositDataJSON{
diff --git a/cmd/prysmctl/validator/withdraw.go b/cmd/prysmctl/validator/withdraw.go
index e5161e8fe3..eebf761146 100644
--- a/cmd/prysmctl/validator/withdraw.go
+++ b/cmd/prysmctl/validator/withdraw.go
@@ -96,7 +96,7 @@ func callWithdrawalEndpoints(ctx context.Context, host string, request []*struct
if err != nil {
return err
}
- data, ok := spec.Data.(map[string]interface{})
+ data, ok := spec.Data.(map[string]any)
if !ok {
return errors.New("config has incorrect structure")
}
diff --git a/cmd/validator/flags/flags_test.go b/cmd/validator/flags/flags_test.go
index a4a3b57876..ab3860d8d3 100644
--- a/cmd/validator/flags/flags_test.go
+++ b/cmd/validator/flags/flags_test.go
@@ -20,9 +20,9 @@ func TestLoadFlagsFromConfig_PreProcessing_Web3signer(t *testing.T) {
pubkey1 := "0xbd36226746676565cd40141a7f0fe1445b9a3fbeb222288b226392c4b230ed0b"
pubkey2 := "0xbd36226746676565cd40141a7f0fe1445b9a3fbeb222288b226392c4b230ed0a"
- require.NoError(t, os.WriteFile("flags_test.yaml", []byte(fmt.Sprintf("%s:\n - %s\n - %s\n", Web3SignerPublicValidatorKeysFlag.Name,
+ require.NoError(t, os.WriteFile("flags_test.yaml", fmt.Appendf(nil, "%s:\n - %s\n - %s\n", Web3SignerPublicValidatorKeysFlag.Name,
pubkey1,
- pubkey2)), 0666))
+ pubkey2), 0666))
require.NoError(t, set.Parse([]string{"test-command", "--" + cmd.ConfigFileFlag.Name, "flags_test.yaml"}))
comFlags := cmd.WrapFlags([]cli.Flag{
diff --git a/cmd/validator/slashing-protection/import_export_test.go b/cmd/validator/slashing-protection/import_export_test.go
index a25b490250..c4aca04cc2 100644
--- a/cmd/validator/slashing-protection/import_export_test.go
+++ b/cmd/validator/slashing-protection/import_export_test.go
@@ -155,7 +155,7 @@ func TestImportExportSlashingProtectionCli_EmptyData(t *testing.T) {
require.NoError(t, err)
attestingHistory := make([][]*common.AttestationRecord, 0)
proposalHistory := make([]common.ProposalHistoryForPubkey, len(pubKeys))
- for i := 0; i < len(pubKeys); i++ {
+ for i := range pubKeys {
proposalHistory[i].Proposals = make([]common.Proposal, 0)
}
mockJSON, err := mocks.MockSlashingProtectionJSON(pubKeys, attestingHistory, proposalHistory)
diff --git a/cmd/validator/usage.go b/cmd/validator/usage.go
index 5ad08d0fe6..8e2f4fed1b 100644
--- a/cmd/validator/usage.go
+++ b/cmd/validator/usage.go
@@ -166,12 +166,12 @@ func init() {
cli.AppHelpTemplate = appHelpTemplate
type helpData struct {
- App interface{}
+ App any
FlagGroups []flagGroup
}
originalHelpPrinter := cli.HelpPrinter
- cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
+ cli.HelpPrinter = func(w io.Writer, tmpl string, data any) {
if tmpl == appHelpTemplate {
for _, group := range appHelpFlagGroups {
sort.Sort(cli.FlagsByName(group.Flags))
diff --git a/config/params/config_test.go b/config/params/config_test.go
index c7102955b1..15d9dbdffa 100644
--- a/config/params/config_test.go
+++ b/config/params/config_test.go
@@ -46,7 +46,7 @@ func TestConfig_OverrideBeaconConfigTestTeardown(t *testing.T) {
func TestConfig_DataRace(t *testing.T) {
params.SetupTestConfigCleanup(t)
wg := new(sync.WaitGroup)
- for i := 0; i < 10; i++ {
+ for range 10 {
wg.Add(2)
go func() {
defer wg.Done()
@@ -197,7 +197,7 @@ func Test_TargetBlobCount(t *testing.T) {
func fillGVR(value byte) [32]byte {
var gvr [32]byte
- for i := 0; i < len(gvr); i++ {
+ for i := range len(gvr) {
gvr[i] = value
}
return gvr
@@ -232,7 +232,6 @@ func TestBeaconChainConfigSlotDuration(t *testing.T) {
}
for _, tt := range tests {
- tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, tt.cfg.SlotDuration())
@@ -266,7 +265,6 @@ func TestBeaconChainConfigSlotDurationMillis(t *testing.T) {
}
for _, tt := range tests {
- tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, tt.cfg.SlotDurationMillis())
@@ -316,7 +314,6 @@ func TestBeaconChainConfigSlotComponentDuration(t *testing.T) {
}
for _, tt := range tests {
- tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, tt.want, tt.cfg.SlotComponentDuration(tt.bp))
diff --git a/config/params/loader_test.go b/config/params/loader_test.go
index fd75ed892d..476c46069b 100644
--- a/config/params/loader_test.go
+++ b/config/params/loader_test.go
@@ -401,7 +401,7 @@ func fieldsFromYamls(t *testing.T, fps []string) []string {
for _, fp := range fps {
yamlFile, err := os.ReadFile(fp)
require.NoError(t, err)
- m := make(map[string]interface{})
+ m := make(map[string]any)
require.NoError(t, yaml.Unmarshal(yamlFile, &m))
for k := range m {
@@ -421,7 +421,7 @@ func fieldsFromYamls(t *testing.T, fps []string) []string {
func assertYamlFieldsMatch(t *testing.T, name string, fields []string, c1, c2 *params.BeaconChainConfig) {
// Ensure all fields from the yaml file exist, were set, and correctly match the expected value.
- ft1 := reflect.TypeOf(*c1)
+ ft1 := reflect.TypeFor[params.BeaconChainConfig]()
for _, field := range fields {
var found bool
for i := 0; i < ft1.NumField(); i++ {
diff --git a/config/util.go b/config/util.go
index 6c69aecfe8..54eb90d16a 100644
--- a/config/util.go
+++ b/config/util.go
@@ -16,7 +16,7 @@ import (
"k8s.io/apimachinery/pkg/util/yaml"
)
-func UnmarshalFromURL(ctx context.Context, from string, to interface{}) error {
+func UnmarshalFromURL(ctx context.Context, from string, to any) error {
u, err := url.ParseRequestURI(from)
if err != nil {
return err
@@ -48,7 +48,7 @@ func UnmarshalFromURL(ctx context.Context, from string, to interface{}) error {
return nil
}
-func UnmarshalFromFile(from string, to interface{}) error {
+func UnmarshalFromFile(from string, to any) error {
cleanpath := filepath.Clean(from)
b, err := os.ReadFile(cleanpath)
if err != nil {
diff --git a/consensus-types/blocks/factory.go b/consensus-types/blocks/factory.go
index c518845459..74a21d970a 100644
--- a/consensus-types/blocks/factory.go
+++ b/consensus-types/blocks/factory.go
@@ -30,7 +30,7 @@ var (
)
// NewSignedBeaconBlock creates a signed beacon block from a protobuf signed beacon block.
-func NewSignedBeaconBlock(i interface{}) (interfaces.SignedBeaconBlock, error) {
+func NewSignedBeaconBlock(i any) (interfaces.SignedBeaconBlock, error) {
switch b := i.(type) {
case nil:
return nil, ErrNilObject
@@ -88,7 +88,7 @@ func NewSignedBeaconBlock(i interface{}) (interfaces.SignedBeaconBlock, error) {
}
// NewBeaconBlock creates a beacon block from a protobuf beacon block.
-func NewBeaconBlock(i interface{}) (interfaces.ReadOnlyBeaconBlock, error) {
+func NewBeaconBlock(i any) (interfaces.ReadOnlyBeaconBlock, error) {
switch b := i.(type) {
case nil:
return nil, ErrNilObject
@@ -144,7 +144,7 @@ func NewBeaconBlock(i interface{}) (interfaces.ReadOnlyBeaconBlock, error) {
}
// NewBeaconBlockBody creates a beacon block body from a protobuf beacon block body.
-func NewBeaconBlockBody(i interface{}) (interfaces.ReadOnlyBeaconBlockBody, error) {
+func NewBeaconBlockBody(i any) (interfaces.ReadOnlyBeaconBlockBody, error) {
switch b := i.(type) {
case nil:
return nil, ErrNilObject
@@ -265,7 +265,7 @@ func BuildSignedBeaconBlock(blk interfaces.ReadOnlyBeaconBlock, signature []byte
}
}
-func getWrappedPayload(payload interface{}) (wrappedPayload interfaces.ExecutionData, wrapErr error) {
+func getWrappedPayload(payload any) (wrappedPayload interfaces.ExecutionData, wrapErr error) {
switch p := payload.(type) {
case *enginev1.ExecutionPayload:
wrappedPayload, wrapErr = WrappedExecutionPayload(p)
@@ -308,7 +308,7 @@ func checkPayloadAgainstHeader(wrappedPayload, payloadHeader interfaces.Executio
// BuildSignedBeaconBlockFromExecutionPayload takes a signed, blinded beacon block and converts into
// a full, signed beacon block by specifying an execution payload.
// nolint:gocognit
-func BuildSignedBeaconBlockFromExecutionPayload(blk interfaces.ReadOnlySignedBeaconBlock, payload interface{}) (interfaces.SignedBeaconBlock, error) {
+func BuildSignedBeaconBlockFromExecutionPayload(blk interfaces.ReadOnlySignedBeaconBlock, payload any) (interfaces.SignedBeaconBlock, error) {
if err := BeaconBlockIsNil(blk); err != nil {
return nil, err
}
@@ -338,7 +338,7 @@ func BuildSignedBeaconBlockFromExecutionPayload(blk interfaces.ReadOnlySignedBea
graffiti := b.Body().Graffiti()
sig := blk.Signature()
- var fullBlock interface{}
+ var fullBlock any
switch blk.Version() {
case version.Bellatrix:
p, ok := payload.(*enginev1.ExecutionPayload)
diff --git a/consensus-types/blocks/kzg_test.go b/consensus-types/blocks/kzg_test.go
index 2349b379a3..982c6ff84b 100644
--- a/consensus-types/blocks/kzg_test.go
+++ b/consensus-types/blocks/kzg_test.go
@@ -230,8 +230,8 @@ func Benchmark_MerkleProofKZGCommitment(b *testing.B) {
body, err := NewBeaconBlockBody(pbBody)
require.NoError(b, err)
index := 1
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
_, err := MerkleProofKZGCommitment(body, index)
require.NoError(b, err)
}
diff --git a/consensus-types/hdiff/fuzz_test.go b/consensus-types/hdiff/fuzz_test.go
index a0f692f681..b387e0a512 100644
--- a/consensus-types/hdiff/fuzz_test.go
+++ b/consensus-types/hdiff/fuzz_test.go
@@ -444,14 +444,14 @@ func FuzzKmpIndex(f *testing.F) {
// Parse comma-separated strings into int slices
var source, target []int
if sourceStr != "" {
- for _, s := range strings.Split(sourceStr, ",") {
+ for s := range strings.SplitSeq(sourceStr, ",") {
if val, err := strconv.Atoi(strings.TrimSpace(s)); err == nil {
source = append(source, val)
}
}
}
if targetStr != "" {
- for _, s := range strings.Split(targetStr, ",") {
+ for s := range strings.SplitSeq(targetStr, ",") {
if val, err := strconv.Atoi(strings.TrimSpace(s)); err == nil {
target = append(target, val)
}
@@ -508,7 +508,7 @@ func FuzzComputeLPS(f *testing.F) {
// Parse comma-separated string into int slice
var pattern []int
if patternStr != "" {
- for _, s := range strings.Split(patternStr, ",") {
+ for s := range strings.SplitSeq(patternStr, ",") {
if val, err := strconv.Atoi(strings.TrimSpace(s)); err == nil {
pattern = append(pattern, val)
}
diff --git a/consensus-types/hdiff/property_test.go b/consensus-types/hdiff/property_test.go
index 1130e77ecc..3e15d90d26 100644
--- a/consensus-types/hdiff/property_test.go
+++ b/consensus-types/hdiff/property_test.go
@@ -47,10 +47,11 @@ func FuzzPropertyRoundTrip(f *testing.F) {
ctx := t.Context()
// Create source state with reasonable size
- validatorCount := uint64(len(validatorChanges) + 8) // Minimum 8 validators
- if validatorCount > 64 {
- validatorCount = 64 // Cap at 64 for performance
- }
+ validatorCount := min(
+ // Minimum 8 validators
+ uint64(len(validatorChanges)+8),
+ // Cap at 64 for performance
+ 64)
source, _ := util.DeterministicGenesisStateElectra(t, validatorCount)
// Create target state with modifications
diff --git a/consensus-types/hdiff/security_test.go b/consensus-types/hdiff/security_test.go
index b108e78507..effb01ec70 100644
--- a/consensus-types/hdiff/security_test.go
+++ b/consensus-types/hdiff/security_test.go
@@ -63,7 +63,7 @@ func TestReasonablePerformance(t *testing.T) {
// Make realistic changes
_ = target.SetSlot(source.Slot() + 32) // One epoch
validators := target.Validators()
- for i := 0; i < 100; i++ { // 10% of validators changed
+ for i := range 100 { // 10% of validators changed
validators[i].EffectiveBalance += 1000000000 // 1 ETH change
}
_ = target.SetValidators(validators)
@@ -129,7 +129,7 @@ func TestStateTransitionValidation(t *testing.T) {
// Some validators get rewards, others get penalties
balances := target.Balances()
- for i := 0; i < len(balances); i++ {
+ for i := range balances {
if i%2 == 0 {
balances[i] += 100000000 // 0.1 ETH reward
} else {
@@ -331,12 +331,12 @@ func TestConcurrencySafety(t *testing.T) {
var wg sync.WaitGroup
errors := make(chan error, numGoroutines*iterations)
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
- for j := 0; j < iterations; j++ {
+ for j := range iterations {
_, err := Diff(source, target)
if err != nil {
errors <- fmt.Errorf("worker %d iteration %d: %v", workerID, j, err)
@@ -367,7 +367,7 @@ func TestConcurrencySafety(t *testing.T) {
var wg sync.WaitGroup
errors := make(chan error, numGoroutines)
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
diff --git a/consensus-types/hdiff/state_diff.go b/consensus-types/hdiff/state_diff.go
index 3d88541576..2ae3b59718 100644
--- a/consensus-types/hdiff/state_diff.go
+++ b/consensus-types/hdiff/state_diff.go
@@ -752,7 +752,7 @@ func (ret *stateDiff) readProposerLookahead(data *[]byte) error {
// Read the proposer lookahead (2 * SlotsPerEpoch uint64 values)
numProposers := 2 * fieldparams.SlotsPerEpoch
ret.proposerLookahead = make([]uint64, numProposers)
- for i := 0; i < numProposers; i++ {
+ for i := range numProposers {
ret.proposerLookahead[i] = binary.LittleEndian.Uint64((*data)[i*8 : (i+1)*8])
}
*data = (*data)[proposerLookaheadLength:]
diff --git a/consensus-types/hdiff/state_diff_test.go b/consensus-types/hdiff/state_diff_test.go
index 082425ee10..083db65e43 100644
--- a/consensus-types/hdiff/state_diff_test.go
+++ b/consensus-types/hdiff/state_diff_test.go
@@ -42,7 +42,7 @@ func Test_diffToState(t *testing.T) {
func Test_kmpIndex(t *testing.T) {
intSlice := make([]*int, 10)
- for i := 0; i < len(intSlice); i++ {
+ for i := range intSlice {
intSlice[i] = new(int)
*intSlice[i] = i
}
@@ -544,7 +544,7 @@ func Test_diffToBalances(t *testing.T) {
}
targetBals := target.Balances()
- for i := 0; i < len(sourceBals); i++ {
+ for i := range sourceBals {
require.Equal(t, targetBals[i], sourceBals[i], "balance mismatch at index %d", i)
}
})
@@ -665,7 +665,7 @@ func Test_applyStateDiff(t *testing.T) {
// Test_computeLPS tests the LPS array computation for KMP algorithm
func Test_computeLPS(t *testing.T) {
intSlice := make([]*int, 10)
- for i := 0; i < len(intSlice); i++ {
+ for i := range intSlice {
intSlice[i] = new(int)
*intSlice[i] = i
}
@@ -955,8 +955,7 @@ func BenchmarkGetDiff(b *testing.B) {
source, target, err := getMainnetStates()
require.NoError(b, err)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
hdiff, err := Diff(source, target)
b.Log("Diff size:", len(hdiff.StateDiff)+len(hdiff.BalancesDiff)+len(hdiff.ValidatorDiffs))
require.NoError(b, err)
@@ -971,8 +970,8 @@ func BenchmarkApplyDiff(b *testing.B) {
require.NoError(b, err)
hdiff, err := Diff(source, target)
require.NoError(b, err)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
source, err = ApplyDiff(b.Context(), source, hdiff)
require.NoError(b, err)
}
@@ -998,7 +997,7 @@ func BenchmarkDiffCreation(b *testing.B) {
_ = target.SetValidators(validators)
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := Diff(source, target)
if err != nil {
b.Fatal(err)
@@ -1026,7 +1025,7 @@ func BenchmarkDiffApplication(b *testing.B) {
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
// Need fresh source for each iteration
freshSource := source.Copy()
_, err := ApplyDiff(ctx, freshSource, diff)
@@ -1049,8 +1048,7 @@ func BenchmarkSerialization(b *testing.B) {
b.Fatal(err)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = hdiff.serialize()
}
}
@@ -1067,8 +1065,7 @@ func BenchmarkDeserialization(b *testing.B) {
b.Fatal(err)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := newHdiff(diff)
if err != nil {
b.Fatal(err)
@@ -1093,7 +1090,7 @@ func BenchmarkBalanceDiff(b *testing.B) {
_ = target.SetBalances(balances)
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := diffToBalances(source, target)
if err != nil {
b.Fatal(err)
@@ -1123,7 +1120,7 @@ func BenchmarkValidatorDiff(b *testing.B) {
_ = target.SetValidators(validators)
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := diffToVals(source, target)
if err != nil {
b.Fatal(err)
@@ -1172,7 +1169,7 @@ func BenchmarkKMPAlgorithm(b *testing.B) {
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = kmpIndex(len(pattern), text, intEquals)
}
})
@@ -1201,7 +1198,7 @@ func BenchmarkCompressionRatio(b *testing.B) {
name: "balance_changes",
modifier: func(target state.BeaconState) {
balances := target.Balances()
- for i := 0; i < 10; i++ {
+ for i := range 10 {
if i < len(balances) {
balances[i] += 1000
}
@@ -1213,7 +1210,7 @@ func BenchmarkCompressionRatio(b *testing.B) {
name: "validator_changes",
modifier: func(target state.BeaconState) {
validators := target.Validators()
- for i := 0; i < 10; i++ {
+ for i := range 10 {
if i < len(validators) {
validators[i].EffectiveBalance += 1000
}
@@ -1235,7 +1232,7 @@ func BenchmarkCompressionRatio(b *testing.B) {
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
diff, err := Diff(source, testTarget)
if err != nil {
b.Fatal(err)
@@ -1262,7 +1259,7 @@ func BenchmarkMemoryUsage(b *testing.B) {
// Modify some data
validators := target.Validators()
- for i := 0; i < 25; i++ {
+ for i := range 25 {
if i < len(validators) {
validators[i].EffectiveBalance += 1000
}
@@ -1270,9 +1267,8 @@ func BenchmarkMemoryUsage(b *testing.B) {
_ = target.SetValidators(validators)
b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
diff, err := Diff(source, target)
if err != nil {
b.Fatal(err)
diff --git a/consensus-types/payload-attribute/types.go b/consensus-types/payload-attribute/types.go
index 563f7dff8b..da2b5730b4 100644
--- a/consensus-types/payload-attribute/types.go
+++ b/consensus-types/payload-attribute/types.go
@@ -30,7 +30,7 @@ var (
)
// New returns a new payload attribute with the given input object.
-func New(i interface{}) (Attributer, error) {
+func New(i any) (Attributer, error) {
switch a := i.(type) {
case nil:
return nil, blocks.ErrNilObject
diff --git a/consensus-types/validator/custom_types.go b/consensus-types/validator/custom_types.go
index 8f00ed9583..b29e89e4de 100644
--- a/consensus-types/validator/custom_types.go
+++ b/consensus-types/validator/custom_types.go
@@ -25,7 +25,7 @@ func (u *Uint64) UnmarshalJSON(bs []byte) error {
}
// UnmarshalYAML custom unmarshal function for yaml
-func (u *Uint64) UnmarshalYAML(unmarshal func(interface{}) error) error {
+func (u *Uint64) UnmarshalYAML(unmarshal func(any) error) error {
var str string
err := unmarshal(&str)
if err != nil {
diff --git a/consensus-types/wrapper/metadata.go b/consensus-types/wrapper/metadata.go
index 08c5fce8f6..5de5137fe3 100644
--- a/consensus-types/wrapper/metadata.go
+++ b/consensus-types/wrapper/metadata.go
@@ -42,7 +42,7 @@ func (m MetadataV0) CustodyGroupCount() uint64 {
}
// InnerObject returns the underlying metadata protobuf structure.
-func (m MetadataV0) InnerObject() interface{} {
+func (m MetadataV0) InnerObject() any {
return m.md
}
@@ -136,7 +136,7 @@ func (m MetadataV1) CustodyGroupCount() uint64 {
}
// InnerObject returns the underlying metadata protobuf structure.
-func (m MetadataV1) InnerObject() interface{} {
+func (m MetadataV1) InnerObject() any {
return m.md
}
@@ -230,7 +230,7 @@ func (m MetadataV2) CustodyGroupCount() uint64 {
}
// InnerObject returns the underlying metadata protobuf structure.
-func (m MetadataV2) InnerObject() interface{} {
+func (m MetadataV2) InnerObject() any {
return m.md
}
diff --git a/container/leaky-bucket/heap.go b/container/leaky-bucket/heap.go
index 7f36db23f4..29289a4ba1 100644
--- a/container/leaky-bucket/heap.go
+++ b/container/leaky-bucket/heap.go
@@ -28,7 +28,7 @@ func (pq priorityQueue) Swap(i, j int) {
}
// Push a LeakyBucket to priorityQueue
-func (pq *priorityQueue) Push(x interface{}) {
+func (pq *priorityQueue) Push(x any) {
n := len(*pq)
b, ok := x.(*LeakyBucket)
if !ok {
@@ -38,7 +38,7 @@ func (pq *priorityQueue) Push(x interface{}) {
*pq = append(*pq, b)
}
-func (pq *priorityQueue) Pop() interface{} {
+func (pq *priorityQueue) Pop() any {
old := *pq
n := len(old)
b := old[n-1]
diff --git a/container/leaky-bucket/heap_test.go b/container/leaky-bucket/heap_test.go
index f153d6d598..4a8888f20f 100644
--- a/container/leaky-bucket/heap_test.go
+++ b/container/leaky-bucket/heap_test.go
@@ -34,7 +34,7 @@ func TestLen(t *testing.T) {
func TestPeak(t *testing.T) {
q := make(priorityQueue, 0, 4096)
- for i := 0; i < 5; i++ {
+ for range 5 {
b := NewLeakyBucket(1.0, 5, time.Second)
q.Push(b)
}
@@ -43,7 +43,7 @@ func TestPeak(t *testing.T) {
func TestLess(t *testing.T) {
q := make(priorityQueue, 0, 4096)
- for i := 0; i < 5; i++ {
+ for i := range 5 {
b := NewLeakyBucket(1.0, 5, time.Second)
b.p = now().Add(time.Duration(i))
q.Push(b)
@@ -59,7 +59,7 @@ func TestLess(t *testing.T) {
func TestSwap(t *testing.T) {
q := make(priorityQueue, 0, 4096)
- for i := 0; i < 5; i++ {
+ for range 5 {
b := NewLeakyBucket(1.0, 5, time.Second)
q.Push(b)
}
@@ -80,7 +80,7 @@ func TestSwap(t *testing.T) {
func TestPush(t *testing.T) {
q := make(priorityQueue, 0, 4096)
- for i := 0; i < 5; i++ {
+ for range 5 {
b := NewLeakyBucket(1.0, 5, time.Second)
q.Push(b)
diff --git a/container/leaky-bucket/leakybucket_test.go b/container/leaky-bucket/leakybucket_test.go
index d3d7e6cf4f..9eb0f0eaa9 100644
--- a/container/leaky-bucket/leakybucket_test.go
+++ b/container/leaky-bucket/leakybucket_test.go
@@ -48,7 +48,7 @@ func TestNewLeakyBucket(t *testing.T) {
type actionSet struct {
count int64
action string
- value interface{}
+ value any
}
type testSet struct {
diff --git a/container/multi-value-slice/multi_value_slice_test.go b/container/multi-value-slice/multi_value_slice_test.go
index e967b12b36..79559137b7 100644
--- a/container/multi-value-slice/multi_value_slice_test.go
+++ b/container/multi-value-slice/multi_value_slice_test.go
@@ -632,7 +632,7 @@ func BenchmarkValue(b *testing.B) {
b.Run("100,000 shared items", func(b *testing.B) {
s := &Slice[int]{}
s.Init(make([]int, _100k))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(&testObject{})
}
})
@@ -641,11 +641,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _100k))
s.individualItems[0] = &MultiValueItem[int]{Values: []*Value[int]{{val: 999, ids: []uint64{}}}}
objs := make([]*testObject, _100k)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.individualItems[0].Values[0].ids = append(s.individualItems[0].Values[0].ids, uint64(i))
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_100k)])
}
})
@@ -653,11 +653,11 @@ func BenchmarkValue(b *testing.B) {
s := &Slice[int]{}
s.Init(make([]int, _100k))
objs := make([]*testObject, _100k)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.individualItems[uint64(i)] = &MultiValueItem[int]{Values: []*Value[int]{{val: i, ids: []uint64{uint64(i)}}}}
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_100k)])
}
})
@@ -666,11 +666,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _100k))
s.appendedItems = []*MultiValueItem[int]{{Values: []*Value[int]{{val: 999, ids: []uint64{}}}}}
objs := make([]*testObject, _100k)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.appendedItems[0].Values[0].ids = append(s.appendedItems[0].Values[0].ids, uint64(i))
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_100k)])
}
})
@@ -679,18 +679,18 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _100k))
s.appendedItems = []*MultiValueItem[int]{}
objs := make([]*testObject, _100k)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.appendedItems = append(s.appendedItems, &MultiValueItem[int]{Values: []*Value[int]{{val: i, ids: []uint64{uint64(i)}}}})
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_100k)])
}
})
b.Run("1,000,000 shared items", func(b *testing.B) {
s := &Slice[int]{}
s.Init(make([]int, _1m))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(&testObject{})
}
})
@@ -699,11 +699,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _1m))
s.individualItems[0] = &MultiValueItem[int]{Values: []*Value[int]{{val: 999, ids: []uint64{}}}}
objs := make([]*testObject, _1m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.individualItems[0].Values[0].ids = append(s.individualItems[0].Values[0].ids, uint64(i))
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_1m)])
}
})
@@ -711,11 +711,11 @@ func BenchmarkValue(b *testing.B) {
s := &Slice[int]{}
s.Init(make([]int, _1m))
objs := make([]*testObject, _1m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.individualItems[uint64(i)] = &MultiValueItem[int]{Values: []*Value[int]{{val: i, ids: []uint64{uint64(i)}}}}
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_1m)])
}
})
@@ -724,11 +724,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _1m))
s.appendedItems = []*MultiValueItem[int]{{Values: []*Value[int]{{val: 999, ids: []uint64{}}}}}
objs := make([]*testObject, _1m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.appendedItems[0].Values[0].ids = append(s.appendedItems[0].Values[0].ids, uint64(i))
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_1m)])
}
})
@@ -737,18 +737,18 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _1m))
s.appendedItems = []*MultiValueItem[int]{}
objs := make([]*testObject, _1m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.appendedItems = append(s.appendedItems, &MultiValueItem[int]{Values: []*Value[int]{{val: i, ids: []uint64{uint64(i)}}}})
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_1m)])
}
})
b.Run("10,000,000 shared items", func(b *testing.B) {
s := &Slice[int]{}
s.Init(make([]int, _10m))
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(&testObject{})
}
})
@@ -757,11 +757,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _10m))
s.individualItems[0] = &MultiValueItem[int]{Values: []*Value[int]{{val: 999, ids: []uint64{}}}}
objs := make([]*testObject, _10m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.individualItems[0].Values[0].ids = append(s.individualItems[0].Values[0].ids, uint64(i))
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_10m)])
}
})
@@ -769,11 +769,11 @@ func BenchmarkValue(b *testing.B) {
s := &Slice[int]{}
s.Init(make([]int, _10m))
objs := make([]*testObject, _10m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.individualItems[uint64(i)] = &MultiValueItem[int]{Values: []*Value[int]{{val: i, ids: []uint64{uint64(i)}}}}
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_10m)])
}
})
@@ -782,11 +782,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _10m))
s.appendedItems = []*MultiValueItem[int]{{Values: []*Value[int]{{val: 999, ids: []uint64{}}}}}
objs := make([]*testObject, _10m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.appendedItems[0].Values[0].ids = append(s.appendedItems[0].Values[0].ids, uint64(i))
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_10m)])
}
})
@@ -795,11 +795,11 @@ func BenchmarkValue(b *testing.B) {
s.Init(make([]int, _10m))
s.appendedItems = []*MultiValueItem[int]{}
objs := make([]*testObject, _10m)
- for i := 0; i < len(objs); i++ {
+ for i := range objs {
objs[i] = &testObject{id: uint64(i)}
s.appendedItems = append(s.appendedItems, &MultiValueItem[int]{Values: []*Value[int]{{val: i, ids: []uint64{uint64(i)}}}})
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
s.Value(objs[rand.Intn(_10m)])
}
})
diff --git a/container/queue/priority_queue.go b/container/queue/priority_queue.go
index 3c3d339fc0..e5a9ceca31 100644
--- a/container/queue/priority_queue.go
+++ b/container/queue/priority_queue.go
@@ -64,7 +64,7 @@ type Item struct {
Key string
// Value is an unspecified type that implementations can use to store
// information
- Value interface{}
+ Value any
// Priority determines ordering in the queue, with the lowest value being the
// highest priority
@@ -185,7 +185,7 @@ func (q queue) Swap(i, j int) {
// Push is used by heap.Interface to push items onto the heap. This method is
// invoked by container/heap, and should not be used directly.
// See: https://golang.org/pkg/container/heap/#Interface
-func (q *queue) Push(x interface{}) {
+func (q *queue) Push(x any) {
n := len(*q)
item, ok := x.(*Item)
if !ok {
@@ -198,7 +198,7 @@ func (q *queue) Push(x interface{}) {
// Pop is used by heap.Interface to pop items off of the heap. This method is
// invoked by container/heap, and should not be used directly.
// See: https://golang.org/pkg/container/heap/#Interface
-func (q *queue) Pop() interface{} {
+func (q *queue) Pop() any {
old := *q
n := len(old)
item := old[n-1]
diff --git a/container/slice/slice_test.go b/container/slice/slice_test.go
index 56aae7a9b3..14e49ac12c 100644
--- a/container/slice/slice_test.go
+++ b/container/slice/slice_test.go
@@ -2,7 +2,7 @@ package slice_test
import (
"reflect"
- "sort"
+ "slices"
"testing"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
@@ -55,13 +55,11 @@ func TestIntersectionUint64(t *testing.T) {
{[]uint64{1, 1, 1}, []uint64{1, 1}, []uint64{1, 2, 3}, []uint64{1}},
}
for _, tt := range testCases {
- setA := append([]uint64{}, tt.setA...)
- setB := append([]uint64{}, tt.setB...)
- setC := append([]uint64{}, tt.setC...)
+ setA := slices.Clone(tt.setA)
+ setB := slices.Clone(tt.setB)
+ setC := slices.Clone(tt.setC)
result := slice.IntersectionUint64(setA, setB, setC)
- sort.Slice(result, func(i, j int) bool {
- return result[i] < result[j]
- })
+ slices.Sort(result)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
@@ -119,13 +117,11 @@ func TestIntersectionInt64(t *testing.T) {
{[]int64{1, 1, 1}, []int64{1, 1}, []int64{1, 2, 3}, []int64{1}},
}
for _, tt := range testCases {
- setA := append([]int64{}, tt.setA...)
- setB := append([]int64{}, tt.setB...)
- setC := append([]int64{}, tt.setC...)
+ setA := slices.Clone(tt.setA)
+ setB := slices.Clone(tt.setB)
+ setC := slices.Clone(tt.setC)
result := slice.IntersectionInt64(setA, setB, setC)
- sort.Slice(result, func(i, j int) bool {
- return result[i] < result[j]
- })
+ slices.Sort(result)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
@@ -525,13 +521,11 @@ func TestIntersectionSlot(t *testing.T) {
{[]primitives.Slot{1, 1, 1}, []primitives.Slot{1, 1}, []primitives.Slot{1, 2, 3}, []primitives.Slot{1}},
}
for _, tt := range testCases {
- setA := append([]primitives.Slot{}, tt.setA...)
- setB := append([]primitives.Slot{}, tt.setB...)
- setC := append([]primitives.Slot{}, tt.setC...)
+ setA := slices.Clone(tt.setA)
+ setB := slices.Clone(tt.setB)
+ setC := slices.Clone(tt.setC)
result := slice.IntersectionSlot(setA, setB, setC)
- sort.Slice(result, func(i, j int) bool {
- return result[i] < result[j]
- })
+ slices.Sort(result)
if !reflect.DeepEqual(result, tt.out) {
t.Errorf("got %d, want %d", result, tt.out)
}
diff --git a/container/thread-safe/map_test.go b/container/thread-safe/map_test.go
index a3956318f3..ec0f55a67f 100644
--- a/container/thread-safe/map_test.go
+++ b/container/thread-safe/map_test.go
@@ -36,8 +36,8 @@ func BenchmarkMap_Concrete(b *testing.B) {
mm := &safeMap{
items: make(map[int]string),
}
- for i := 0; i < b.N; i++ {
- for j := 0; j < 1000; j++ {
+ for b.Loop() {
+ for j := range 1000 {
mm.Put(j, "foo")
mm.Get(j)
mm.Delete(j)
@@ -48,8 +48,8 @@ func BenchmarkMap_Concrete(b *testing.B) {
func BenchmarkMap_Generic(b *testing.B) {
items := make(map[int]string)
mm := NewThreadSafeMap(items)
- for i := 0; i < b.N; i++ {
- for j := 0; j < 1000; j++ {
+ for b.Loop() {
+ for j := range 1000 {
mm.Put(j, "foo")
mm.Get(j)
mm.Delete(j)
@@ -59,8 +59,8 @@ func BenchmarkMap_Generic(b *testing.B) {
func BenchmarkMap_GenericTx(b *testing.B) {
items := make(map[int]string)
mm := NewThreadSafeMap(items)
- for i := 0; i < b.N; i++ {
- for j := 0; j < 1000; j++ {
+ for b.Loop() {
+ for j := range 1000 {
mm.Do(func(mp map[int]string) {
mp[j] = "foo"
_ = mp[j]
@@ -85,7 +85,7 @@ func TestMap(t *testing.T) {
require.Equal(t, 3, tMap.Len())
var wg sync.WaitGroup
- for i := 0; i < 100; i++ {
+ for range 100 {
wg.Add(1)
go func(w *sync.WaitGroup, scopedMap *Map[int, string]) {
defer w.Done()
diff --git a/container/trie/sparse_merkle.go b/container/trie/sparse_merkle.go
index 2c95f367f1..02a67ed5a6 100644
--- a/container/trie/sparse_merkle.go
+++ b/container/trie/sparse_merkle.go
@@ -82,7 +82,7 @@ func GenerateTrieFromItems(items [][]byte, depth uint64) (*SparseMerkleTrie, err
transformedLeaves[i] = arr[:]
}
layers[0] = transformedLeaves
- for i := uint64(0); i < depth; i++ {
+ for i := range depth {
if len(layers[i])%2 == 1 {
layers[i] = append(layers[i], ZeroHashes[i][:])
}
diff --git a/container/trie/sparse_merkle_test.go b/container/trie/sparse_merkle_test.go
index dfc13413ff..fb1f11811a 100644
--- a/container/trie/sparse_merkle_test.go
+++ b/container/trie/sparse_merkle_test.go
@@ -20,7 +20,7 @@ func TestCreateTrieFromProto_Validation(t *testing.T) {
h := hash.Hash([]byte("hi"))
genValidLayers := func(num int) []*ethpb.TrieLayer {
l := make([]*ethpb.TrieLayer, num)
- for i := 0; i < num; i++ {
+ for i := range num {
l[i] = ðpb.TrieLayer{
Layer: [][]byte{h[:]},
}
@@ -339,17 +339,17 @@ func BenchmarkGenerateTrieFromItems(b *testing.B) {
[]byte("FFFFFF"),
[]byte("GGGGGGG"),
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := trie.GenerateTrieFromItems(items, params.BeaconConfig().DepositContractTreeDepth)
require.NoError(b, err, "Could not generate Merkle trie from items")
}
}
func BenchmarkInsertTrie_Optimized(b *testing.B) {
- b.StopTimer()
+
numDeposits := 16000
items := make([][]byte, numDeposits)
- for i := 0; i < numDeposits; i++ {
+ for i := range numDeposits {
someRoot := bytesutil.ToBytes32([]byte(strconv.Itoa(i)))
items[i] = someRoot[:]
}
@@ -357,14 +357,14 @@ func BenchmarkInsertTrie_Optimized(b *testing.B) {
require.NoError(b, err)
someItem := bytesutil.ToBytes32([]byte("hello-world"))
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for i := 0; b.Loop(); i++ {
require.NoError(b, tr.Insert(someItem[:], i%numDeposits))
}
}
func BenchmarkGenerateProof(b *testing.B) {
- b.StopTimer()
+
items := [][]byte{
[]byte("A"),
[]byte("BB"),
@@ -377,15 +377,14 @@ func BenchmarkGenerateProof(b *testing.B) {
normalTrie, err := trie.GenerateTrieFromItems(items, params.BeaconConfig().DepositContractTreeDepth)
require.NoError(b, err)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := normalTrie.MerkleProof(3)
require.NoError(b, err)
}
}
func BenchmarkVerifyMerkleProofWithDepth(b *testing.B) {
- b.StopTimer()
+
items := [][]byte{
[]byte("A"),
[]byte("BB"),
@@ -402,8 +401,8 @@ func BenchmarkVerifyMerkleProofWithDepth(b *testing.B) {
root, err := m.HashTreeRoot()
require.NoError(b, err)
- b.StartTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
if ok := trie.VerifyMerkleProofWithDepth(root[:], items[2], 2, proof, params.BeaconConfig().DepositContractTreeDepth); !ok {
b.Error("Merkle proof did not verify")
}
diff --git a/contracts/deposit/deposit_tree_test.go b/contracts/deposit/deposit_tree_test.go
index fd228b8561..388e3c8983 100644
--- a/contracts/deposit/deposit_tree_test.go
+++ b/contracts/deposit/deposit_tree_test.go
@@ -34,7 +34,7 @@ func TestDepositTrieRoot_OK(t *testing.T) {
testAcc.TxOpts.Value = depositcontract.Amount32Eth()
- for i := 0; i < 100; i++ {
+ for i := range 100 {
data := depositDataItems[i]
var dataRoot [32]byte
copy(dataRoot[:], depositDataRoots[i])
@@ -75,7 +75,7 @@ func TestDepositTrieRoot_Fail(t *testing.T) {
require.NoError(t, err)
testAcc.TxOpts.Value = depositcontract.Amount32Eth()
- for i := 0; i < 100; i++ {
+ for i := range 100 {
data := depositDataItems[i]
var dataRoot [32]byte
copy(dataRoot[:], depositDataRoots[i])
diff --git a/crypto/bls/blst/bls_benchmark_test.go b/crypto/bls/blst/bls_benchmark_test.go
index 6f63a623ea..7b22129e53 100644
--- a/crypto/bls/blst/bls_benchmark_test.go
+++ b/crypto/bls/blst/bls_benchmark_test.go
@@ -17,8 +17,7 @@ func BenchmarkSignature_Verify(b *testing.B) {
msg := []byte("Some msg")
sig := sk.Sign(msg)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
if !sig.Verify(sk.PublicKey(), msg) {
b.Fatal("could not verify sig")
}
@@ -31,7 +30,7 @@ func BenchmarkSignature_AggregateVerify(b *testing.B) {
var pks []common.PublicKey
var sigs []common.Signature
var msgs [][32]byte
- for i := 0; i < sigN; i++ {
+ for i := range sigN {
msg := [32]byte{'s', 'i', 'g', 'n', 'e', 'd', byte(i)}
sk, err := blst.RandKey()
require.NoError(b, err)
@@ -42,9 +41,8 @@ func BenchmarkSignature_AggregateVerify(b *testing.B) {
}
aggregated := blst.AggregateSignatures(sigs)
- b.ResetTimer()
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
if !aggregated.AggregateVerify(pks, msgs) {
b.Fatal("could not verify aggregate sig")
}
@@ -56,8 +54,7 @@ func BenchmarkSecretKey_Marshal(b *testing.B) {
require.NoError(b, err)
d := key.Marshal()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := blst.SecretKeyFromBytes(d)
_ = err
}
diff --git a/crypto/bls/blst/public_key_test.go b/crypto/bls/blst/public_key_test.go
index f1eea07a1b..978331a441 100644
--- a/crypto/bls/blst/public_key_test.go
+++ b/crypto/bls/blst/public_key_test.go
@@ -95,7 +95,7 @@ func TestPublicKey_Aggregate(t *testing.T) {
func TestPublicKey_Aggregation_NoCorruption(t *testing.T) {
var pubkeys []common.PublicKey
- for i := 0; i < 100; i++ {
+ for range 100 {
priv, err := blst.RandKey()
require.NoError(t, err)
pubkey := priv.PublicKey()
@@ -113,54 +113,40 @@ func TestPublicKey_Aggregation_NoCorruption(t *testing.T) {
wg := new(sync.WaitGroup)
// Aggregate different sets of keys.
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys)
require.NoError(t, err)
- wg.Done()
- }()
+ })
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys[:10])
require.NoError(t, err)
- wg.Done()
- }()
+ })
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys[:40])
require.NoError(t, err)
- wg.Done()
- }()
+ })
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys[20:60])
require.NoError(t, err)
- wg.Done()
- }()
+ })
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys[80:])
require.NoError(t, err)
- wg.Done()
- }()
+ })
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys[60:90])
require.NoError(t, err)
- wg.Done()
- }()
+ })
- wg.Add(1)
- go func() {
+ wg.Go(func() {
_, err := blst.AggregatePublicKeys(compressedKeys[40:99])
require.NoError(t, err)
- wg.Done()
- }()
+ })
wg.Wait()
@@ -185,7 +171,7 @@ func BenchmarkPublicKeyFromBytes(b *testing.B) {
b.Run("cache on", func(b *testing.B) {
blst.EnableCaches()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := blst.PublicKeyFromBytes(pubkeyBytes)
require.NoError(b, err)
}
@@ -193,7 +179,7 @@ func BenchmarkPublicKeyFromBytes(b *testing.B) {
b.Run("cache off", func(b *testing.B) {
blst.DisableCaches()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := blst.PublicKeyFromBytes(pubkeyBytes)
require.NoError(b, err)
}
diff --git a/crypto/bls/blst/signature.go b/crypto/bls/blst/signature.go
index d6e789adab..8983963995 100644
--- a/crypto/bls/blst/signature.go
+++ b/crypto/bls/blst/signature.go
@@ -144,7 +144,7 @@ func (s *Signature) AggregateVerify(pubKeys []common.PublicKey, msgs [][32]byte)
}
msgSlices := make([][]byte, len(msgs))
rawKeys := make([]*blstPublicKey, len(msgs))
- for i := 0; i < size; i++ {
+ for i := range size {
msgSlices[i] = msgs[i][:]
rawKeys[i] = pubKeys[i].(*PublicKey).p
}
@@ -168,7 +168,7 @@ func (s *Signature) FastAggregateVerify(pubKeys []common.PublicKey, msg [32]byte
return false
}
rawKeys := make([]*blstPublicKey, len(pubKeys))
- for i := 0; i < len(pubKeys); i++ {
+ for i := range pubKeys {
rawKeys[i] = pubKeys[i].(*PublicKey).p
}
return s.s.FastAggregateVerify(true, rawKeys, msg[:], dst)
@@ -206,7 +206,7 @@ func AggregateSignatures(sigs []common.Signature) common.Signature {
}
rawSigs := make([]*blstSignature, len(sigs))
- for i := 0; i < len(sigs); i++ {
+ for i := range sigs {
rawSigs[i] = sigs[i].(*Signature).s
}
@@ -246,7 +246,7 @@ func VerifyMultipleSignatures(sigs [][]byte, msgs [][32]byte, pubKeys []common.P
mulP1Aff := make([]*blstPublicKey, length)
rawMsgs := make([]blst.Message, length)
- for i := 0; i < length; i++ {
+ for i := range length {
mulP1Aff[i] = pubKeys[i].(*PublicKey).p
rawMsgs[i] = msgs[i][:]
}
diff --git a/crypto/bls/blst/signature_test.go b/crypto/bls/blst/signature_test.go
index 2890bf7161..7d5965fa22 100644
--- a/crypto/bls/blst/signature_test.go
+++ b/crypto/bls/blst/signature_test.go
@@ -25,7 +25,7 @@ func TestAggregateVerify(t *testing.T) {
pubkeys := make([]common.PublicKey, 0, 100)
sigs := make([]common.Signature, 0, 100)
var msgs [][32]byte
- for i := 0; i < 100; i++ {
+ for i := range 100 {
msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)}
priv, err := RandKey()
require.NoError(t, err)
@@ -45,7 +45,7 @@ func TestAggregateVerify_CompressedSignatures(t *testing.T) {
sigs := make([]common.Signature, 0, 100)
var sigBytes [][]byte
var msgs [][32]byte
- for i := 0; i < 100; i++ {
+ for i := range 100 {
msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)}
priv, err := RandKey()
require.NoError(t, err)
@@ -69,7 +69,7 @@ func TestFastAggregateVerify(t *testing.T) {
pubkeys := make([]common.PublicKey, 0, 100)
sigs := make([]common.Signature, 0, 100)
msg := [32]byte{'h', 'e', 'l', 'l', 'o'}
- for i := 0; i < 100; i++ {
+ for range 100 {
priv, err := RandKey()
require.NoError(t, err)
pub := priv.PublicKey()
@@ -119,7 +119,7 @@ func TestMultipleSignatureVerification(t *testing.T) {
pubkeys := make([]common.PublicKey, 0, 100)
sigs := make([][]byte, 0, 100)
var msgs [][32]byte
- for i := 0; i < 100; i++ {
+ for i := range 100 {
msg := [32]byte{'h', 'e', 'l', 'l', 'o', byte(i)}
priv, err := RandKey()
require.NoError(t, err)
@@ -146,7 +146,7 @@ func TestEth2FastAggregateVerify(t *testing.T) {
pubkeys := make([]common.PublicKey, 0, 100)
sigs := make([]common.Signature, 0, 100)
msg := [32]byte{'h', 'e', 'l', 'l', 'o'}
- for i := 0; i < 100; i++ {
+ for range 100 {
priv, err := RandKey()
require.NoError(t, err)
pub := priv.PublicKey()
diff --git a/crypto/bls/signature_batch.go b/crypto/bls/signature_batch.go
index 607616e595..c13c8652b0 100644
--- a/crypto/bls/signature_batch.go
+++ b/crypto/bls/signature_batch.go
@@ -3,6 +3,7 @@ package bls
import (
"encoding/hex"
"fmt"
+ "strings"
"github.com/pkg/errors"
)
@@ -54,7 +55,8 @@ func (s *SignatureBatch) VerifyVerbosely() (bool, error) {
// if signature batch is invalid, we then verify signatures one by one.
- errmsg := "some signatures are invalid. details:"
+ var errmsg strings.Builder
+ errmsg.WriteString("some signatures are invalid. details:")
for i := 0; i < len(s.Signatures); i++ {
sig := s.Signatures[i]
msg := s.Messages[i]
@@ -64,20 +66,20 @@ func (s *SignatureBatch) VerifyVerbosely() (bool, error) {
if !valid {
desc := s.Descriptions[i]
if err != nil {
- errmsg += fmt.Sprintf("\nsignature '%s' is invalid."+
+ errmsg.WriteString(fmt.Sprintf("\nsignature '%s' is invalid."+
" signature: 0x%s, public key: 0x%s, message: 0x%v, error: %v",
desc, hex.EncodeToString(sig), hex.EncodeToString(pubKey.Marshal()),
- hex.EncodeToString(msg[:]), err)
+ hex.EncodeToString(msg[:]), err))
} else {
- errmsg += fmt.Sprintf("\nsignature '%s' is invalid."+
+ errmsg.WriteString(fmt.Sprintf("\nsignature '%s' is invalid."+
" signature: 0x%s, public key: 0x%s, message: 0x%v",
desc, hex.EncodeToString(sig), hex.EncodeToString(pubKey.Marshal()),
- hex.EncodeToString(msg[:]))
+ hex.EncodeToString(msg[:])))
}
}
}
- return false, errors.New(errmsg)
+ return false, errors.New(errmsg.String())
}
// Copy the attached signature batch and return it
diff --git a/crypto/bls/signature_batch_test.go b/crypto/bls/signature_batch_test.go
index a33c0b4a00..2a235c41ff 100644
--- a/crypto/bls/signature_batch_test.go
+++ b/crypto/bls/signature_batch_test.go
@@ -90,7 +90,7 @@ func TestVerifyVerbosely_VerificationThrowsError(t *testing.T) {
func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
var keys []SecretKey
- for i := 0; i < 100; i++ {
+ for range 100 {
key, err := RandKey()
assert.NoError(t, err)
keys = append(keys, key)
@@ -379,7 +379,7 @@ func TestSignatureBatch_RemoveDuplicates(t *testing.T) {
func TestSignatureBatch_AggregateBatch(t *testing.T) {
var keys []SecretKey
- for i := 0; i < 100; i++ {
+ for range 100 {
key, err := RandKey()
assert.NoError(t, err)
keys = append(keys, key)
@@ -654,7 +654,7 @@ func NewValidSignatureSet(t *testing.T, msgBody string, num int) *SignatureBatch
Descriptions: make([]string, num),
}
- for i := 0; i < num; i++ {
+ for i := range num {
priv, err := RandKey()
require.NoError(t, err)
pubkey := priv.PublicKey()
@@ -679,7 +679,7 @@ func NewInvalidSignatureSet(t *testing.T, msgBody string, num int, throwErr bool
Descriptions: make([]string, num),
}
- for i := 0; i < num; i++ {
+ for i := range num {
priv, err := RandKey()
require.NoError(t, err)
pubkey := priv.PublicKey()
diff --git a/crypto/hash/hash.go b/crypto/hash/hash.go
index 972f006032..d0473c0d49 100644
--- a/crypto/hash/hash.go
+++ b/crypto/hash/hash.go
@@ -19,7 +19,7 @@ import (
// or has nil objects within lists.
var ErrNilProto = errors.New("cannot hash a nil protobuf message")
-var sha256Pool = sync.Pool{New: func() interface{} {
+var sha256Pool = sync.Pool{New: func() any {
return sha256.New()
}}
@@ -75,7 +75,7 @@ func CustomSHA256Hasher() func([]byte) [32]byte {
}
}
-var keccak256Pool = sync.Pool{New: func() interface{} {
+var keccak256Pool = sync.Pool{New: func() any {
return sha3.NewLegacyKeccak256()
}}
diff --git a/crypto/hash/hash_test.go b/crypto/hash/hash_test.go
index afd15a35e8..9d8ae01cf3 100644
--- a/crypto/hash/hash_test.go
+++ b/crypto/hash/hash_test.go
@@ -26,7 +26,7 @@ func TestHash(t *testing.T) {
}
func BenchmarkHash(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
hash.Hash([]byte("abc"))
}
}
@@ -50,7 +50,7 @@ func TestHashKeccak256(t *testing.T) {
}
func BenchmarkHashKeccak256(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
hash.Keccak256([]byte("abc"))
}
}
@@ -77,7 +77,7 @@ func TestHashProtoFuzz(t *testing.T) {
}
}(t)
- for i := 0; i < 1000; i++ {
+ for range 1000 {
msg := &pb.AddressBook{}
f.Fuzz(msg)
_, err := hash.Proto(msg)
@@ -98,7 +98,7 @@ func BenchmarkHashProto(b *testing.B) {
Signature: bls.NewAggregateSignature().Marshal(),
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
if _, err := hash.Proto(att); err != nil {
b.Log(err)
}
diff --git a/crypto/hash/htr/hashtree.go b/crypto/hash/htr/hashtree.go
index 6a052226e1..5ff9d33b53 100644
--- a/crypto/hash/htr/hashtree.go
+++ b/crypto/hash/htr/hashtree.go
@@ -35,7 +35,7 @@ func VectorizedSha256(inputList [][32]byte) [][32]byte {
wg := sync.WaitGroup{}
wg.Add(n)
groupSize := len(inputList) / (2 * (n + 1))
- for j := 0; j < n; j++ {
+ for j := range n {
go hashParallel(inputList[j*2*groupSize:(j+1)*2*groupSize], outputList[j*groupSize:], &wg)
}
err := gohashtree.Hash(outputList[n*groupSize:], inputList[n*2*groupSize:])
diff --git a/crypto/hash/htr/hashtree_test.go b/crypto/hash/htr/hashtree_test.go
index 57f1e12ea2..d57f355597 100644
--- a/crypto/hash/htr/hashtree_test.go
+++ b/crypto/hash/htr/hashtree_test.go
@@ -12,12 +12,10 @@ func Test_VectorizedSha256(t *testing.T) {
secondLargeSlice := make([][32]byte, 32*minSliceSizeToParallelize)
hash1 := make([][32]byte, 16*minSliceSizeToParallelize)
wg := sync.WaitGroup{}
- wg.Add(1)
- go func() {
- defer wg.Done()
+ wg.Go(func() {
tempHash := VectorizedSha256(largeSlice)
copy(hash1, tempHash)
- }()
+ })
wg.Wait()
hash2 := VectorizedSha256(secondLargeSlice)
require.Equal(t, len(hash1), len(hash2))
diff --git a/crypto/keystore/key.go b/crypto/keystore/key.go
index e4d538ef84..3dae549b58 100644
--- a/crypto/keystore/key.go
+++ b/crypto/keystore/key.go
@@ -75,12 +75,12 @@ type encryptedKeyJSON struct {
}
type cryptoJSON struct {
- Cipher string `json:"cipher"`
- CipherText string `json:"ciphertext"`
- CipherParams cipherparamsJSON `json:"cipherparams"`
- KDF string `json:"kdf"`
- KDFParams map[string]interface{} `json:"kdfparams"`
- MAC string `json:"mac"`
+ Cipher string `json:"cipher"`
+ CipherText string `json:"ciphertext"`
+ CipherParams cipherparamsJSON `json:"cipherparams"`
+ KDF string `json:"kdf"`
+ KDFParams map[string]any `json:"kdfparams"`
+ MAC string `json:"mac"`
}
type cipherparamsJSON struct {
diff --git a/crypto/keystore/keystore.go b/crypto/keystore/keystore.go
index e4fbf98ad1..9fb01d9efb 100644
--- a/crypto/keystore/keystore.go
+++ b/crypto/keystore/keystore.go
@@ -153,7 +153,7 @@ func EncryptKey(key *Key, password string, scryptN, scryptP int) ([]byte, error)
mac := Keccak256(derivedKey[16:32], cipherText)
- scryptParamsJSON := make(map[string]interface{}, 5)
+ scryptParamsJSON := make(map[string]any, 5)
scryptParamsJSON["n"] = scryptN
scryptParamsJSON["r"] = scryptR
scryptParamsJSON["p"] = scryptP
diff --git a/crypto/keystore/utils.go b/crypto/keystore/utils.go
index 6b518438b1..05e9021a68 100644
--- a/crypto/keystore/utils.go
+++ b/crypto/keystore/utils.go
@@ -41,7 +41,7 @@ func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
return outText, err
}
-func ensureInt(x interface{}) int {
+func ensureInt(x any) int {
res, ok := x.(int)
if !ok {
res = int(x.(float64))
diff --git a/crypto/random/random_test.go b/crypto/random/random_test.go
index 3d61f3e241..676b52480c 100644
--- a/crypto/random/random_test.go
+++ b/crypto/random/random_test.go
@@ -48,7 +48,7 @@ func TestGetRandBlobElements(t *testing.T) {
blob := GetRandBlob(seed)
// Check that each field element in the blob matches what we'd get from GetRandFieldElement
- for i := 0; i < GoKZG.ScalarsPerBlob; i++ {
+ for i := range GoKZG.ScalarsPerBlob {
start := i * GoKZG.SerializedScalarSize
end := start + GoKZG.SerializedScalarSize
diff --git a/deps.bzl b/deps.bzl
index 8082c17f83..1b41264eb3 100644
--- a/deps.bzl
+++ b/deps.bzl
@@ -4784,8 +4784,8 @@ def prysm_deps():
go_repository(
name = "org_golang_x_crypto",
importpath = "golang.org/x/crypto",
- sum = "h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=",
- version = "v0.36.0",
+ sum = "h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=",
+ version = "v0.43.0",
)
go_repository(
name = "org_golang_x_exp",
@@ -4820,14 +4820,14 @@ def prysm_deps():
go_repository(
name = "org_golang_x_mod",
importpath = "golang.org/x/mod",
- sum = "h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=",
- version = "v0.23.0",
+ sum = "h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=",
+ version = "v0.29.0",
)
go_repository(
name = "org_golang_x_net",
importpath = "golang.org/x/net",
- sum = "h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=",
- version = "v0.38.0",
+ sum = "h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=",
+ version = "v0.46.0",
)
go_repository(
name = "org_golang_x_oauth2",
@@ -4844,32 +4844,32 @@ def prysm_deps():
go_repository(
name = "org_golang_x_sync",
importpath = "golang.org/x/sync",
- sum = "h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=",
- version = "v0.12.0",
+ sum = "h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=",
+ version = "v0.17.0",
)
go_repository(
name = "org_golang_x_sys",
importpath = "golang.org/x/sys",
- sum = "h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=",
- version = "v0.31.0",
+ sum = "h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=",
+ version = "v0.37.0",
)
go_repository(
name = "org_golang_x_telemetry",
importpath = "golang.org/x/telemetry",
- sum = "h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=",
- version = "v0.0.0-20240521205824-bda55230c457",
+ sum = "h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=",
+ version = "v0.0.0-20251008203120-078029d740a8",
)
go_repository(
name = "org_golang_x_term",
importpath = "golang.org/x/term",
- sum = "h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=",
- version = "v0.30.0",
+ sum = "h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=",
+ version = "v0.36.0",
)
go_repository(
name = "org_golang_x_text",
importpath = "golang.org/x/text",
- sum = "h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=",
- version = "v0.23.0",
+ sum = "h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=",
+ version = "v0.30.0",
)
go_repository(
name = "org_golang_x_time",
@@ -4880,8 +4880,14 @@ def prysm_deps():
go_repository(
name = "org_golang_x_tools",
importpath = "golang.org/x/tools",
- sum = "h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=",
- version = "v0.30.0",
+ sum = "h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=",
+ version = "v0.38.0",
+ )
+ go_repository(
+ name = "org_golang_x_tools_go_expect",
+ importpath = "golang.org/x/tools/go/expect",
+ sum = "h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=",
+ version = "v0.1.1-deprecated",
)
go_repository(
name = "org_golang_x_xerrors",
diff --git a/encoding/bytesutil/bytes_go120.go b/encoding/bytesutil/bytes_go120.go
index e35dfa1791..7bc7acdbad 100644
--- a/encoding/bytesutil/bytes_go120.go
+++ b/encoding/bytesutil/bytes_go120.go
@@ -1,5 +1,4 @@
//go:build go1.20
-// +build go1.20
package bytesutil
diff --git a/encoding/bytesutil/bytes_legacy.go b/encoding/bytesutil/bytes_legacy.go
index 7ede8a934f..53b3f16916 100644
--- a/encoding/bytesutil/bytes_legacy.go
+++ b/encoding/bytesutil/bytes_legacy.go
@@ -1,5 +1,4 @@
//go:build !go1.20
-// +build !go1.20
package bytesutil
diff --git a/encoding/bytesutil/bytes_test.go b/encoding/bytesutil/bytes_test.go
index e086831dce..81956d5262 100644
--- a/encoding/bytesutil/bytes_test.go
+++ b/encoding/bytesutil/bytes_test.go
@@ -235,7 +235,7 @@ func BenchmarkUnsafeCastToString(b *testing.B) {
var nilData []byte
b.Run("string(b)", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = string(data)
_ = string(empty)
_ = string(nilData)
@@ -243,7 +243,7 @@ func BenchmarkUnsafeCastToString(b *testing.B) {
})
b.Run("bytesutil.UnsafeCastToString(b)", func(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = bytesutil.UnsafeCastToString(data)
_ = bytesutil.UnsafeCastToString(empty)
_ = bytesutil.UnsafeCastToString(nilData)
@@ -263,7 +263,7 @@ func FuzzUnsafeCastToString(f *testing.F) {
func BenchmarkToBytes32(b *testing.B) {
x := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
bytesutil.ToBytes32(x)
}
}
@@ -318,7 +318,7 @@ func TestSafeCopyBytes_Copy(t *testing.T) {
func BenchmarkSafeCopyBytes(b *testing.B) {
dSlice := make([][]byte, 900000)
- for i := 0; i < 900000; i++ {
+ for i := range 900000 {
slice := make([]byte, 32)
slice[0] = 'A'
dSlice[i] = slice
diff --git a/encoding/bytesutil/integers.go b/encoding/bytesutil/integers.go
index 1e91542b4c..4c62474a4d 100644
--- a/encoding/bytesutil/integers.go
+++ b/encoding/bytesutil/integers.go
@@ -15,10 +15,7 @@ func ToBytes(x uint64, length int) []byte {
if length < 0 {
length = 0
}
- makeLength := length
- if length < 8 {
- makeLength = 8
- }
+ makeLength := max(length, 8)
bytes := make([]byte, makeLength)
binary.LittleEndian.PutUint64(bytes, x)
return bytes[:length]
diff --git a/encoding/bytesutil/integers_test.go b/encoding/bytesutil/integers_test.go
index 968a390dd0..b9cc86994f 100644
--- a/encoding/bytesutil/integers_test.go
+++ b/encoding/bytesutil/integers_test.go
@@ -254,7 +254,7 @@ func TestUint32ToBytes4(t *testing.T) {
}
func TestUint64ToBytes_RoundTrip(t *testing.T) {
- for i := uint64(0); i < 10000; i++ {
+ for i := range uint64(10000) {
b := bytesutil.Uint64ToBytesBigEndian(i)
if got := bytesutil.BytesToUint64BigEndian(b); got != i {
t.Error("Round trip did not match original value")
diff --git a/encoding/ssz/equality/deep_equal.go b/encoding/ssz/equality/deep_equal.go
index d7e6714510..744cdb004b 100644
--- a/encoding/ssz/equality/deep_equal.go
+++ b/encoding/ssz/equality/deep_equal.go
@@ -296,7 +296,7 @@ func deepValueBaseTypeEqual(v1, v2 reflect.Value) bool {
//
// Credits go to the Go team as this is an extension of the official Go source code's
// reflect.DeepEqual function to handle special SSZ edge cases.
-func DeepEqual(x, y interface{}) bool {
+func DeepEqual(x, y any) bool {
if x == nil || y == nil {
return x == y
}
@@ -312,7 +312,7 @@ func DeepEqual(x, y interface{}) bool {
return deepValueEqual(v1, v2, make(map[visit]bool), 0)
}
-func IsProto(item interface{}) bool {
+func IsProto(item any) bool {
typ := reflect.TypeOf(item)
kind := typ.Kind()
if kind != reflect.Slice && kind != reflect.Array && kind != reflect.Map {
diff --git a/encoding/ssz/equality/deep_equal_test.go b/encoding/ssz/equality/deep_equal_test.go
index 31b7357954..cff6ccec35 100644
--- a/encoding/ssz/equality/deep_equal_test.go
+++ b/encoding/ssz/equality/deep_equal_test.go
@@ -89,7 +89,7 @@ func TestDeepEqualProto(t *testing.T) {
func Test_IsProto(t *testing.T) {
tests := []struct {
name string
- item interface{}
+ item any
want bool
}{
{
diff --git a/encoding/ssz/helpers.go b/encoding/ssz/helpers.go
index c5c9553037..73a218bae5 100644
--- a/encoding/ssz/helpers.go
+++ b/encoding/ssz/helpers.go
@@ -83,12 +83,10 @@ func PackByChunk(serializedItems [][]byte) ([][BytesPerChunk]byte, error) {
numItems := len(orderedItems)
var chunks [][BytesPerChunk]byte
for i := 0; i < numItems; i += BytesPerChunk {
- j := i + BytesPerChunk
- // We create our upper bound index of the chunk, if it is greater than numItems,
- // we set it as numItems itself.
- if j > numItems {
- j = numItems
- }
+ j := min(
+ // We create our upper bound index of the chunk, if it is greater than numItems,
+ // we set it as numItems itself.
+ i+BytesPerChunk, numItems)
// We create chunks from the list of items based on the
// indices determined above.
// Right-pad the last chunk with zero bytes if it does not
diff --git a/encoding/ssz/htrutils_test.go b/encoding/ssz/htrutils_test.go
index 0188fafca1..8529e03a81 100644
--- a/encoding/ssz/htrutils_test.go
+++ b/encoding/ssz/htrutils_test.go
@@ -124,7 +124,7 @@ func TestTransactionsRoot(t *testing.T) {
name: "max txs",
txs: func() [][]byte {
var txs [][]byte
- for i := 0; i < fieldparams.MaxTxsPerPayloadLength; i++ {
+ for range fieldparams.MaxTxsPerPayloadLength {
txs = append(txs, []byte{})
}
return txs
@@ -135,7 +135,7 @@ func TestTransactionsRoot(t *testing.T) {
name: "exceed max txs",
txs: func() [][]byte {
var txs [][]byte
- for i := 0; i < fieldparams.MaxTxsPerPayloadLength+1; i++ {
+ for range fieldparams.MaxTxsPerPayloadLength + 1 {
txs = append(txs, []byte{})
}
return txs
diff --git a/encoding/ssz/merkleize.go b/encoding/ssz/merkleize.go
index 76feec0469..72845204ef 100644
--- a/encoding/ssz/merkleize.go
+++ b/encoding/ssz/merkleize.go
@@ -113,7 +113,7 @@ func Merkleize(hasher Hasher, count, limit uint64, leaf func(i uint64) []byte) (
}
// merge in leaf by leaf.
- for i := uint64(0); i < count; i++ {
+ for i := range count {
copy(h, leaf(i))
merge(i)
}
@@ -141,7 +141,7 @@ func MerkleizeVector(elements [][32]byte, length uint64) [32]byte {
if len(elements) == 0 {
return trie.ZeroHashes[depth]
}
- for i := uint8(0); i < depth; i++ {
+ for i := range depth {
layerLen := len(elements)
oddNodeLength := layerLen%2 == 1
if oddNodeLength {
diff --git a/encoding/ssz/slice_root.go b/encoding/ssz/slice_root.go
index 85575f03d3..fe116fe6c8 100644
--- a/encoding/ssz/slice_root.go
+++ b/encoding/ssz/slice_root.go
@@ -16,7 +16,7 @@ func SliceRoot[T Hashable](slice []T, limit uint64) ([32]byte, error) {
}
roots := make([][32]byte, len(slice))
- for i := 0; i < len(slice); i++ {
+ for i := range slice {
r, err := slice[i].HashTreeRoot()
if err != nil {
return [32]byte{}, errors.Wrap(err, "could not merkleize object")
diff --git a/genesis/initialize_test.go b/genesis/initialize_test.go
index af4fef1aba..db4d89cf4c 100644
--- a/genesis/initialize_test.go
+++ b/genesis/initialize_test.go
@@ -98,7 +98,7 @@ func createTestGenesisState(t *testing.T, numValidators uint64, slot primitives.
}
// Initialize validators and balances
- for i := uint64(0); i < numValidators; i++ {
+ for i := range numValidators {
pb.Validators[i] = ðpb.Validator{
PublicKey: deposits[i].Data.PublicKey,
WithdrawalCredentials: deposits[i].Data.WithdrawalCredentials,
diff --git a/genesis/internal/embedded/mainnet.go b/genesis/internal/embedded/mainnet.go
index ca855c7313..730402a62f 100644
--- a/genesis/internal/embedded/mainnet.go
+++ b/genesis/internal/embedded/mainnet.go
@@ -1,5 +1,4 @@
//go:build !noMainnetGenesis
-// +build !noMainnetGenesis
package embedded
diff --git a/go.mod b/go.mod
index d829269912..d0abcd3c1d 100644
--- a/go.mod
+++ b/go.mod
@@ -88,10 +88,10 @@ require (
go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/automaxprocs v1.5.2
go.uber.org/mock v0.5.2
- golang.org/x/crypto v0.36.0
+ golang.org/x/crypto v0.43.0
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c
- golang.org/x/sync v0.12.0
- golang.org/x/tools v0.30.0
+ golang.org/x/sync v0.17.0
+ golang.org/x/tools v0.38.0
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
@@ -263,12 +263,14 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
- golang.org/x/mod v0.23.0 // indirect
- golang.org/x/net v0.38.0 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/net v0.46.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
- golang.org/x/term v0.30.0 // indirect
- golang.org/x/text v0.23.0 // indirect
+ golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
+ golang.org/x/term v0.36.0 // indirect
+ golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.9.0 // indirect
+ golang.org/x/tools/go/expect v0.1.1-deprecated // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
@@ -288,7 +290,7 @@ require (
github.com/go-playground/validator/v10 v10.13.0
github.com/peterh/liner v1.2.0 // indirect
github.com/prysmaticlabs/gohashtree v0.0.5-beta
- golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/sys v0.37.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
)
diff --git a/go.sum b/go.sum
index 46d405baa0..b413fb263a 100644
--- a/go.sum
+++ b/go.sum
@@ -1173,8 +1173,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1217,8 +1217,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
-golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1274,8 +1274,8 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1305,8 +1305,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
-golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
+golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1395,8 +1395,10 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
+golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1405,8 +1407,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
-golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
-golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
+golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
+golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1421,8 +1423,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1495,8 +1497,10 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
-golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
+golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/io/file/fileutil.go b/io/file/fileutil.go
index 004d162f33..9996e9529c 100644
--- a/io/file/fileutil.go
+++ b/io/file/fileutil.go
@@ -9,6 +9,7 @@ import (
"os/user"
"path"
"path/filepath"
+ "slices"
"sort"
"strings"
@@ -326,7 +327,7 @@ func HashDir(dir string) (string, error) {
}
h := sha256.New()
- files = append([]string(nil), files...)
+ files = slices.Clone(files)
sort.Strings(files)
for _, file := range files {
hf, err := HashFile(filepath.Join(dir, file))
diff --git a/math/math_helper_test.go b/math/math_helper_test.go
index f90c740125..5aff499652 100644
--- a/math/math_helper_test.go
+++ b/math/math_helper_test.go
@@ -156,21 +156,21 @@ func TestMath_Mod(t *testing.T) {
func BenchmarkIntegerSquareRootBelow52Bits(b *testing.B) {
val := uint64(1 << 33)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.Equal(b, uint64(92681), math.IntegerSquareRoot(val))
}
}
func BenchmarkIntegerSquareRootAbove52Bits(b *testing.B) {
val := uint64(1 << 62)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.Equal(b, uint64(1<<31), math.IntegerSquareRoot(val))
}
}
func BenchmarkSquareRootEffectiveBalance(b *testing.B) {
val := uint64(1 << 62)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.Equal(b, uint64(1<<31), math.CachedSquareRoot(val))
}
}
@@ -178,7 +178,7 @@ func BenchmarkSquareRootEffectiveBalance(b *testing.B) {
func BenchmarkSquareRootBabylonian(b *testing.B) {
//Start with 700K validators' effective balance
val := uint64(22400000000000000)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
sqr := math.CachedSquareRoot(val)
require.Equal(b, true, sqr^2 <= val)
require.Equal(b, true, (sqr+1)*(sqr+1) > val)
@@ -189,7 +189,7 @@ func BenchmarkSquareRootBabylonian(b *testing.B) {
func BenchmarkSquareRootOldWay(b *testing.B) {
//Start with 700K validators' effective balance
val := uint64(22400000000000000)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
sqr := math.IntegerSquareRoot(val)
require.Equal(b, true, sqr^2 <= val)
require.Equal(b, true, (sqr+1)*(sqr+1) > val)
@@ -199,7 +199,7 @@ func BenchmarkSquareRootOldWay(b *testing.B) {
func BenchmarkIntegerSquareRoot_WithDatatable(b *testing.B) {
val := uint64(1024)
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
require.Equal(b, uint64(32), math.IntegerSquareRoot(val))
}
}
diff --git a/monitoring/journald/journalhook_linux.go b/monitoring/journald/journalhook_linux.go
index 091c645e43..7f136687d6 100644
--- a/monitoring/journald/journalhook_linux.go
+++ b/monitoring/journald/journalhook_linux.go
@@ -49,7 +49,7 @@ func stringifyKey(key string) string {
}
// Journal wants strings but logrus takes anything.
-func stringifyEntries(data map[string]interface{}) map[string]string {
+func stringifyEntries(data map[string]any) map[string]string {
entries := make(map[string]string)
for k, v := range data {
key := stringifyKey(k)
diff --git a/monitoring/journald/journalhook_linux_test.go b/monitoring/journald/journalhook_linux_test.go
index 4d2832f202..44ddf7476d 100644
--- a/monitoring/journald/journalhook_linux_test.go
+++ b/monitoring/journald/journalhook_linux_test.go
@@ -3,7 +3,7 @@ package journald
import "testing"
func TestStringifyEntries(t *testing.T) {
- input := map[string]interface{}{
+ input := map[string]any{
"foo": "bar",
"baz": 123,
"foo-foo": "x",
diff --git a/monitoring/prometheus/content_negotiation.go b/monitoring/prometheus/content_negotiation.go
index 0b49e6ab3d..74677f0cf7 100644
--- a/monitoring/prometheus/content_negotiation.go
+++ b/monitoring/prometheus/content_negotiation.go
@@ -20,7 +20,7 @@ type generatedResponse struct {
Err string `json:"error"`
// Data is response output, if any.
- Data interface{} `json:"data"`
+ Data any `json:"data"`
}
// negotiateContentType parses "Accept:" header and returns preferred content type string.
diff --git a/monitoring/prometheus/logrus_collector_test.go b/monitoring/prometheus/logrus_collector_test.go
index 5e4e57852a..fa41e56632 100644
--- a/monitoring/prometheus/logrus_collector_test.go
+++ b/monitoring/prometheus/logrus_collector_test.go
@@ -17,9 +17,9 @@ import (
)
type logger interface {
- Info(args ...interface{})
- Warn(args ...interface{})
- Error(args ...interface{})
+ Info(args ...any)
+ Warn(args ...any)
+ Error(args ...any)
}
func TestLogrusCollector(t *testing.T) {
diff --git a/monitoring/tracing/recovery_interceptor_option.go b/monitoring/tracing/recovery_interceptor_option.go
index 756440a63a..e7d8c05bc5 100644
--- a/monitoring/tracing/recovery_interceptor_option.go
+++ b/monitoring/tracing/recovery_interceptor_option.go
@@ -13,7 +13,7 @@ import (
// RecoveryHandlerFunc is a function that recovers from the panic `p` by returning an `error`.
// The context can be used to extract request scoped metadata and context values.
-func RecoveryHandlerFunc(ctx context.Context, p interface{}) error {
+func RecoveryHandlerFunc(ctx context.Context, p any) error {
span := trace.FromContext(ctx)
if span != nil {
span.SetAttributes(trace.StringAttribute("stack", string(debug.Stack())))
diff --git a/network/auth_test.go b/network/auth_test.go
index 2b7c6327ab..00a3c60b68 100644
--- a/network/auth_test.go
+++ b/network/auth_test.go
@@ -28,7 +28,7 @@ func TestJWTAuthTransport(t *testing.T) {
// The format should be `Bearer ${token}`.
require.Equal(t, 2, len(splitToken))
reqToken = strings.TrimSpace(splitToken[1])
- token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) {
+ token, err := jwt.Parse(reqToken, func(token *jwt.Token) (any, error) {
// We should be doing HMAC signing.
_, ok := token.Method.(*jwt.SigningMethodHMAC)
require.Equal(t, true, ok)
@@ -70,7 +70,7 @@ func TestJWTWithId(t *testing.T) {
// The format should be `Bearer ${token}`.
require.Equal(t, 2, len(splitToken))
reqToken = strings.TrimSpace(splitToken[1])
- token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) {
+ token, err := jwt.Parse(reqToken, func(token *jwt.Token) (any, error) {
// We should be doing HMAC signing.
_, ok := token.Method.(*jwt.SigningMethodHMAC)
require.Equal(t, true, ok)
@@ -114,7 +114,7 @@ func TestJWTWithoutId(t *testing.T) {
// The format should be `Bearer ${token}`.
require.Equal(t, 2, len(splitToken))
reqToken = strings.TrimSpace(splitToken[1])
- token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) {
+ token, err := jwt.Parse(reqToken, func(token *jwt.Token) (any, error) {
// We should be doing HMAC signing.
_, ok := token.Method.(*jwt.SigningMethodHMAC)
require.Equal(t, true, ok)
diff --git a/nogo_config.json b/nogo_config.json
index 01a3b69333..f0dbf3e83f 100644
--- a/nogo_config.json
+++ b/nogo_config.json
@@ -215,7 +215,92 @@
"external/com_github_ethereum_go_ethereum/.*": "Unsafe third party code",
"rules_go_work-.*": "Third party code"
}
- },
+ },
+ "buildtag": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "any": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "appendclipped": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "fmtappendf": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "forvar": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "mapsloop": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "minmax": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "newexpr": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "omitzero": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "rangeint": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "reflecttypefor": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "slicescontains": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "slicessort": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "stringsbuilder": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "stringscutprefix": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "stringsseq": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
+ "waitgroup": {
+ "exclude_files": {
+ "external/.*": "Third party code"
+ }
+ },
"nopanic": {
"exclude_files": {
"validator/web/site_data.go": "generated code",
diff --git a/proto/engine/v1/execution_engine.go b/proto/engine/v1/execution_engine.go
index 00b542b863..d10e249a99 100644
--- a/proto/engine/v1/execution_engine.go
+++ b/proto/engine/v1/execution_engine.go
@@ -9,7 +9,7 @@ type copier[T any] interface {
func copySlice[T any, C copier[T]](original []C) []T {
// Create a new slice with the same length as the original
newSlice := make([]T, len(original))
- for i := 0; i < len(newSlice); i++ {
+ for i := range newSlice {
newSlice[i] = original[i].Copy()
}
return newSlice
diff --git a/proto/engine/v1/execution_engine_fuzz_test.go b/proto/engine/v1/execution_engine_fuzz_test.go
index 43f9bc2f99..b4d0fdf1ec 100644
--- a/proto/engine/v1/execution_engine_fuzz_test.go
+++ b/proto/engine/v1/execution_engine_fuzz_test.go
@@ -25,7 +25,7 @@ func fuzzCopies[T any, C enginev1.Copier[T]](t *testing.T, obj C) {
fuzzer := fuzz.NewWithSeed(0)
amount := 1000
t.Run(fmt.Sprintf("%T", obj), func(t *testing.T) {
- for i := 0; i < amount; i++ {
+ for range amount {
fuzzer.Fuzz(obj) // Populate thing with random values
got := obj.Copy()
require.DeepEqual(t, obj, got)
diff --git a/proto/engine/v1/json_marshal_unmarshal.go b/proto/engine/v1/json_marshal_unmarshal.go
index d89c9988ec..34a7db3700 100644
--- a/proto/engine/v1/json_marshal_unmarshal.go
+++ b/proto/engine/v1/json_marshal_unmarshal.go
@@ -80,7 +80,7 @@ type ExecutionBlock struct {
}
func (e *ExecutionBlock) MarshalJSON() ([]byte, error) {
- decoded := make(map[string]interface{})
+ decoded := make(map[string]any)
encodedHeader, err := e.Header.MarshalJSON()
if err != nil {
return nil, err
@@ -110,7 +110,7 @@ func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
if err := e.Header.UnmarshalJSON(enc); err != nil {
return err
}
- decoded := make(map[string]interface{})
+ decoded := make(map[string]any)
if err := json.Unmarshal(enc, &decoded); err != nil {
return err
}
@@ -162,7 +162,7 @@ func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
// Exit early if there are no transactions stored in the json payload.
return nil
}
- txsList, ok := rawTxList.([]interface{})
+ txsList, ok := rawTxList.([]any)
if !ok {
return errors.Errorf("expected transaction list to be of a slice interface type.")
}
@@ -186,7 +186,7 @@ func (e *ExecutionBlock) UnmarshalJSON(enc []byte) error {
// UnmarshalJSON --
func (b *PayloadIDBytes) UnmarshalJSON(enc []byte) error {
var res [8]byte
- if err := hexutil.UnmarshalFixedJSON(reflect.TypeOf(b), enc, res[:]); err != nil {
+ if err := hexutil.UnmarshalFixedJSON(reflect.TypeFor[*PayloadIDBytes](), enc, res[:]); err != nil {
return err
}
*b = res
diff --git a/proto/engine/v1/json_marshal_unmarshal_test.go b/proto/engine/v1/json_marshal_unmarshal_test.go
index a5bee9e040..e6c1c7df71 100644
--- a/proto/engine/v1/json_marshal_unmarshal_test.go
+++ b/proto/engine/v1/json_marshal_unmarshal_test.go
@@ -296,7 +296,7 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
enc, err := json.Marshal(want)
require.NoError(t, err)
- payloadItems := make(map[string]interface{})
+ payloadItems := make(map[string]any)
require.NoError(t, json.Unmarshal(enc, &payloadItems))
blockHash := want.Hash()
@@ -351,7 +351,7 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
enc, err := json.Marshal(want)
require.NoError(t, err)
- payloadItems := make(map[string]interface{})
+ payloadItems := make(map[string]any)
require.NoError(t, json.Unmarshal(enc, &payloadItems))
blockHash := want.Hash()
@@ -410,7 +410,7 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
enc, err := json.Marshal(want)
require.NoError(t, err)
- payloadItems := make(map[string]interface{})
+ payloadItems := make(map[string]any)
require.NoError(t, json.Unmarshal(enc, &payloadItems))
tx := gethtypes.NewTransaction(
@@ -478,7 +478,7 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
enc, err := json.Marshal(want)
require.NoError(t, err)
- payloadItems := make(map[string]interface{})
+ payloadItems := make(map[string]any)
require.NoError(t, json.Unmarshal(enc, &payloadItems))
blockHash := want.Hash()
@@ -569,7 +569,7 @@ func TestJsonMarshalUnmarshal(t *testing.T) {
enc, err := json.Marshal(want)
require.NoError(t, err)
- payloadItems := make(map[string]interface{})
+ payloadItems := make(map[string]any)
require.NoError(t, json.Unmarshal(enc, &payloadItems))
blockHash := want.Hash()
diff --git a/proto/prysm/v1alpha1/attestation/aggregation/attestations/attestations.go b/proto/prysm/v1alpha1/attestation/aggregation/attestations/attestations.go
index 78889dbb13..0c0aa043ae 100644
--- a/proto/prysm/v1alpha1/attestation/aggregation/attestations/attestations.go
+++ b/proto/prysm/v1alpha1/attestation/aggregation/attestations/attestations.go
@@ -60,7 +60,7 @@ func AggregateDisjointOneBitAtts(atts []ethpb.Att) (ethpb.Att, error) {
}
}
keys := make([]int, len(atts))
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
keys[i] = i
}
idx, err := aggregateAttestations(atts, keys, coverage)
diff --git a/proto/prysm/v1alpha1/attestation/aggregation/attestations/maxcover.go b/proto/prysm/v1alpha1/attestation/aggregation/attestations/maxcover.go
index 4b8c078cbf..ad454a5f2a 100644
--- a/proto/prysm/v1alpha1/attestation/aggregation/attestations/maxcover.go
+++ b/proto/prysm/v1alpha1/attestation/aggregation/attestations/maxcover.go
@@ -27,7 +27,7 @@ func MaxCoverAttestationAggregation(atts []ethpb.Att) ([]ethpb.Att, error) {
// In the future this conversion will be redundant, as attestation bitlist will be of a Bitlist64
// type, so incoming `atts` parameters can be used as candidates list directly.
candidates := make([]*bitfield.Bitlist64, len(atts))
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
var err error
candidates[i], err = atts[i].GetAggregationBits().ToBitlist64()
if err != nil {
@@ -114,7 +114,7 @@ func MaxCoverAttestationAggregation(atts []ethpb.Att) ([]ethpb.Att, error) {
// NewMaxCover returns initialized Maximum Coverage problem for attestations aggregation.
func NewMaxCover(atts []*ethpb.Attestation) *aggregation.MaxCoverProblem {
candidates := make([]*aggregation.MaxCoverCandidate, len(atts))
- for i := 0; i < len(atts); i++ {
+ for i := range atts {
candidates[i] = aggregation.NewMaxCoverCandidate(i, &atts[i].AggregationBits)
}
return &aggregation.MaxCoverProblem{Candidates: candidates}
@@ -126,7 +126,7 @@ func (al attList) aggregate(coverage bitfield.Bitlist) (*ethpb.Attestation, erro
return nil, errors.Wrap(ErrInvalidAttestationCount, "cannot aggregate")
}
signs := make([]bls.Signature, len(al))
- for i := 0; i < len(al); i++ {
+ for i := range al {
sig, err := signatureFromBytes(al[i].GetSignature())
if err != nil {
return nil, err
diff --git a/proto/prysm/v1alpha1/attestation/aggregation/maxcover_bench_test.go b/proto/prysm/v1alpha1/attestation/aggregation/maxcover_bench_test.go
index 8d47200eb1..f65e572b53 100644
--- a/proto/prysm/v1alpha1/attestation/aggregation/maxcover_bench_test.go
+++ b/proto/prysm/v1alpha1/attestation/aggregation/maxcover_bench_test.go
@@ -78,7 +78,7 @@ func BenchmarkMaxCoverProblem_MaxCover(b *testing.B) {
}
b.StartTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
candidates := make([]*aggregation.MaxCoverCandidate, len(bitlists))
for i := 0; i < len(bitlists); i++ {
candidates[i] = aggregation.NewMaxCoverCandidate(i, &bitlists[i])
@@ -98,7 +98,7 @@ func BenchmarkMaxCoverProblem_MaxCover(b *testing.B) {
}
b.StartTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _, err := aggregation.MaxCover(bitlists, len(bitlists), tt.allowOverlaps)
_ = err
}
diff --git a/proto/prysm/v1alpha1/attestation/aggregation/testing/bitlistutils.go b/proto/prysm/v1alpha1/attestation/aggregation/testing/bitlistutils.go
index 48c6392d91..7872fa4bb5 100644
--- a/proto/prysm/v1alpha1/attestation/aggregation/testing/bitlistutils.go
+++ b/proto/prysm/v1alpha1/attestation/aggregation/testing/bitlistutils.go
@@ -14,7 +14,7 @@ import (
// BitlistWithAllBitsSet creates list of bitlists with all bits set.
func BitlistWithAllBitsSet(length uint64) bitfield.Bitlist {
b := bitfield.NewBitlist(length)
- for i := uint64(0); i < length; i++ {
+ for i := range length {
b.SetBitAt(i, true)
}
return b
@@ -23,7 +23,7 @@ func BitlistWithAllBitsSet(length uint64) bitfield.Bitlist {
// BitlistsWithSingleBitSet creates list of bitlists with a single bit set in each.
func BitlistsWithSingleBitSet(n, length uint64) []bitfield.Bitlist {
lists := make([]bitfield.Bitlist, n)
- for i := uint64(0); i < n; i++ {
+ for i := range n {
b := bitfield.NewBitlist(length)
b.SetBitAt(i%length, true)
lists[i] = b
@@ -34,7 +34,7 @@ func BitlistsWithSingleBitSet(n, length uint64) []bitfield.Bitlist {
// Bitlists64WithSingleBitSet creates list of bitlists with a single bit set in each.
func Bitlists64WithSingleBitSet(n, length uint64) []*bitfield.Bitlist64 {
lists := make([]*bitfield.Bitlist64, n)
- for i := uint64(0); i < n; i++ {
+ for i := range n {
b := bitfield.NewBitlist64(length)
b.SetBitAt(i%length, true)
lists[i] = b
@@ -48,7 +48,7 @@ func BitlistsWithMultipleBitSet(t testing.TB, n, length, count uint64) []bitfiel
t.Logf("bitlistsWithMultipleBitSet random seed: %v", seed)
r := rand.New(rand.NewSource(seed)) // #nosec G404
lists := make([]bitfield.Bitlist, n)
- for i := uint64(0); i < n; i++ {
+ for i := range n {
b := bitfield.NewBitlist(length)
keys := r.Perm(int(length)) // lint:ignore uintcast -- This is safe in test code.
for _, key := range keys[:count] {
@@ -65,7 +65,7 @@ func Bitlists64WithMultipleBitSet(t testing.TB, n, length, count uint64) []*bitf
t.Logf("Bitlists64WithMultipleBitSet random seed: %v", seed)
r := rand.New(rand.NewSource(seed)) // #nosec G404
lists := make([]*bitfield.Bitlist64, n)
- for i := uint64(0); i < n; i++ {
+ for i := range n {
b := bitfield.NewBitlist64(length)
keys := r.Perm(int(length)) // lint:ignore uintcast -- This is safe in test code.
for _, key := range keys[:count] {
diff --git a/proto/prysm/v1alpha1/attestation/attestation_utils.go b/proto/prysm/v1alpha1/attestation/attestation_utils.go
index effec69855..086eede8e9 100644
--- a/proto/prysm/v1alpha1/attestation/attestation_utils.go
+++ b/proto/prysm/v1alpha1/attestation/attestation_utils.go
@@ -8,7 +8,6 @@ import (
"fmt"
"runtime/debug"
"slices"
- "sort"
"github.com/OffchainLabs/go-bitfield"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
@@ -45,9 +44,7 @@ func ConvertToIndexed(_ context.Context, attestation ethpb.Att, committees ...[]
return nil, err
}
- sort.Slice(attIndices, func(i, j int) bool {
- return attIndices[i] < attIndices[j]
- })
+ slices.Sort(attIndices)
if attestation.Version() >= version.Electra {
return ðpb.IndexedAttestationElectra{
diff --git a/proto/prysm/v1alpha1/attestation/attestation_utils_test.go b/proto/prysm/v1alpha1/attestation/attestation_utils_test.go
index 565d9132df..a2ec72fe87 100644
--- a/proto/prysm/v1alpha1/attestation/attestation_utils_test.go
+++ b/proto/prysm/v1alpha1/attestation/attestation_utils_test.go
@@ -217,8 +217,7 @@ func BenchmarkAttestingIndices_PartialCommittee(b *testing.B) {
bf := bitfield.Bitlist{0b11111111, 0b11111111, 0b10000111, 0b11111111, 0b100}
committee := []primitives.ValidatorIndex{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, err := attestation.AttestingIndices(ð.Attestation{AggregationBits: bf}, committee)
require.NoError(b, err)
}
@@ -226,7 +225,7 @@ func BenchmarkAttestingIndices_PartialCommittee(b *testing.B) {
func BenchmarkIsValidAttestationIndices(b *testing.B) {
indices := make([]uint64, params.BeaconConfig().MaxValidatorsPerCommittee)
- for i := 0; i < len(indices); i++ {
+ for i := range indices {
indices[i] = uint64(i)
}
att := ð.IndexedAttestation{
@@ -237,8 +236,8 @@ func BenchmarkIsValidAttestationIndices(b *testing.B) {
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
if err := attestation.IsValidAttestationIndices(b.Context(), att, params.BeaconConfig().MaxValidatorsPerCommittee, params.BeaconConfig().MaxCommitteesPerSlot); err != nil {
require.NoError(b, err)
}
@@ -460,14 +459,14 @@ func BenchmarkAttDataIsEqual(b *testing.B) {
b.Run("fast", func(b *testing.B) {
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
assert.Equal(b, true, attestation.AttDataIsEqual(attData1, attData2))
}
})
b.Run("proto.Equal", func(b *testing.B) {
b.ReportAllocs()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
assert.Equal(b, true, attestation.AttDataIsEqual(attData1, attData2))
}
})
diff --git a/proto/prysm/v1alpha1/cloners.go b/proto/prysm/v1alpha1/cloners.go
index 2d76721dd0..436196cc2f 100644
--- a/proto/prysm/v1alpha1/cloners.go
+++ b/proto/prysm/v1alpha1/cloners.go
@@ -11,7 +11,7 @@ type copier[T any] interface {
func CopySlice[T any, C copier[T]](original []C) []T {
// Create a new slice with the same length as the original
newSlice := make([]T, len(original))
- for i := 0; i < len(newSlice); i++ {
+ for i := range newSlice {
newSlice[i] = original[i].Copy()
}
return newSlice
diff --git a/proto/prysm/v1alpha1/cloners_test.go b/proto/prysm/v1alpha1/cloners_test.go
index 1697134968..a6e9975bc9 100644
--- a/proto/prysm/v1alpha1/cloners_test.go
+++ b/proto/prysm/v1alpha1/cloners_test.go
@@ -312,7 +312,7 @@ func TestCopyBlindedBeaconBlockBodyDeneb(t *testing.T) {
func bytes(length int) []byte {
b := make([]byte, length)
- for i := 0; i < length; i++ {
+ for i := range length {
b[i] = uint8(rand.Int31n(255) + 1)
}
return b
@@ -382,7 +382,7 @@ func genAttestation() *v1alpha1.Attestation {
func genAttestations(num int) []*v1alpha1.Attestation {
atts := make([]*v1alpha1.Attestation, num)
- for i := 0; i < num; i++ {
+ for i := range num {
atts[i] = genAttestation()
}
return atts
@@ -461,7 +461,7 @@ func genProposerSlashing() *v1alpha1.ProposerSlashing {
func genProposerSlashings(num int) []*v1alpha1.ProposerSlashing {
ps := make([]*v1alpha1.ProposerSlashing, num)
- for i := 0; i < num; i++ {
+ for i := range num {
ps[i] = genProposerSlashing()
}
return ps
@@ -484,7 +484,7 @@ func genIndexedAttestation() *v1alpha1.IndexedAttestation {
func genAttesterSlashings(num int) []*v1alpha1.AttesterSlashing {
as := make([]*v1alpha1.AttesterSlashing, num)
- for i := 0; i < num; i++ {
+ for i := range num {
as[i] = genAttesterSlashing()
}
return as
@@ -525,7 +525,7 @@ func genDeposit() *v1alpha1.Deposit {
func genDeposits(num int) []*v1alpha1.Deposit {
d := make([]*v1alpha1.Deposit, num)
- for i := 0; i < num; i++ {
+ for i := range num {
d[i] = genDeposit()
}
return d
@@ -547,7 +547,7 @@ func genSignedVoluntaryExit() *v1alpha1.SignedVoluntaryExit {
func genSignedVoluntaryExits(num int) []*v1alpha1.SignedVoluntaryExit {
sv := make([]*v1alpha1.SignedVoluntaryExit, num)
- for i := 0; i < num; i++ {
+ for i := range num {
sv[i] = genSignedVoluntaryExit()
}
return sv
@@ -765,7 +765,7 @@ func genBlindedBeaconBlockBodyDeneb() *v1alpha1.BlindedBeaconBlockBodyDeneb {
func getKZGCommitments(n int) [][]byte {
kzgs := make([][]byte, n)
- for i := 0; i < n; i++ {
+ for i := range n {
kzgs[i] = bytes(48)
}
return kzgs
@@ -941,7 +941,7 @@ func genWithdrawal() *enginev1.Withdrawal {
func genBLSToExecutionChanges(num int) []*v1alpha1.SignedBLSToExecutionChange {
changes := make([]*v1alpha1.SignedBLSToExecutionChange, num)
- for i := 0; i < num; i++ {
+ for i := range num {
changes[i] = genBLSToExecutionChange()
}
return changes
@@ -969,7 +969,7 @@ func genAttestationElectra() *v1alpha1.AttestationElectra {
func genAttesterSlashingsElectra(num int) []*v1alpha1.AttesterSlashingElectra {
as := make([]*v1alpha1.AttesterSlashingElectra, num)
- for i := 0; i < num; i++ {
+ for i := range num {
as[i] = genAttesterSlashingElectra()
}
return as
@@ -992,7 +992,7 @@ func genIndexedAttestationElectra() *v1alpha1.IndexedAttestationElectra {
func genAttestationsElectra(num int) []*v1alpha1.AttestationElectra {
atts := make([]*v1alpha1.AttestationElectra, num)
- for i := 0; i < num; i++ {
+ for i := range num {
atts[i] = genAttestationElectra()
}
return atts
@@ -1078,7 +1078,7 @@ func genExecutionRequests() *enginev1.ExecutionRequests {
func genDepositRequests(num int) []*enginev1.DepositRequest {
drs := make([]*enginev1.DepositRequest, num)
- for i := 0; i < num; i++ {
+ for i := range num {
drs[i] = genDepositRequest()
}
return drs
@@ -1096,7 +1096,7 @@ func genDepositRequest() *enginev1.DepositRequest {
func genWithdrawalRequests(num int) []*enginev1.WithdrawalRequest {
wrs := make([]*enginev1.WithdrawalRequest, num)
- for i := 0; i < num; i++ {
+ for i := range num {
wrs[i] = genWithdrawalRequest()
}
return wrs
@@ -1112,7 +1112,7 @@ func genWithdrawalRequest() *enginev1.WithdrawalRequest {
func genConsolidationRequests(num int) []*enginev1.ConsolidationRequest {
crs := make([]*enginev1.ConsolidationRequest, num)
- for i := 0; i < num; i++ {
+ for i := range num {
crs[i] = genConsolidationRequest()
}
return crs
diff --git a/proto/prysm/v1alpha1/fuzz_test.go b/proto/prysm/v1alpha1/fuzz_test.go
index b8b8c138db..bcfbf10e05 100644
--- a/proto/prysm/v1alpha1/fuzz_test.go
+++ b/proto/prysm/v1alpha1/fuzz_test.go
@@ -13,7 +13,7 @@ func fuzzCopies[T any, C eth.Copier[T]](t *testing.T, obj C) {
fuzzer := fuzz.NewWithSeed(0)
amount := 1000
t.Run(fmt.Sprintf("%T", obj), func(t *testing.T) {
- for i := 0; i < amount; i++ {
+ for range amount {
fuzzer.Fuzz(obj) // Populate thing with random values
got := obj.Copy()
diff --git a/proto/prysm/v1alpha1/metadata/metadata_interfaces.go b/proto/prysm/v1alpha1/metadata/metadata_interfaces.go
index dec759f4ce..d2a04e5cf0 100644
--- a/proto/prysm/v1alpha1/metadata/metadata_interfaces.go
+++ b/proto/prysm/v1alpha1/metadata/metadata_interfaces.go
@@ -12,7 +12,7 @@ type Metadata interface {
AttnetsBitfield() bitfield.Bitvector64
SyncnetsBitfield() bitfield.Bitvector4
CustodyGroupCount() uint64
- InnerObject() interface{}
+ InnerObject() any
IsNil() bool
Copy() Metadata
ssz.Marshaler
diff --git a/proto/testing/tags_test.go b/proto/testing/tags_test.go
index a164347019..fc8d96bf08 100644
--- a/proto/testing/tags_test.go
+++ b/proto/testing/tags_test.go
@@ -35,7 +35,7 @@ func TestSSZTagSize(t *testing.T) {
assert.Equal(t, pubKeySize, sizes[0], "Unexpected signature size")
}
-func sszTagSizes(i interface{}, fName string) ([]int, error) {
+func sszTagSizes(i any, fName string) ([]int, error) {
v := reflect.ValueOf(i)
field, exists := v.Type().FieldByName(fName)
if !exists {
@@ -49,7 +49,7 @@ func sszTagSizes(i interface{}, fName string) ([]int, error) {
items := strings.Split(tag[start+1:], ",")
sizes := make([]int, len(items))
var err error
- for i := 0; i < len(items); i++ {
+ for i := range items {
if items[i] == "?" {
sizes[i] = 0
continue
diff --git a/runtime/interop/generate_genesis_state.go b/runtime/interop/generate_genesis_state.go
index 83e5705edf..e2cfcbd841 100644
--- a/runtime/interop/generate_genesis_state.go
+++ b/runtime/interop/generate_genesis_state.go
@@ -79,7 +79,7 @@ func GenerateGenesisStateFromDepositData(
// GenerateDepositsFromData a list of deposit items by creating proofs for each of them from a sparse Merkle trie.
func GenerateDepositsFromData(depositDataItems []*ethpb.Deposit_Data, trie *trie.SparseMerkleTrie) ([]*ethpb.Deposit, error) {
deposits := make([]*ethpb.Deposit, len(depositDataItems))
- results, err := async.Scatter(len(depositDataItems), func(offset int, entries int, _ *sync.RWMutex) (interface{}, error) {
+ results, err := async.Scatter(len(depositDataItems), func(offset int, entries int, _ *sync.RWMutex) (any, error) {
return generateDepositsFromData(depositDataItems[offset:offset+entries], offset, trie)
})
if err != nil {
@@ -119,7 +119,7 @@ func DepositDataFromKeys(privKeys []bls.SecretKey, pubKeys []bls.PublicKey) ([]*
}
depositDataItems := make([]*ethpb.Deposit_Data, len(privKeys))
depositDataRoots := make([][]byte, len(privKeys))
- results, err := async.Scatter(len(privKeys), func(offset int, entries int, _ *sync.RWMutex) (interface{}, error) {
+ results, err := async.Scatter(len(privKeys), func(offset int, entries int, _ *sync.RWMutex) (any, error) {
items, roots, err := depositDataFromKeys(privKeys[offset:offset+entries], pubKeys[offset:offset+entries], 0)
return &depositData{items: items, roots: roots}, err
})
@@ -145,7 +145,7 @@ func DepositDataFromKeysWithExecCreds(privKeys []bls.SecretKey, pubKeys []bls.Pu
func depositDataFromKeys(privKeys []bls.SecretKey, pubKeys []bls.PublicKey, numOfCreds uint64) ([]*ethpb.Deposit_Data, [][]byte, error) {
dataRoots := make([][]byte, len(privKeys))
depositDataItems := make([]*ethpb.Deposit_Data, len(privKeys))
- for i := 0; i < len(privKeys); i++ {
+ for i := range privKeys {
withCred := uint64(i) < numOfCreds
data, err := createDepositData(privKeys[i], pubKeys[i], withCred)
if err != nil {
diff --git a/runtime/interop/generate_keys.go b/runtime/interop/generate_keys.go
index 655dad5abc..84ac022b6f 100644
--- a/runtime/interop/generate_keys.go
+++ b/runtime/interop/generate_keys.go
@@ -27,7 +27,7 @@ func DeterministicallyGenerateKeys(startIndex, numKeys uint64) ([]bls.SecretKey,
publics []bls.PublicKey
}
// lint:ignore uintcast -- this is safe because we can reasonably expect that the number of keys is less than max int64.
- results, err := async.Scatter(int(numKeys), func(offset int, entries int, _ *sync.RWMutex) (interface{}, error) {
+ results, err := async.Scatter(int(numKeys), func(offset int, entries int, _ *sync.RWMutex) (any, error) {
secs, pubs, err := deterministicallyGenerateKeys(uint64(offset)+startIndex, uint64(entries))
return &keys{secrets: secs, publics: pubs}, err
})
diff --git a/runtime/interop/premine-state.go b/runtime/interop/premine-state.go
index be38b420a2..292ef1a907 100644
--- a/runtime/interop/premine-state.go
+++ b/runtime/interop/premine-state.go
@@ -372,7 +372,7 @@ func (s *PremineGenesisConfig) setInactivityScores(g state.BeaconState) error {
}
scoresMissing := len(g.Validators()) - len(scores)
if scoresMissing > 0 {
- for i := 0; i < scoresMissing; i++ {
+ for range scoresMissing {
scores = append(scores, 0)
}
}
@@ -390,7 +390,7 @@ func (s *PremineGenesisConfig) setCurrentEpochParticipation(g state.BeaconState)
}
missing := len(g.Validators()) - len(p)
if missing > 0 {
- for i := 0; i < missing; i++ {
+ for range missing {
p = append(p, 0)
}
}
@@ -408,7 +408,7 @@ func (s *PremineGenesisConfig) setPrevEpochParticipation(g state.BeaconState) er
}
missing := len(g.Validators()) - len(p)
if missing > 0 {
- for i := 0; i < missing; i++ {
+ for range missing {
p = append(p, 0)
}
}
@@ -755,7 +755,7 @@ func unwrapUint64Ptr(u *uint64) uint64 {
func nZeroRoots(n uint64) [][]byte {
roots := make([][]byte, n)
zh := params.BeaconConfig().ZeroHash[:]
- for i := uint64(0); i < n; i++ {
+ for i := range n {
roots[i] = zh
}
return roots
@@ -763,7 +763,7 @@ func nZeroRoots(n uint64) [][]byte {
func nSetRoots(n uint64, r []byte) [][]byte {
roots := make([][]byte, n)
- for i := uint64(0); i < n; i++ {
+ for i := range n {
h := make([]byte, 32)
copy(h, r)
roots[i] = h
diff --git a/runtime/logging/logrus-prefixed-formatter/formatter.go b/runtime/logging/logrus-prefixed-formatter/formatter.go
index 9f43579ca7..241802b654 100644
--- a/runtime/logging/logrus-prefixed-formatter/formatter.go
+++ b/runtime/logging/logrus-prefixed-formatter/formatter.go
@@ -343,7 +343,7 @@ func extractPrefix(msg string) (string, string) {
return prefix, msg
}
-func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}, appendSpace bool) error {
+func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value any, appendSpace bool) error {
b.WriteString(key)
b.WriteByte('=')
if err := f.appendValue(b, value); err != nil {
@@ -356,7 +356,7 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf
return nil
}
-func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) (err error) {
+func (f *TextFormatter) appendValue(b *bytes.Buffer, value any) (err error) {
switch value := value.(type) {
case string:
if !f.needsQuoting(value) {
diff --git a/runtime/service_registry.go b/runtime/service_registry.go
index 84fcba0a04..1c51802a8e 100644
--- a/runtime/service_registry.go
+++ b/runtime/service_registry.go
@@ -83,7 +83,7 @@ func (s *ServiceRegistry) RegisterService(service Service) error {
// FetchService takes in a struct pointer and sets the value of that pointer
// to a service currently stored in the service registry. This ensures the input argument is
// set to the right pointer that refers to the originally registered service.
-func (s *ServiceRegistry) FetchService(service interface{}) error {
+func (s *ServiceRegistry) FetchService(service any) error {
if reflect.TypeOf(service).Kind() != reflect.Ptr {
return fmt.Errorf("input must be of pointer type, received value type instead: %T", service)
}
diff --git a/runtime/service_registry_test.go b/runtime/service_registry_test.go
index 1fbf0173c7..266956ed17 100644
--- a/runtime/service_registry_test.go
+++ b/runtime/service_registry_test.go
@@ -63,11 +63,11 @@ func TestRegisterService_Different(t *testing.T) {
require.Equal(t, 2, len(registry.serviceTypes))
- _, exists := registry.services[reflect.TypeOf(m)]
- assert.Equal(t, true, exists, "service of type %v not registered", reflect.TypeOf(m))
+ _, exists := registry.services[reflect.TypeFor[*mockService]()]
+ assert.Equal(t, true, exists, "service of type %v not registered", reflect.TypeFor[*mockService]())
- _, exists = registry.services[reflect.TypeOf(s)]
- assert.Equal(t, true, exists, "service of type %v not registered", reflect.TypeOf(s))
+ _, exists = registry.services[reflect.TypeFor[*secondMockService]()]
+ assert.Equal(t, true, exists, "service of type %v not registered", reflect.TypeFor[*secondMockService]())
}
func TestFetchService_OK(t *testing.T) {
@@ -104,6 +104,6 @@ func TestServiceStatus_OK(t *testing.T) {
statuses := registry.Statuses()
- assert.ErrorContains(t, "something bad has happened", statuses[reflect.TypeOf(m)])
- assert.ErrorContains(t, "woah, horsee", statuses[reflect.TypeOf(s)])
+ assert.ErrorContains(t, "something bad has happened", statuses[reflect.TypeFor[*mockService]()])
+ assert.ErrorContains(t, "woah, horsee", statuses[reflect.TypeFor[*secondMockService]()])
}
diff --git a/testing/assert/assertions.go b/testing/assert/assertions.go
index 434bdc6b4a..79620a4733 100644
--- a/testing/assert/assertions.go
+++ b/testing/assert/assertions.go
@@ -6,12 +6,12 @@ import (
)
// Equal compares values using comparison operator.
-func Equal(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func Equal(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.Equal(tb.Errorf, expected, actual, msg...)
}
// NotEqual compares values using comparison operator.
-func NotEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func NotEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.NotEqual(tb.Errorf, expected, actual, msg...)
}
@@ -19,7 +19,7 @@ func NotEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, ms
// NOTE: this function does not work for checking arrays/slices or maps of protobuf messages.
// For arrays/slices, please use DeepSSZEqual.
// For maps, please iterate through and compare the individual keys and values.
-func DeepEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepEqual(tb.Errorf, expected, actual, msg...)
}
@@ -27,57 +27,57 @@ func DeepEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, m
// NOTE: this function does not work for checking arrays/slices or maps of protobuf messages.
// For arrays/slices, please use DeepNotSSZEqual.
// For maps, please iterate through and compare the individual keys and values.
-func DeepNotEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepNotEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepNotEqual(tb.Errorf, expected, actual, msg...)
}
// DeepSSZEqual compares values using ssz.DeepEqual.
-func DeepSSZEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepSSZEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepSSZEqual(tb.Errorf, expected, actual, msg...)
}
// DeepNotSSZEqual compares values using ssz.DeepEqual.
-func DeepNotSSZEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepNotSSZEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepNotSSZEqual(tb.Errorf, expected, actual, msg...)
}
// StringContains asserts a string contains specified substring.
-func StringContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...interface{}) {
+func StringContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...any) {
assertions.StringContains(tb.Errorf, expected, actual, true, msg...)
}
// StringNotContains asserts a string does not contain specified substring.
-func StringNotContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...interface{}) {
+func StringNotContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...any) {
assertions.StringContains(tb.Errorf, expected, actual, false, msg...)
}
// NoError asserts that error is nil.
-func NoError(tb assertions.AssertionTestingTB, err error, msg ...interface{}) {
+func NoError(tb assertions.AssertionTestingTB, err error, msg ...any) {
assertions.NoError(tb.Errorf, err, msg...)
}
// ErrorContains asserts that actual error contains wanted message.
-func ErrorContains(tb assertions.AssertionTestingTB, want string, err error, msg ...interface{}) {
+func ErrorContains(tb assertions.AssertionTestingTB, want string, err error, msg ...any) {
assertions.ErrorContains(tb.Errorf, want, err, msg...)
}
// NotNil asserts that passed value is not nil.
-func NotNil(tb assertions.AssertionTestingTB, obj interface{}, msg ...interface{}) {
+func NotNil(tb assertions.AssertionTestingTB, obj any, msg ...any) {
assertions.NotNil(tb.Errorf, obj, msg...)
}
// LogsContain checks that the desired string is a subset of the current log output.
-func LogsContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...interface{}) {
+func LogsContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...any) {
assertions.LogsContain(tb.Errorf, hook, want, true, msg...)
}
// LogsDoNotContain is the inverse check of LogsContain.
-func LogsDoNotContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...interface{}) {
+func LogsDoNotContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...any) {
assertions.LogsContain(tb.Errorf, hook, want, false, msg...)
}
// NotEmpty checks that the object fields are not empty. This method also checks all of the
// pointer fields to ensure none of those fields are empty.
-func NotEmpty(tb assertions.AssertionTestingTB, obj interface{}, msg ...interface{}) {
+func NotEmpty(tb assertions.AssertionTestingTB, obj any, msg ...any) {
assertions.NotEmpty(tb.Errorf, obj, msg...)
}
diff --git a/testing/assertions/assertions.go b/testing/assertions/assertions.go
index 9aa2f11800..27a2570e3f 100644
--- a/testing/assertions/assertions.go
+++ b/testing/assertions/assertions.go
@@ -19,20 +19,20 @@ import (
// AssertionTestingTB exposes enough testing.TB methods for assertions.
type AssertionTestingTB interface {
- Errorf(format string, args ...interface{})
- Fatalf(format string, args ...interface{})
+ Errorf(format string, args ...any)
+ Fatalf(format string, args ...any)
}
-type assertionLoggerFn func(string, ...interface{})
+type assertionLoggerFn func(string, ...any)
func SprintfAssertionLoggerFn(s *string) assertionLoggerFn {
- return func(ef string, eargs ...interface{}) {
+ return func(ef string, eargs ...any) {
*s = fmt.Sprintf(ef, eargs...)
}
}
// Equal compares values using comparison operator.
-func Equal(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...interface{}) {
+func Equal(loggerFn assertionLoggerFn, expected, actual any, msg ...any) {
if expected != actual {
errMsg := parseMsg("Values are not equal", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -41,7 +41,7 @@ func Equal(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...inte
}
// NotEqual compares values using comparison operator.
-func NotEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...interface{}) {
+func NotEqual(loggerFn assertionLoggerFn, expected, actual any, msg ...any) {
if expected == actual {
errMsg := parseMsg("Values are equal", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -50,7 +50,7 @@ func NotEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...i
}
// DeepEqual compares values using DeepEqual.
-func DeepEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...interface{}) {
+func DeepEqual(loggerFn assertionLoggerFn, expected, actual any, msg ...any) {
if !isDeepEqual(expected, actual) {
errMsg := parseMsg("Values are not equal", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -68,7 +68,7 @@ var protobufPrivateFields = map[string]bool{
"state": true,
}
-func ProtobufPrettyDiff(a, b interface{}) string {
+func ProtobufPrettyDiff(a, b any) string {
d, _ := messagediff.DeepDiff(a, b)
var dstr []string
appendNotProto := func(path, str string) {
@@ -92,7 +92,7 @@ func ProtobufPrettyDiff(a, b interface{}) string {
}
// DeepNotEqual compares values using DeepEqual.
-func DeepNotEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...interface{}) {
+func DeepNotEqual(loggerFn assertionLoggerFn, expected, actual any, msg ...any) {
if isDeepEqual(expected, actual) {
errMsg := parseMsg("Values are equal", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -101,7 +101,7 @@ func DeepNotEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg
}
// DeepSSZEqual compares values using ssz.DeepEqual.
-func DeepSSZEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...interface{}) {
+func DeepSSZEqual(loggerFn assertionLoggerFn, expected, actual any, msg ...any) {
if !equality.DeepEqual(expected, actual) {
errMsg := parseMsg("Values are not equal", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -111,7 +111,7 @@ func DeepSSZEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg
}
// DeepNotSSZEqual compares values using ssz.DeepEqual.
-func DeepNotSSZEqual(loggerFn assertionLoggerFn, expected, actual interface{}, msg ...interface{}) {
+func DeepNotSSZEqual(loggerFn assertionLoggerFn, expected, actual any, msg ...any) {
if equality.DeepEqual(expected, actual) {
errMsg := parseMsg("Values are equal", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -120,7 +120,7 @@ func DeepNotSSZEqual(loggerFn assertionLoggerFn, expected, actual interface{}, m
}
// StringContains checks whether a string contains specified substring. If flag is false, inverse is checked.
-func StringContains(loggerFn assertionLoggerFn, expected, actual string, flag bool, msg ...interface{}) {
+func StringContains(loggerFn assertionLoggerFn, expected, actual string, flag bool, msg ...any) {
if flag {
if !strings.Contains(actual, expected) {
errMsg := parseMsg("Expected substring is not found", msg...)
@@ -137,7 +137,7 @@ func StringContains(loggerFn assertionLoggerFn, expected, actual string, flag bo
}
// NoError asserts that error is nil.
-func NoError(loggerFn assertionLoggerFn, err error, msg ...interface{}) {
+func NoError(loggerFn assertionLoggerFn, err error, msg ...any) {
// reflect.ValueOf is needed for nil instances of custom types implementing Error
if err != nil && !reflect.ValueOf(err).IsNil() {
errMsg := parseMsg("Unexpected error", msg...)
@@ -148,7 +148,7 @@ func NoError(loggerFn assertionLoggerFn, err error, msg ...interface{}) {
// ErrorIs uses Errors.Is to recursively unwrap err looking for target in the chain.
// If any error in the chain matches target, the assertion will pass.
-func ErrorIs(loggerFn assertionLoggerFn, err, target error, msg ...interface{}) {
+func ErrorIs(loggerFn assertionLoggerFn, err, target error, msg ...any) {
if !errors.Is(err, target) {
errMsg := parseMsg(fmt.Sprintf("error %s", target), msg...)
_, file, line, _ := runtime.Caller(2)
@@ -157,7 +157,7 @@ func ErrorIs(loggerFn assertionLoggerFn, err, target error, msg ...interface{})
}
// ErrorContains asserts that actual error contains wanted message.
-func ErrorContains(loggerFn assertionLoggerFn, want string, err error, msg ...interface{}) {
+func ErrorContains(loggerFn assertionLoggerFn, want string, err error, msg ...any) {
if want == "" {
loggerFn("Want string can't be empty")
}
@@ -169,7 +169,7 @@ func ErrorContains(loggerFn assertionLoggerFn, want string, err error, msg ...in
}
// NotNil asserts that passed value is not nil.
-func NotNil(loggerFn assertionLoggerFn, obj interface{}, msg ...interface{}) {
+func NotNil(loggerFn assertionLoggerFn, obj any, msg ...any) {
if deepNil(obj) {
errMsg := parseMsg("Unexpected nil value", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -178,7 +178,7 @@ func NotNil(loggerFn assertionLoggerFn, obj interface{}, msg ...interface{}) {
}
// IsNil asserts that observed value is nil.
-func IsNil(loggerFn assertionLoggerFn, got interface{}, msg ...interface{}) {
+func IsNil(loggerFn assertionLoggerFn, got any, msg ...any) {
if !deepNil(got) {
errMsg := parseMsg("Value is unexpectedly not nil", msg...)
_, file, line, _ := runtime.Caller(2)
@@ -187,7 +187,7 @@ func IsNil(loggerFn assertionLoggerFn, got interface{}, msg ...interface{}) {
}
// deepNil checks that underlying value of obj is nil.
-func deepNil(got interface{}) bool {
+func deepNil(got any) bool {
if got == nil {
return true
}
@@ -200,7 +200,7 @@ func deepNil(got interface{}) bool {
}
// LogsContain checks whether a given substring is a part of logs. If flag=false, inverse is checked.
-func LogsContain(loggerFn assertionLoggerFn, hook *test.Hook, want string, flag bool, msg ...interface{}) {
+func LogsContain(loggerFn assertionLoggerFn, hook *test.Hook, want string, flag bool, msg ...any) {
_, file, line, _ := runtime.Caller(2)
entries := hook.AllEntries()
logs := make([]string, 0, len(entries))
@@ -236,7 +236,7 @@ func LogsContain(loggerFn assertionLoggerFn, hook *test.Hook, want string, flag
}
}
-func parseMsg(defaultMsg string, msg ...interface{}) string {
+func parseMsg(defaultMsg string, msg ...any) string {
if len(msg) >= 1 {
msgFormat, ok := msg[0].(string)
if !ok {
@@ -247,7 +247,7 @@ func parseMsg(defaultMsg string, msg ...interface{}) string {
return defaultMsg
}
-func isDeepEqual(expected, actual interface{}) bool {
+func isDeepEqual(expected, actual any) bool {
_, isProto := expected.(proto.Message)
if isProto {
return proto.Equal(expected.(proto.Message), actual.(proto.Message))
@@ -257,7 +257,7 @@ func isDeepEqual(expected, actual interface{}) bool {
// NotEmpty asserts that an object's fields are not empty. This function recursively checks each
// pointer / struct field.
-func NotEmpty(loggerFn assertionLoggerFn, obj interface{}, msg ...interface{}) {
+func NotEmpty(loggerFn assertionLoggerFn, obj any, msg ...any) {
_, ignoreFieldsWithoutTags := obj.(proto.Message)
notEmpty(loggerFn, obj, ignoreFieldsWithoutTags, []string{} /*fields*/, 0 /*stackSize*/, msg...)
}
@@ -265,7 +265,7 @@ func NotEmpty(loggerFn assertionLoggerFn, obj interface{}, msg ...interface{}) {
// notEmpty checks all fields are not zero, including pointer field references to other structs.
// This method has the option to ignore fields without struct tags, which is helpful for checking
// protobuf messages that have internal fields.
-func notEmpty(loggerFn assertionLoggerFn, obj interface{}, ignoreFieldsWithoutTags bool, fields []string, stackSize int, msg ...interface{}) {
+func notEmpty(loggerFn assertionLoggerFn, obj any, ignoreFieldsWithoutTags bool, fields []string, stackSize int, msg ...any) {
var v reflect.Value
if vo, ok := obj.(reflect.Value); ok {
v = reflect.Indirect(vo)
@@ -333,11 +333,11 @@ type TBMock struct {
}
// Errorf writes testing logs to ErrorfMsg.
-func (tb *TBMock) Errorf(format string, args ...interface{}) {
+func (tb *TBMock) Errorf(format string, args ...any) {
tb.ErrorfMsg = fmt.Sprintf(format, args...)
}
// Fatalf writes testing logs to FatalfMsg.
-func (tb *TBMock) Fatalf(format string, args ...interface{}) {
+func (tb *TBMock) Fatalf(format string, args ...any) {
tb.FatalfMsg = fmt.Sprintf(format, args...)
}
diff --git a/testing/assertions/assertions_test.go b/testing/assertions/assertions_test.go
index c81034c90a..0cd40cb0d1 100644
--- a/testing/assertions/assertions_test.go
+++ b/testing/assertions/assertions_test.go
@@ -20,9 +20,9 @@ import (
func Test_Equal(t *testing.T) {
type args struct {
tb *assertions.TBMock
- expected interface{}
- actual interface{}
- msgs []interface{}
+ expected any
+ actual any
+ msgs []any
}
tests := []struct {
name string
@@ -61,7 +61,7 @@ func Test_Equal(t *testing.T) {
tb: &assertions.TBMock{},
expected: 42,
actual: 41,
- msgs: []interface{}{"Custom values are not equal"},
+ msgs: []any{"Custom values are not equal"},
},
expectedErr: "Custom values are not equal, want: 42 (int), got: 41 (int)",
},
@@ -71,7 +71,7 @@ func Test_Equal(t *testing.T) {
tb: &assertions.TBMock{},
expected: 42,
actual: 41,
- msgs: []interface{}{"Custom values are not equal (for slot %d)", 12},
+ msgs: []any{"Custom values are not equal (for slot %d)", 12},
},
expectedErr: "Custom values are not equal (for slot 12), want: 42 (int), got: 41 (int)",
},
@@ -98,9 +98,9 @@ func Test_Equal(t *testing.T) {
func Test_NotEqual(t *testing.T) {
type args struct {
tb *assertions.TBMock
- expected interface{}
- actual interface{}
- msgs []interface{}
+ expected any
+ actual any
+ msgs []any
}
tests := []struct {
name string
@@ -138,7 +138,7 @@ func Test_NotEqual(t *testing.T) {
tb: &assertions.TBMock{},
expected: 42,
actual: 42,
- msgs: []interface{}{"Custom values are equal"},
+ msgs: []any{"Custom values are equal"},
},
expectedErr: "Custom values are equal, both values are equal",
},
@@ -165,9 +165,9 @@ func Test_NotEqual(t *testing.T) {
func TestAssert_DeepEqual(t *testing.T) {
type args struct {
tb *assertions.TBMock
- expected interface{}
- actual interface{}
- msgs []interface{}
+ expected any
+ actual any
+ msgs []any
}
tests := []struct {
name string
@@ -197,7 +197,7 @@ func TestAssert_DeepEqual(t *testing.T) {
tb: &assertions.TBMock{},
expected: struct{ i int }{42},
actual: struct{ i int }{41},
- msgs: []interface{}{"Custom values are not equal"},
+ msgs: []any{"Custom values are not equal"},
},
expectedErr: "Custom values are not equal, expected != actual, diff: struct{ i int }{\n- \ti: 42,\n+ \ti: 41,\n }",
},
@@ -207,7 +207,7 @@ func TestAssert_DeepEqual(t *testing.T) {
tb: &assertions.TBMock{},
expected: struct{ i int }{42},
actual: struct{ i int }{41},
- msgs: []interface{}{"Custom values are not equal (for slot %d)", 12},
+ msgs: []any{"Custom values are not equal (for slot %d)", 12},
},
expectedErr: "Custom values are not equal (for slot 12), expected != actual, diff: struct{ i int }{\n- \ti: 42,\n+ \ti: 41,\n }\n",
},
@@ -249,9 +249,9 @@ func TestAssert_DeepEqual(t *testing.T) {
func TestAssert_DeepNotEqual(t *testing.T) {
type args struct {
tb *assertions.TBMock
- expected interface{}
- actual interface{}
- msgs []interface{}
+ expected any
+ actual any
+ msgs []any
}
tests := []struct {
name string
@@ -281,7 +281,7 @@ func TestAssert_DeepNotEqual(t *testing.T) {
tb: &assertions.TBMock{},
expected: struct{ i int }{42},
actual: struct{ i int }{42},
- msgs: []interface{}{"Custom values are equal"},
+ msgs: []any{"Custom values are equal"},
},
expectedErr: "Custom values are equal, want: struct { i int }{i:42}, got: struct { i int }{i:42}",
},
@@ -291,7 +291,7 @@ func TestAssert_DeepNotEqual(t *testing.T) {
tb: &assertions.TBMock{},
expected: struct{ i int }{42},
actual: struct{ i int }{42},
- msgs: []interface{}{"Custom values are equal (for slot %d)", 12},
+ msgs: []any{"Custom values are equal (for slot %d)", 12},
},
expectedErr: "Custom values are equal (for slot 12), want: struct { i int }{i:42}, got: struct { i int }{i:42}",
},
@@ -318,8 +318,8 @@ func TestAssert_DeepNotEqual(t *testing.T) {
func TestAssert_DeepSSZEqual(t *testing.T) {
type args struct {
tb *assertions.TBMock
- expected interface{}
- actual interface{}
+ expected any
+ actual any
}
tests := []struct {
name string
@@ -380,8 +380,8 @@ func TestAssert_DeepSSZEqual(t *testing.T) {
func TestAssert_DeepNotSSZEqual(t *testing.T) {
type args struct {
tb *assertions.TBMock
- expected interface{}
- actual interface{}
+ expected any
+ actual any
}
tests := []struct {
name string
@@ -443,7 +443,7 @@ func TestAssert_NoError(t *testing.T) {
type args struct {
tb *assertions.TBMock
err error
- msgs []interface{}
+ msgs []any
}
tests := []struct {
name string
@@ -469,7 +469,7 @@ func TestAssert_NoError(t *testing.T) {
args: args{
tb: &assertions.TBMock{},
err: errors.New("failed"),
- msgs: []interface{}{"Custom error message"},
+ msgs: []any{"Custom error message"},
},
expectedErr: "Custom error message: failed",
},
@@ -478,7 +478,7 @@ func TestAssert_NoError(t *testing.T) {
args: args{
tb: &assertions.TBMock{},
err: errors.New("failed"),
- msgs: []interface{}{"Custom error message (for slot %d)", 12},
+ msgs: []any{"Custom error message (for slot %d)", 12},
},
expectedErr: "Custom error message (for slot 12): failed",
},
@@ -507,7 +507,7 @@ func TestAssert_ErrorContains(t *testing.T) {
tb *assertions.TBMock
want string
err error
- msgs []interface{}
+ msgs []any
}
tests := []struct {
name string
@@ -546,7 +546,7 @@ func TestAssert_ErrorContains(t *testing.T) {
tb: &assertions.TBMock{},
want: "another error",
err: errors.New("failed"),
- msgs: []interface{}{"Something wrong"},
+ msgs: []any{"Something wrong"},
},
expectedErr: "Something wrong, got: failed, want: another error",
},
@@ -556,7 +556,7 @@ func TestAssert_ErrorContains(t *testing.T) {
tb: &assertions.TBMock{},
want: "failed",
err: errors.New("failed"),
- msgs: []interface{}{"Something wrong"},
+ msgs: []any{"Something wrong"},
},
expectedErr: "",
},
@@ -566,7 +566,7 @@ func TestAssert_ErrorContains(t *testing.T) {
tb: &assertions.TBMock{},
want: "another error",
err: errors.New("failed"),
- msgs: []interface{}{"Something wrong (for slot %d)", 12},
+ msgs: []any{"Something wrong (for slot %d)", 12},
},
expectedErr: "Something wrong (for slot 12), got: failed, want: another error",
},
@@ -576,7 +576,7 @@ func TestAssert_ErrorContains(t *testing.T) {
tb: &assertions.TBMock{},
want: "failed",
err: errors.New("failed"),
- msgs: []interface{}{"Something wrong (for slot %d)", 12},
+ msgs: []any{"Something wrong (for slot %d)", 12},
},
expectedErr: "",
},
@@ -586,7 +586,7 @@ func TestAssert_ErrorContains(t *testing.T) {
tb: &assertions.TBMock{},
want: "",
err: errors.New("failed"),
- msgs: []interface{}{"Something wrong (for slot %d)", 12},
+ msgs: []any{"Something wrong (for slot %d)", 12},
},
expectedErr: "Want string can't be empty",
},
@@ -613,8 +613,8 @@ func TestAssert_ErrorContains(t *testing.T) {
func Test_NotNil(t *testing.T) {
type args struct {
tb *assertions.TBMock
- obj interface{}
- msgs []interface{}
+ obj any
+ msgs []any
}
var nilBlock *eth.SignedBeaconBlock = nil
tests := []struct {
@@ -633,7 +633,7 @@ func Test_NotNil(t *testing.T) {
name: "nil custom message",
args: args{
tb: &assertions.TBMock{},
- msgs: []interface{}{"This should not be nil"},
+ msgs: []any{"This should not be nil"},
},
expectedErr: "This should not be nil",
},
@@ -641,7 +641,7 @@ func Test_NotNil(t *testing.T) {
name: "nil custom message with params",
args: args{
tb: &assertions.TBMock{},
- msgs: []interface{}{"This should not be nil (for slot %d)", 12},
+ msgs: []any{"This should not be nil (for slot %d)", 12},
},
expectedErr: "This should not be nil (for slot 12)",
},
@@ -693,7 +693,7 @@ func Test_LogsContainDoNotContain(t *testing.T) {
tb *assertions.TBMock
want string
flag bool
- msgs []interface{}
+ msgs []any
}
tests := []struct {
name string
@@ -726,7 +726,7 @@ func Test_LogsContainDoNotContain(t *testing.T) {
name: "should contain not found custom message",
args: args{
tb: &assertions.TBMock{},
- msgs: []interface{}{"Waited for logs"},
+ msgs: []any{"Waited for logs"},
want: "here goes some expected log string",
flag: true,
},
@@ -736,7 +736,7 @@ func Test_LogsContainDoNotContain(t *testing.T) {
name: "should contain not found custom message with params",
args: args{
tb: &assertions.TBMock{},
- msgs: []interface{}{"Waited for %d logs", 10},
+ msgs: []any{"Waited for %d logs", 10},
want: "here goes some expected log string",
flag: true,
},
@@ -765,7 +765,7 @@ func Test_LogsContainDoNotContain(t *testing.T) {
name: "should not contain but found custom message",
args: args{
tb: &assertions.TBMock{},
- msgs: []interface{}{"Dit not expect logs"},
+ msgs: []any{"Dit not expect logs"},
want: "here goes some unexpected log string",
},
updateLogs: func(log *logrus.Logger) {
@@ -777,7 +777,7 @@ func Test_LogsContainDoNotContain(t *testing.T) {
name: "should not contain but found custom message with params",
args: args{
tb: &assertions.TBMock{},
- msgs: []interface{}{"Dit not expect %d logs", 10},
+ msgs: []any{"Dit not expect %d logs", 10},
want: "here goes some unexpected log string",
},
updateLogs: func(log *logrus.Logger) {
@@ -824,9 +824,9 @@ func Test_LogsContainDoNotContain(t *testing.T) {
func TestAssert_NotEmpty(t *testing.T) {
type args struct {
tb *assertions.TBMock
- input interface{}
- actual interface{}
- msgs []interface{}
+ input any
+ actual any
+ msgs []any
}
tests := []struct {
name string
diff --git a/testing/endtoend/component_handler_test.go b/testing/endtoend/component_handler_test.go
index 9743268791..48a67947f4 100644
--- a/testing/endtoend/component_handler_test.go
+++ b/testing/endtoend/component_handler_test.go
@@ -256,7 +256,7 @@ func (c *componentHandler) required() []e2etypes.ComponentRunner {
return requiredComponents
}
-func (c *componentHandler) printPIDs(logger func(string, ...interface{})) {
+func (c *componentHandler) printPIDs(logger func(string, ...any)) {
msg := "\nPID of components. Attach a debugger... if you dare!\n\n"
msg += "This test PID: " + strconv.Itoa(os.Getpid()) + " (parent=" + strconv.Itoa(os.Getppid()) + ")\n"
diff --git a/testing/endtoend/components/beacon_node.go b/testing/endtoend/components/beacon_node.go
index 98f9131394..ad827b86be 100644
--- a/testing/endtoend/components/beacon_node.go
+++ b/testing/endtoend/components/beacon_node.go
@@ -72,7 +72,7 @@ func (s *BeaconNodeSet) Start(ctx context.Context) error {
// Once nodes are ready passed in handler function will be called.
return helpers.WaitOnNodes(ctx, nodes, func() {
if s.config.UseFixedPeerIDs {
- for i := 0; i < len(nodes); i++ {
+ for i := range nodes {
s.ids = append(s.ids, nodes[i].(*BeaconNode).peerID)
}
s.config.PeerIDs = s.ids
diff --git a/testing/endtoend/components/builder.go b/testing/endtoend/components/builder.go
index 467b27b8f0..d3e8aaa482 100644
--- a/testing/endtoend/components/builder.go
+++ b/testing/endtoend/components/builder.go
@@ -36,7 +36,7 @@ func NewBuilderSet() *BuilderSet {
func (s *BuilderSet) Start(ctx context.Context) error {
totalNodeCount := e2e.TestParams.BeaconNodeCount + e2e.TestParams.LighthouseBeaconNodeCount
nodes := make([]e2etypes.ComponentRunner, totalNodeCount)
- for i := 0; i < totalNodeCount; i++ {
+ for i := range totalNodeCount {
nodes[i] = NewBuilder(i)
}
s.builders = nodes
diff --git a/testing/endtoend/components/eth1/depositor.go b/testing/endtoend/components/eth1/depositor.go
index f7dda70ab6..73993f3655 100644
--- a/testing/endtoend/components/eth1/depositor.go
+++ b/testing/endtoend/components/eth1/depositor.go
@@ -198,7 +198,7 @@ func (d *Depositor) SendAndMineByBatch(ctx context.Context, offset, nvals, batch
}
numBatch := len(deposits) / batchSize
log.WithField("numDeposits", len(deposits)).WithField("batchSize", batchSize).WithField("numBatches", numBatch).WithField("balance", balance.String()).WithField("account", d.Key.Address.Hex()).Info("SendAndMineByBatch check")
- for i := 0; i < numBatch; i++ {
+ for i := range numBatch {
txo, err := d.txops(ctx)
if err != nil {
return err
diff --git a/testing/endtoend/components/eth1/miner.go b/testing/endtoend/components/eth1/miner.go
index 3f09a9e866..a8cb378e5e 100644
--- a/testing/endtoend/components/eth1/miner.go
+++ b/testing/endtoend/components/eth1/miner.go
@@ -191,7 +191,7 @@ func (m *Miner) Start(ctx context.Context) error {
// give the miner start a couple of tries, since the p2p networking check is flaky
var retryErr error
var minerLog *os.File
- for attempt := 0; attempt < 3; attempt++ {
+ for attempt := range 3 {
minerLog, retryErr = m.initAttempt(ctx, attempt)
if retryErr == nil {
log.Infof("Miner started after %d retries", attempt)
diff --git a/testing/endtoend/components/eth1/node.go b/testing/endtoend/components/eth1/node.go
index 50d6f73eb4..ef9d9b5bea 100644
--- a/testing/endtoend/components/eth1/node.go
+++ b/testing/endtoend/components/eth1/node.go
@@ -116,7 +116,7 @@ func (node *Node) Start(ctx context.Context) error {
// give the miner start a couple of tries, since the p2p networking check is flaky
var retryErr error
- for retries := 0; retries < 3; retries++ {
+ for retries := range 3 {
retryErr = nil
log.Infof("Starting eth1 node %d, attempt %d with flags: %s", node.index, retries, strings.Join(args[2:], " "))
runCmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Safe
diff --git a/testing/endtoend/components/eth1/node_set.go b/testing/endtoend/components/eth1/node_set.go
index ccd80636ce..0cdab45d24 100644
--- a/testing/endtoend/components/eth1/node_set.go
+++ b/testing/endtoend/components/eth1/node_set.go
@@ -37,7 +37,7 @@ func (s *NodeSet) Start(ctx context.Context) error {
// beacon node will connect to the already existing Eth1 miner.
totalNodeCount := e2e.TestParams.BeaconNodeCount + e2e.TestParams.LighthouseBeaconNodeCount - 1
nodes := make([]e2etypes.ComponentRunner, totalNodeCount)
- for i := 0; i < totalNodeCount; i++ {
+ for i := range totalNodeCount {
// We start indexing nodes from 1 because the miner has an implicit 0 index.
node := NewNode(i+1, s.enr)
nodes[i] = node
diff --git a/testing/endtoend/components/eth1/proxy.go b/testing/endtoend/components/eth1/proxy.go
index 1de8c1cda2..8ab3144a34 100644
--- a/testing/endtoend/components/eth1/proxy.go
+++ b/testing/endtoend/components/eth1/proxy.go
@@ -36,7 +36,7 @@ func NewProxySet() *ProxySet {
func (s *ProxySet) Start(ctx context.Context) error {
totalNodeCount := e2e.TestParams.BeaconNodeCount + e2e.TestParams.LighthouseBeaconNodeCount
nodes := make([]e2etypes.ComponentRunner, totalNodeCount)
- for i := 0; i < totalNodeCount; i++ {
+ for i := range totalNodeCount {
nodes[i] = NewProxy(i)
}
s.proxies = nodes
@@ -194,7 +194,7 @@ func (node *Proxy) Stop() error {
}
// AddRequestInterceptor adds in a json-rpc request interceptor.
-func (node *Proxy) AddRequestInterceptor(rpcMethodName string, responseGen func() interface{}, trigger func() bool) {
+func (node *Proxy) AddRequestInterceptor(rpcMethodName string, responseGen func() any, trigger func() bool) {
node.engineProxy.AddRequestInterceptor(rpcMethodName, responseGen, trigger)
}
diff --git a/testing/endtoend/components/eth1/transactions.go b/testing/endtoend/components/eth1/transactions.go
index e6b8f15679..0ff409dcb4 100644
--- a/testing/endtoend/components/eth1/transactions.go
+++ b/testing/endtoend/components/eth1/transactions.go
@@ -138,7 +138,7 @@ func SendTransaction(client *rpc.Client, key *ecdsa.PrivateKey, f *filler.Filler
}
g, _ := errgroup.WithContext(context.Background())
txs := make([]*types.Transaction, 10)
- for i := uint64(0); i < 10; i++ {
+ for i := range uint64(10) {
index := i
g.Go(func() error {
tx, err := RandomBlobTx(client, f, fundedAccount.Address, nonce+index, gasPrice, chainid, al)
@@ -182,7 +182,7 @@ func SendTransaction(client *rpc.Client, key *ecdsa.PrivateKey, f *filler.Filler
}
txs = make([]*types.Transaction, N)
- for i := uint64(0); i < N; i++ {
+ for i := range N {
index := i
g.Go(func() error {
tx, err := txfuzz.RandomValidTx(client, f, sender, nonce+index, gasPrice, chainid, al)
@@ -359,10 +359,7 @@ func encodeBlobs(data []byte) []kzg4844.Blob {
blobIndex++
fieldIndex = 0
}
- max := i + 31
- if max > len(data) {
- max = len(data)
- }
+ max := min(i+31, len(data))
copy(blobs[blobIndex][fieldIndex*32+1:], data[i:max])
}
return blobs
diff --git a/testing/endtoend/components/lighthouse_beacon.go b/testing/endtoend/components/lighthouse_beacon.go
index 3eb5c4a6e9..6e3be4dc93 100644
--- a/testing/endtoend/components/lighthouse_beacon.go
+++ b/testing/endtoend/components/lighthouse_beacon.go
@@ -261,7 +261,7 @@ func (node *LighthouseBeaconNode) createTestnetDir(ctx context.Context, index in
return "", err
}
bootPath := filepath.Join(testNetDir, "boot_enr.yaml")
- enrYaml := []byte(fmt.Sprintf("[%s]", node.enr))
+ enrYaml := fmt.Appendf(nil, "[%s]", node.enr)
if err := file.WriteFile(bootPath, enrYaml); err != nil {
return "", err
}
diff --git a/testing/endtoend/components/lighthouse_validator.go b/testing/endtoend/components/lighthouse_validator.go
index 7cbdcd530b..da9e7054ce 100644
--- a/testing/endtoend/components/lighthouse_validator.go
+++ b/testing/endtoend/components/lighthouse_validator.go
@@ -59,7 +59,7 @@ func (s *LighthouseValidatorNodeSet) Start(ctx context.Context) error {
// Create validator nodes.
nodes := make([]types.ComponentRunner, lighthouseBeaconNum)
- for i := 0; i < lighthouseBeaconNum; i++ {
+ for i := range lighthouseBeaconNum {
offsetIdx := i + prysmBeaconNum
nodes[i] = NewLighthouseValidatorNode(s.config, validatorsPerNode, i, validatorsPerNode*offsetIdx)
}
@@ -260,7 +260,7 @@ func (k *KeystoreGenerator) Start(_ context.Context) error {
}
validatorsPerNode := validatorNum / beaconNodeNum
- for i := 0; i < lighthouseBeaconNum; i++ {
+ for i := range lighthouseBeaconNum {
offsetIdx := i + prysmBeaconNum
_, err := setupKeystores(i, validatorsPerNode*offsetIdx, validatorsPerNode)
if err != nil {
diff --git a/testing/endtoend/components/validator.go b/testing/endtoend/components/validator.go
index 0fa5c6aa03..bf50868d37 100644
--- a/testing/endtoend/components/validator.go
+++ b/testing/endtoend/components/validator.go
@@ -62,7 +62,7 @@ func (s *ValidatorNodeSet) Start(ctx context.Context) error {
validatorsPerNode := validatorNum / beaconNodeNum
// Create validator nodes.
nodes := make([]e2etypes.ComponentRunner, prysmBeaconNodeNum)
- for i := 0; i < prysmBeaconNodeNum; i++ {
+ for i := range prysmBeaconNodeNum {
nodes[i] = NewValidatorNode(s.config, validatorsPerNode, i, validatorsPerNode*i)
}
s.nodes = nodes
diff --git a/testing/endtoend/endtoend_test.go b/testing/endtoend/endtoend_test.go
index 70c661ae59..bf08bbaa64 100644
--- a/testing/endtoend/endtoend_test.go
+++ b/testing/endtoend/endtoend_test.go
@@ -11,6 +11,7 @@ import (
"math/big"
"os"
"path"
+ "slices"
"strings"
"sync"
"testing"
@@ -322,7 +323,7 @@ func (r *testRunner) testCheckpointSync(ctx context.Context, g *errgroup.Group,
return err
}
- flags := append([]string{}, r.config.BeaconFlags...)
+ flags := slices.Clone(r.config.BeaconFlags)
flags = append(flags, fmt.Sprintf("--checkpoint-sync-url=%s", bnAPI))
flags = append(flags, fmt.Sprintf("--genesis-beacon-api-url=%s", bnAPI))
@@ -689,7 +690,7 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
// Set it for prysm beacon node.
component, err := r.comHandler.eth1Proxy.ComponentAtIndex(0)
require.NoError(r.t, err)
- component.(e2etypes.EngineProxy).AddRequestInterceptor(newPayloadMethod, func() interface{} {
+ component.(e2etypes.EngineProxy).AddRequestInterceptor(newPayloadMethod, func() any {
return &enginev1.PayloadStatus{
Status: enginev1.PayloadStatus_SYNCING,
LatestValidHash: make([]byte, 32),
@@ -700,7 +701,7 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
// Set it for lighthouse beacon node.
component, err = r.comHandler.eth1Proxy.ComponentAtIndex(2)
require.NoError(r.t, err)
- component.(e2etypes.EngineProxy).AddRequestInterceptor(newPayloadMethod, func() interface{} {
+ component.(e2etypes.EngineProxy).AddRequestInterceptor(newPayloadMethod, func() any {
return &enginev1.PayloadStatus{
Status: enginev1.PayloadStatus_SYNCING,
LatestValidHash: make([]byte, 32),
@@ -709,7 +710,7 @@ func (r *testRunner) multiScenarioMulticlient(ec *e2etypes.EvaluationContext, ep
return true
})
- component.(e2etypes.EngineProxy).AddRequestInterceptor(forkChoiceUpdatedMethod, func() interface{} {
+ component.(e2etypes.EngineProxy).AddRequestInterceptor(forkChoiceUpdatedMethod, func() any {
return &ForkchoiceUpdatedResponse{
Status: &enginev1.PayloadStatus{
Status: enginev1.PayloadStatus_SYNCING,
@@ -814,7 +815,7 @@ func (r *testRunner) multiScenario(ec *e2etypes.EvaluationContext, epoch uint64,
case optimisticStartEpoch:
component, err := r.comHandler.eth1Proxy.ComponentAtIndex(0)
require.NoError(r.t, err)
- component.(e2etypes.EngineProxy).AddRequestInterceptor(newPayloadMethod, func() interface{} {
+ component.(e2etypes.EngineProxy).AddRequestInterceptor(newPayloadMethod, func() any {
return &enginev1.PayloadStatus{
Status: enginev1.PayloadStatus_SYNCING,
LatestValidHash: make([]byte, 32),
diff --git a/testing/endtoend/evaluators/beaconapi/requests.go b/testing/endtoend/evaluators/beaconapi/requests.go
index 7abdaa4249..854dd65019 100644
--- a/testing/endtoend/evaluators/beaconapi/requests.go
+++ b/testing/endtoend/evaluators/beaconapi/requests.go
@@ -149,7 +149,7 @@ var getRequests = map[string]endpoint{
withSanityCheckOnly()),
"/config/fork_schedule": newMetadata[structs.GetForkScheduleResponse](
v1PathTemplate,
- withCustomEval(func(p interface{}, lh interface{}) error {
+ withCustomEval(func(p any, lh any) error {
pResp, ok := p.(*structs.GetForkScheduleResponse)
if !ok {
return fmt.Errorf(msgWrongJSON, &structs.GetForkScheduleResponse{}, p)
@@ -199,7 +199,7 @@ var getRequests = map[string]endpoint{
withSanityCheckOnly()),
"/node/version": newMetadata[structs.GetVersionResponse](
v1PathTemplate,
- withCustomEval(func(p interface{}, _ interface{}) error {
+ withCustomEval(func(p any, _ any) error {
pResp, ok := p.(*structs.GetVersionResponse)
if !ok {
return fmt.Errorf(msgWrongJSON, &structs.GetVersionResponse{}, p)
@@ -218,7 +218,7 @@ var getRequests = map[string]endpoint{
withParams(func(currentEpoch primitives.Epoch) []string {
return []string{fmt.Sprintf("%v", currentEpoch)}
}),
- withCustomEval(func(p interface{}, lh interface{}) error {
+ withCustomEval(func(p any, lh any) error {
pResp, ok := p.(*structs.GetProposerDutiesResponse)
if !ok {
return fmt.Errorf(msgWrongJSON, &structs.GetProposerDutiesResponse{}, p)
@@ -259,7 +259,7 @@ var (
withParams(func(_ primitives.Epoch) []string {
return []string{"head"}
}),
- withPOSTObj(func() interface{} {
+ withPOSTObj(func() any {
return struct {
Ids []string `json:"ids"`
Statuses []string `json:"statuses"`
diff --git a/testing/endtoend/evaluators/beaconapi/types.go b/testing/endtoend/evaluators/beaconapi/types.go
index 8253f79eae..d9118de700 100644
--- a/testing/endtoend/evaluators/beaconapi/types.go
+++ b/testing/endtoend/evaluators/beaconapi/types.go
@@ -14,16 +14,16 @@ type endpoint interface {
setSszResp(resp []byte) // sets the Prysm SSZ response
getStart() primitives.Epoch
setStart(start primitives.Epoch)
- getPOSTObj() interface{}
- setPOSTObj(obj interface{})
- getPResp() interface{} // retrieves the Prysm JSON response
- getLHResp() interface{} // retrieves the Lighthouse JSON response
+ getPOSTObj() any
+ setPOSTObj(obj any)
+ getPResp() any // retrieves the Prysm JSON response
+ getLHResp() any // retrieves the Lighthouse JSON response
getParams(currentEpoch primitives.Epoch) []string
setParams(f func(currentEpoch primitives.Epoch) []string)
getQueryParams(currentEpoch primitives.Epoch) []string
setQueryParams(f func(currentEpoch primitives.Epoch) []string)
- getCustomEval() func(interface{}, interface{}) error
- setCustomEval(f func(interface{}, interface{}) error)
+ getCustomEval() func(any, any) error
+ setCustomEval(f func(any, any) error)
}
type apiEndpoint[Resp any] struct {
@@ -31,13 +31,13 @@ type apiEndpoint[Resp any] struct {
sanity bool
ssz bool
start primitives.Epoch
- postObj interface{}
+ postObj any
pResp *Resp // Prysm JSON response
lhResp *Resp // Lighthouse JSON response
sszResp []byte // Prysm SSZ response
params func(currentEpoch primitives.Epoch) []string
queryParams func(currentEpoch primitives.Epoch) []string
- customEval func(interface{}, interface{}) error
+ customEval func(any, any) error
}
func (e *apiEndpoint[Resp]) getBasePath() string {
@@ -76,19 +76,19 @@ func (e *apiEndpoint[Resp]) setStart(start primitives.Epoch) {
e.start = start
}
-func (e *apiEndpoint[Resp]) getPOSTObj() interface{} {
+func (e *apiEndpoint[Resp]) getPOSTObj() any {
return e.postObj
}
-func (e *apiEndpoint[Resp]) setPOSTObj(obj interface{}) {
+func (e *apiEndpoint[Resp]) setPOSTObj(obj any) {
e.postObj = obj
}
-func (e *apiEndpoint[Resp]) getPResp() interface{} {
+func (e *apiEndpoint[Resp]) getPResp() any {
return e.pResp
}
-func (e *apiEndpoint[Resp]) getLHResp() interface{} {
+func (e *apiEndpoint[Resp]) getLHResp() any {
return e.lhResp
}
@@ -114,11 +114,11 @@ func (e *apiEndpoint[Resp]) setQueryParams(f func(currentEpoch primitives.Epoch)
e.queryParams = f
}
-func (e *apiEndpoint[Resp]) getCustomEval() func(interface{}, interface{}) error {
+func (e *apiEndpoint[Resp]) getCustomEval() func(any, any) error {
return e.customEval
}
-func (e *apiEndpoint[Resp]) setCustomEval(f func(interface{}, interface{}) error) {
+func (e *apiEndpoint[Resp]) setCustomEval(f func(any, any) error) {
e.customEval = f
}
@@ -158,7 +158,7 @@ func withStart(start primitives.Epoch) endpointOpt {
}
// We perform a POST instead of GET, sending an object.
-func withPOSTObj(obj interface{}) endpointOpt {
+func withPOSTObj(obj any) endpointOpt {
return func(e endpoint) {
e.setPOSTObj(obj)
}
@@ -179,7 +179,7 @@ func withQueryParams(f func(currentEpoch primitives.Epoch) []string) endpointOpt
}
// We perform custom evaluation on responses.
-func withCustomEval(f func(interface{}, interface{}) error) endpointOpt {
+func withCustomEval(f func(any, any) error) endpointOpt {
return func(e endpoint) {
e.setCustomEval(f)
}
diff --git a/testing/endtoend/evaluators/beaconapi/util.go b/testing/endtoend/evaluators/beaconapi/util.go
index 43cb842537..93001b23da 100644
--- a/testing/endtoend/evaluators/beaconapi/util.go
+++ b/testing/endtoend/evaluators/beaconapi/util.go
@@ -25,7 +25,7 @@ const (
msgSSZUnmarshalFailed = "failed to unmarshal SSZ"
)
-func doJSONGETRequest(template, requestPath string, beaconNodeIdx int, resp interface{}, bnType ...string) error {
+func doJSONGETRequest(template, requestPath string, beaconNodeIdx int, resp any, bnType ...string) error {
if len(bnType) == 0 {
bnType = []string{"Prysm"}
}
@@ -46,7 +46,7 @@ func doJSONGETRequest(template, requestPath string, beaconNodeIdx int, resp inte
return errors.Wrap(err, "request failed")
}
- var body interface{}
+ var body any
if httpResp.StatusCode != http.StatusOK {
if httpResp.Header.Get("Content-Type") == api.JsonMediaType {
if err = json.NewDecoder(httpResp.Body).Decode(&body); err != nil {
@@ -95,7 +95,7 @@ func doSSZGETRequest(template, requestPath string, beaconNodeIdx int, bnType ...
return nil, errors.Wrap(err, "request failed")
}
if resp.StatusCode != http.StatusOK {
- var body interface{}
+ var body any
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return nil, errors.Wrap(err, "failed to decode response body")
}
@@ -110,7 +110,7 @@ func doSSZGETRequest(template, requestPath string, beaconNodeIdx int, bnType ...
return body, nil
}
-func doJSONPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj, resp interface{}, bnType ...string) error {
+func doJSONPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj, resp any, bnType ...string) error {
if len(bnType) == 0 {
bnType = []string{"Prysm"}
}
@@ -139,7 +139,7 @@ func doJSONPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj,
return errors.Wrap(err, "request failed")
}
- var body interface{}
+ var body any
if httpResp.StatusCode != http.StatusOK {
if httpResp.Header.Get("Content-Type") == api.JsonMediaType {
if err = json.NewDecoder(httpResp.Body).Decode(&body); err != nil {
@@ -161,7 +161,7 @@ func doJSONPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj,
return nil
}
-func doSSZPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj interface{}, bnType ...string) ([]byte, error) {
+func doSSZPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj any, bnType ...string) ([]byte, error) {
if len(bnType) == 0 {
bnType = []string{"Prysm"}
}
@@ -193,7 +193,7 @@ func doSSZPOSTRequest(template, requestPath string, beaconNodeIdx int, postObj i
return nil, errors.Wrap(err, "request failed")
}
if resp.StatusCode != http.StatusOK {
- var body interface{}
+ var body any
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
return nil, errors.Wrap(err, "failed to decode response body")
}
diff --git a/testing/endtoend/evaluators/beaconapi/verify.go b/testing/endtoend/evaluators/beaconapi/verify.go
index a47e599380..2cf97b51c2 100644
--- a/testing/endtoend/evaluators/beaconapi/verify.go
+++ b/testing/endtoend/evaluators/beaconapi/verify.go
@@ -244,7 +244,7 @@ func postEvaluation(nodeIdx int, requests map[string]endpoint, epoch primitives.
return nil
}
-func compareGETJSON(nodeIdx int, base, path string, pResp, lhResp interface{}, customEval func(interface{}, interface{}) error) error {
+func compareGETJSON(nodeIdx int, base, path string, pResp, lhResp any, customEval func(any, any) error) error {
if err := doJSONGETRequest(base, path, nodeIdx, pResp); err != nil {
return errors.Wrapf(err, "issue during Prysm JSON GET request for path %s", path)
}
@@ -264,7 +264,7 @@ func compareGETJSON(nodeIdx int, base, path string, pResp, lhResp interface{}, c
}
}
-func comparePOSTJSON(nodeIdx int, base, path string, postObj, pResp, lhResp interface{}, customEval func(interface{}, interface{}) error) error {
+func comparePOSTJSON(nodeIdx int, base, path string, postObj, pResp, lhResp any, customEval func(any, any) error) error {
if err := doJSONPOSTRequest(base, path, nodeIdx, postObj, pResp); err != nil {
return errors.Wrapf(err, "issue during Prysm JSON POST request for path %s", path)
}
@@ -299,7 +299,7 @@ func compareGETSSZ(nodeIdx int, base, path string) ([]byte, error) {
return pResp, nil
}
-func comparePOSTSSZ(nodeIdx int, base, path string, postObj interface{}) ([]byte, error) {
+func comparePOSTSSZ(nodeIdx int, base, path string, postObj any) ([]byte, error) {
pResp, err := doSSZPOSTRequest(base, path, nodeIdx, postObj)
if err != nil {
return nil, errors.Wrapf(err, "issue during Prysm SSZ POST request for path %s", path)
@@ -314,7 +314,7 @@ func comparePOSTSSZ(nodeIdx int, base, path string, postObj interface{}) ([]byte
return pResp, nil
}
-func compareJSON(pResp, lhResp interface{}) error {
+func compareJSON(pResp, lhResp any) error {
if !reflect.DeepEqual(pResp, lhResp) {
p, err := json.Marshal(pResp)
if err != nil {
diff --git a/testing/endtoend/evaluators/data.go b/testing/endtoend/evaluators/data.go
index 080c9d8ad2..370ebaea42 100644
--- a/testing/endtoend/evaluators/data.go
+++ b/testing/endtoend/evaluators/data.go
@@ -26,7 +26,7 @@ func checkColdStateCheckpoint(_ *e2etypes.EvaluationContext, conns ...*grpc.Clie
ctx := context.Background()
client := eth.NewBeaconChainClient(conns[0])
- for i := primitives.Epoch(0); i < epochToCheck; i++ {
+ for i := range primitives.Epoch(epochToCheck) {
res, err := client.ListValidatorAssignments(ctx, ð.ListValidatorAssignmentsRequest{
QueryFilter: ð.ListValidatorAssignmentsRequest_Epoch{Epoch: i},
})
diff --git a/testing/endtoend/evaluators/metrics.go b/testing/endtoend/evaluators/metrics.go
index 27e022892d..29d057b9bf 100644
--- a/testing/endtoend/evaluators/metrics.go
+++ b/testing/endtoend/evaluators/metrics.go
@@ -90,7 +90,7 @@ func metricsTest(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error {
currentSlot := slots.CurrentSlot(genesis.Time())
currentEpoch := slots.ToEpoch(currentSlot)
forkDigest := params.ForkDigest(currentEpoch)
- for i := 0; i < len(conns); i++ {
+ for i := range conns {
response, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", e2e.TestParams.Ports.PrysmBeaconNodeMetricsPort+i))
if err != nil {
// Continue if the connection fails, regular flake.
diff --git a/testing/endtoend/evaluators/node.go b/testing/endtoend/evaluators/node.go
index fb3cb23c54..1d3fe98bab 100644
--- a/testing/endtoend/evaluators/node.go
+++ b/testing/endtoend/evaluators/node.go
@@ -55,7 +55,7 @@ var AllNodesHaveSameHead = e2etypes.Evaluator{
func healthzCheck(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error {
count := len(conns)
- for i := 0; i < count; i++ {
+ for i := range count {
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/healthz", e2e.TestParams.Ports.PrysmBeaconNodeMetricsPort+i))
if err != nil {
// Continue if the connection fails, regular flake.
@@ -74,7 +74,7 @@ func healthzCheck(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) erro
time.Sleep(connTimeDelay)
}
- for i := 0; i < count; i++ {
+ for i := range count {
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/healthz", e2e.TestParams.Ports.ValidatorMetricsPort+i))
if err != nil {
// Continue if the connection fails, regular flake.
@@ -157,7 +157,7 @@ func allNodesHaveSameHead(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientCo
return err
}
- for i := 0; i < len(conns); i++ {
+ for i := range conns {
if headEpochs[0] != headEpochs[i] {
return fmt.Errorf(
"received conflicting head epochs on node %d, expected %d, received %d",
diff --git a/testing/endtoend/evaluators/peers.go b/testing/endtoend/evaluators/peers.go
index 813d3705c5..de963e1c88 100644
--- a/testing/endtoend/evaluators/peers.go
+++ b/testing/endtoend/evaluators/peers.go
@@ -55,7 +55,7 @@ func peersTest(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error {
return baseErr
}
-func wrapError(err error, format string, args ...interface{}) error {
+func wrapError(err error, format string, args ...any) error {
if err == nil {
err = errors.New("")
}
diff --git a/testing/endtoend/helpers/helpers.go b/testing/endtoend/helpers/helpers.go
index 54911dab3c..2c075fc3ea 100644
--- a/testing/endtoend/helpers/helpers.go
+++ b/testing/endtoend/helpers/helpers.go
@@ -303,7 +303,7 @@ func NewLocalConnection(ctx context.Context, port int) (*grpc.ClientConn, error)
// NewLocalConnections returns number of GRPC connections, along with function to close all of them.
func NewLocalConnections(ctx context.Context, numConns int) ([]*grpc.ClientConn, func(), error) {
conns := make([]*grpc.ClientConn, numConns)
- for i := 0; i < len(conns); i++ {
+ for i := range conns {
conn, err := NewLocalConnection(ctx, e2e.TestParams.Ports.PrysmBeaconNodeRPCPort+i)
if err != nil {
return nil, nil, err
@@ -322,7 +322,7 @@ func NewLocalConnections(ctx context.Context, numConns int) ([]*grpc.ClientConn,
// BeaconAPIHostnames constructs a hostname:port string for the
func BeaconAPIHostnames(numConns int) []string {
hostnames := make([]string, 0)
- for i := 0; i < numConns; i++ {
+ for i := range numConns {
port := e2e.TestParams.Ports.PrysmBeaconNodeHTTPPort + i
hostnames = append(hostnames, net.JoinHostPort("127.0.0.1", strconv.Itoa(port)))
}
diff --git a/testing/endtoend/types/types.go b/testing/endtoend/types/types.go
index 887ead02be..d68494aeda 100644
--- a/testing/endtoend/types/types.go
+++ b/testing/endtoend/types/types.go
@@ -184,7 +184,7 @@ type MultipleComponentRunners interface {
type EngineProxy interface {
ComponentRunner
// AddRequestInterceptor adds in a json-rpc request interceptor.
- AddRequestInterceptor(rpcMethodName string, responseGen func() interface{}, trigger func() bool)
+ AddRequestInterceptor(rpcMethodName string, responseGen func() any, trigger func() bool)
// RemoveRequestInterceptor removes the request interceptor for the provided method.
RemoveRequestInterceptor(rpcMethodName string)
// ReleaseBackedUpRequests releases backed up http requests.
diff --git a/testing/middleware/builder/builder.go b/testing/middleware/builder/builder.go
index 6833a6d812..2d5c43ef18 100644
--- a/testing/middleware/builder/builder.go
+++ b/testing/middleware/builder/builder.go
@@ -67,18 +67,18 @@ var (
)
type jsonRPCObject struct {
- Jsonrpc string `json:"jsonrpc"`
- Method string `json:"method"`
- Params []interface{} `json:"params"`
- ID uint64 `json:"id"`
- Result interface{} `json:"result"`
+ Jsonrpc string `json:"jsonrpc"`
+ Method string `json:"method"`
+ Params []any `json:"params"`
+ ID uint64 `json:"id"`
+ Result any `json:"result"`
}
type ForkchoiceUpdatedResponse struct {
- Jsonrpc string `json:"jsonrpc"`
- Method string `json:"method"`
- Params []interface{} `json:"params"`
- ID uint64 `json:"id"`
+ Jsonrpc string `json:"jsonrpc"`
+ Method string `json:"method"`
+ Params []any `json:"params"`
+ ID uint64 `json:"id"`
Result struct {
Status *v1.PayloadStatus `json:"payloadStatus"`
PayloadId *v1.PayloadIDBytes `json:"payloadId"`
@@ -728,7 +728,7 @@ var errInvalidTypeConversion = errors.New("unable to translate between api and f
// This involves serializing the execution payload value so that the abstract payload envelope can be used.
func ExecutionPayloadResponseFromData(v int, ed interfaces.ExecutionData, bundle *v1.BlobsBundle) (*builderAPI.ExecutionPayloadResponse, error) {
pb := ed.Proto()
- var data interface{}
+ var data any
var err error
ver := version.String(v)
switch pbStruct := pb.(type) {
diff --git a/testing/middleware/engine-api-proxy/proxy.go b/testing/middleware/engine-api-proxy/proxy.go
index c01b9033f1..56ebfef580 100644
--- a/testing/middleware/engine-api-proxy/proxy.go
+++ b/testing/middleware/engine-api-proxy/proxy.go
@@ -26,15 +26,15 @@ var (
)
type jsonRPCObject struct {
- Jsonrpc string `json:"jsonrpc"`
- Method string `json:"method"`
- Params []interface{} `json:"params"`
- ID uint64 `json:"id"`
- Result interface{} `json:"result"`
+ Jsonrpc string `json:"jsonrpc"`
+ Method string `json:"method"`
+ Params []any `json:"params"`
+ ID uint64 `json:"id"`
+ Result any `json:"result"`
}
type interceptorConfig struct {
- responseGen func() interface{}
+ responseGen func() any
trigger func() bool
}
@@ -128,7 +128,7 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// AddRequestInterceptor for a desired json-rpc method by specifying a custom response
// and a function that checks if the interceptor should be triggered.
-func (p *Proxy) AddRequestInterceptor(rpcMethodName string, response func() interface{}, trigger func() bool) {
+func (p *Proxy) AddRequestInterceptor(rpcMethodName string, response func() any, trigger func() bool) {
p.lock.Lock()
defer p.lock.Unlock()
p.cfg.logger.Infof("Adding in interceptor for method %s", rpcMethodName)
diff --git a/testing/middleware/engine-api-proxy/proxy_test.go b/testing/middleware/engine-api-proxy/proxy_test.go
index 38bc7fce69..1c20ac7a39 100644
--- a/testing/middleware/engine-api-proxy/proxy_test.go
+++ b/testing/middleware/engine-api-proxy/proxy_test.go
@@ -113,7 +113,7 @@ func TestProxy_CustomInterceptors(t *testing.T) {
// RPC method to intercept.
proxy.AddRequestInterceptor(
method,
- func() interface{} {
+ func() any {
return &syncingResponse{Syncing: false}
}, // Custom response.
func() bool {
@@ -162,7 +162,7 @@ func TestProxy_CustomInterceptors(t *testing.T) {
method := "engine_newPayloadV1"
// RPC method to intercept.
- wantInterceptedResponse := func() interface{} {
+ wantInterceptedResponse := func() any {
return &engineResponse{BlockHash: common.BytesToHash([]byte("bar"))}
}
conditional := false
@@ -229,7 +229,7 @@ func TestProxy_CustomInterceptors(t *testing.T) {
method := "engine_newPayloadV1"
// RPC method to intercept.
- wantInterceptedResponse := func() interface{} {
+ wantInterceptedResponse := func() any {
return &engineResponse{BlockHash: common.BytesToHash([]byte("bar"))}
}
proxy.AddRequestInterceptor(
@@ -295,13 +295,13 @@ func Test_isEngineAPICall(t *testing.T) {
}
}
-func destinationServerSetup(t *testing.T, response interface{}) *httptest.Server {
+func destinationServerSetup(t *testing.T, response any) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
defer func() {
require.NoError(t, r.Body.Close())
}()
- resp := map[string]interface{}{
+ resp := map[string]any{
"jsonrpc": "2.0",
"id": 1,
"result": response,
diff --git a/testing/require/requires.go b/testing/require/requires.go
index 98cb8d6702..a4f5caa2d3 100644
--- a/testing/require/requires.go
+++ b/testing/require/requires.go
@@ -6,12 +6,12 @@ import (
)
// Equal compares values using comparison operator.
-func Equal(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func Equal(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.Equal(tb.Fatalf, expected, actual, msg...)
}
// NotEqual compares values using comparison operator.
-func NotEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func NotEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.NotEqual(tb.Fatalf, expected, actual, msg...)
}
@@ -19,7 +19,7 @@ func NotEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, ms
// NOTE: this function does not work for checking arrays/slices or maps of protobuf messages.
// For arrays/slices, please use DeepSSZEqual.
// For maps, please iterate through and compare the individual keys and values.
-func DeepEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepEqual(tb.Fatalf, expected, actual, msg...)
}
@@ -27,63 +27,63 @@ func DeepEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, m
// NOTE: this function does not work for checking arrays/slices or maps of protobuf messages.
// For arrays/slices, please use DeepNotSSZEqual.
// For maps, please iterate through and compare the individual keys and values.
-func DeepNotEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepNotEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepNotEqual(tb.Fatalf, expected, actual, msg...)
}
// DeepSSZEqual compares values using DeepEqual.
-func DeepSSZEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepSSZEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepSSZEqual(tb.Fatalf, expected, actual, msg...)
}
// DeepNotSSZEqual compares values using DeepEqual.
-func DeepNotSSZEqual(tb assertions.AssertionTestingTB, expected, actual interface{}, msg ...interface{}) {
+func DeepNotSSZEqual(tb assertions.AssertionTestingTB, expected, actual any, msg ...any) {
assertions.DeepNotSSZEqual(tb.Fatalf, expected, actual, msg...)
}
// NoError asserts that error is nil.
-func NoError(tb assertions.AssertionTestingTB, err error, msg ...interface{}) {
+func NoError(tb assertions.AssertionTestingTB, err error, msg ...any) {
assertions.NoError(tb.Fatalf, err, msg...)
}
// ErrorContains asserts that actual error contains wanted message.
-func ErrorContains(tb assertions.AssertionTestingTB, want string, err error, msg ...interface{}) {
+func ErrorContains(tb assertions.AssertionTestingTB, want string, err error, msg ...any) {
assertions.ErrorContains(tb.Fatalf, want, err, msg...)
}
// IsNil asserts that the observed value is nil.
-func IsNil(tb assertions.AssertionTestingTB, got interface{}, msg ...interface{}) {
+func IsNil(tb assertions.AssertionTestingTB, got any, msg ...any) {
assertions.IsNil(tb.Fatalf, got, msg...)
}
// NotNil asserts that passed value is not nil.
-func NotNil(tb assertions.AssertionTestingTB, obj interface{}, msg ...interface{}) {
+func NotNil(tb assertions.AssertionTestingTB, obj any, msg ...any) {
assertions.NotNil(tb.Fatalf, obj, msg...)
}
// LogsContain checks that the desired string is a subset of the current log output.
-func LogsContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...interface{}) {
+func LogsContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...any) {
assertions.LogsContain(tb.Fatalf, hook, want, true, msg...)
}
// LogsDoNotContain is the inverse check of LogsContain.
-func LogsDoNotContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...interface{}) {
+func LogsDoNotContain(tb assertions.AssertionTestingTB, hook *test.Hook, want string, msg ...any) {
assertions.LogsContain(tb.Fatalf, hook, want, false, msg...)
}
// NotEmpty checks that the object fields are not empty. This method also checks all of the
// pointer fields to ensure none of those fields are empty.
-func NotEmpty(tb assertions.AssertionTestingTB, obj interface{}, msg ...interface{}) {
+func NotEmpty(tb assertions.AssertionTestingTB, obj any, msg ...any) {
assertions.NotEmpty(tb.Fatalf, obj, msg...)
}
// ErrorIs uses Errors.Is to recursively unwrap err looking for target in the chain.
// If any error in the chain matches target, the assertion will pass.
-func ErrorIs(tb assertions.AssertionTestingTB, err, target error, msg ...interface{}) {
+func ErrorIs(tb assertions.AssertionTestingTB, err, target error, msg ...any) {
assertions.ErrorIs(tb.Fatalf, err, target, msg)
}
// StringContains asserts that actual string contains expected message.
-func StringContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...interface{}) {
+func StringContains(tb assertions.AssertionTestingTB, expected, actual string, msg ...any) {
assertions.StringContains(tb.Fatalf, expected, actual, true, msg)
}
diff --git a/testing/slasher/simulator/attestation_generator.go b/testing/slasher/simulator/attestation_generator.go
index aa2a89f0a6..05ceadc3b3 100644
--- a/testing/slasher/simulator/attestation_generator.go
+++ b/testing/slasher/simulator/attestation_generator.go
@@ -55,10 +55,7 @@ func (s *Simulator) generateAttestationsForSlot(ctx context.Context, ver int, sl
valsPerAttestation := uint64(math.Floor(s.srvConfig.Params.AggregationPercent * float64(valsPerCommittee)))
for i := startIdx; i < endIdx; i += valsPerAttestation {
- attEndIdx := i + valsPerAttestation
- if attEndIdx >= endIdx {
- attEndIdx = endIdx
- }
+ attEndIdx := min(i+valsPerAttestation, endIdx)
indices := make([]uint64, 0, valsPerAttestation)
for idx := i; idx < attEndIdx; idx++ {
indices = append(indices, idx)
diff --git a/testing/spectest/shared/altair/ssz_static/ssz_static.go b/testing/spectest/shared/altair/ssz_static/ssz_static.go
index 0924bda4dc..4a135fcddb 100644
--- a/testing/spectest/shared/altair/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/altair/ssz_static/ssz_static.go
@@ -17,10 +17,10 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "altair", unmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
switch object.(type) {
case *ethpb.BeaconStateAltair:
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoAltair(s.(*ethpb.BeaconStateAltair))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.Background())
@@ -30,8 +30,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// unmarshalledSSZ unmarshalls serialized input.
-func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
case "Attestation":
obj = ðpb.Attestation{}
diff --git a/testing/spectest/shared/bellatrix/ssz_static/ssz_static.go b/testing/spectest/shared/bellatrix/ssz_static/ssz_static.go
index 718670c317..dac59f8450 100644
--- a/testing/spectest/shared/bellatrix/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/bellatrix/ssz_static/ssz_static.go
@@ -18,10 +18,10 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "bellatrix", unmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
switch object.(type) {
case *ethpb.BeaconStateBellatrix:
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoBellatrix(s.(*ethpb.BeaconStateBellatrix))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.Background())
@@ -31,8 +31,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// unmarshalledSSZ unmarshalls serialized input.
-func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
case "ExecutionPayload":
obj = &enginev1.ExecutionPayload{}
diff --git a/testing/spectest/shared/capella/ssz_static/ssz_static.go b/testing/spectest/shared/capella/ssz_static/ssz_static.go
index 1976e9b3e6..28ef7fc1aa 100644
--- a/testing/spectest/shared/capella/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/capella/ssz_static/ssz_static.go
@@ -18,10 +18,10 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "capella", unmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
switch object.(type) {
case *ethpb.BeaconStateCapella:
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoCapella(s.(*ethpb.BeaconStateCapella))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.Background())
@@ -31,8 +31,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// unmarshalledSSZ unmarshalls serialized input.
-func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
case "ExecutionPayload":
obj = &enginev1.ExecutionPayloadCapella{}
diff --git a/testing/spectest/shared/common/forkchoice/runner.go b/testing/spectest/shared/common/forkchoice/runner.go
index 92908590f4..4cb9561f1d 100644
--- a/testing/spectest/shared/common/forkchoice/runner.go
+++ b/testing/spectest/shared/common/forkchoice/runner.go
@@ -352,7 +352,7 @@ func runDataColumnStep(t *testing.T,
} else {
numCells := len(kzgs)
column := make([][]byte, numCells)
- for cellIndex := 0; cellIndex < numCells; cellIndex++ {
+ for cellIndex := range numCells {
cell := make([]byte, 2048)
cellStart := cellIndex * 2048
cellEnd := cellStart + 2048
diff --git a/testing/spectest/shared/common/ssz_static/ssz_static.go b/testing/spectest/shared/common/ssz_static/ssz_static.go
index 459722c0e0..74954a3fb8 100644
--- a/testing/spectest/shared/common/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/common/ssz_static/ssz_static.go
@@ -56,7 +56,7 @@ func RunSSZStaticTests(t *testing.T, config, forkOrPhase string, unmarshaller Un
// All types support fastssz generated code, but may also include a custom HTR method.
var htrs []HTR
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
sszObj, ok := s.(fssz.HashRoot)
if !ok {
return [32]byte{}, errors.New("could not get hash root, not compatible object")
diff --git a/testing/spectest/shared/common/ssz_static/ssz_static_example_test.go b/testing/spectest/shared/common/ssz_static/ssz_static_example_test.go
index 7fa24b2db6..4ae469f961 100644
--- a/testing/spectest/shared/common/ssz_static/ssz_static_example_test.go
+++ b/testing/spectest/shared/common/ssz_static/ssz_static_example_test.go
@@ -16,8 +16,8 @@ import (
func ExampleRunSSZStaticTests() {
// Define an unmarshaller to select the correct go type based on the string
// name provided in spectests and then populate it with the serialized bytes.
- unmarshaller := func(t *testing.T, serializedBytes []byte, objectName string) (interface{}, error) {
- var obj interface{}
+ unmarshaller := func(t *testing.T, serializedBytes []byte, objectName string) (any, error) {
+ var obj any
switch objectName {
case "Attestation":
obj = ðpb.Attestation{}
@@ -45,10 +45,10 @@ func ExampleRunSSZStaticTests() {
// This argument may be nil if your test does not require custom HTR methods.
// Most commonly, this is used when a handwritten HTR method with specialized caching
// is used and you want to ensure it passes spectests.
- customHTR := func(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+ customHTR := func(t *testing.T, htrs []common.HTR, object any) []common.HTR {
switch object.(type) {
case *ethpb.BeaconState:
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoPhase0(s.(*ethpb.BeaconState))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.TODO())
diff --git a/testing/spectest/shared/common/ssz_static/types.go b/testing/spectest/shared/common/ssz_static/types.go
index 7d509a36f5..ca70c9fac3 100644
--- a/testing/spectest/shared/common/ssz_static/types.go
+++ b/testing/spectest/shared/common/ssz_static/types.go
@@ -5,7 +5,7 @@ import (
)
// HTR is the HashTreeRoot function signature.
-type HTR func(interface{}) ([32]byte, error)
+type HTR func(any) ([32]byte, error)
// SSZRoots is the format used to read spectest test data.
type SSZRoots struct {
@@ -15,8 +15,8 @@ type SSZRoots struct {
// Unmarshaller determines the correct type per ObjectName and then hydrates the object from the
// serializedBytes. This method may call t.Skip if the type is not supported.
-type Unmarshaller func(t *testing.T, serializedBytes []byte, objectName string) (interface{}, error)
+type Unmarshaller func(t *testing.T, serializedBytes []byte, objectName string) (any, error)
// CustomHTRAdder adds any custom HTR methods for the given object. This method should return a HTR
// slice with the custom HTR method applied.
-type CustomHTRAdder func(t *testing.T, htrs []HTR, object interface{}) []HTR
+type CustomHTRAdder func(t *testing.T, htrs []HTR, object any) []HTR
diff --git a/testing/spectest/shared/deneb/ssz_static/ssz_static.go b/testing/spectest/shared/deneb/ssz_static/ssz_static.go
index f0742bde84..347fc69f37 100644
--- a/testing/spectest/shared/deneb/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/deneb/ssz_static/ssz_static.go
@@ -18,13 +18,13 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "deneb", UnmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
_, ok := object.(*ethpb.BeaconStateDeneb)
if !ok {
return htrs
}
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoDeneb(s.(*ethpb.BeaconStateDeneb))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.Background())
@@ -33,8 +33,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// UnmarshalledSSZ unmarshalls serialized input.
-func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
case "ExecutionPayload":
obj = &enginev1.ExecutionPayloadDeneb{}
diff --git a/testing/spectest/shared/electra/ssz_static/ssz_static.go b/testing/spectest/shared/electra/ssz_static/ssz_static.go
index a0d18e75a5..6fc77f552f 100644
--- a/testing/spectest/shared/electra/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/electra/ssz_static/ssz_static.go
@@ -18,13 +18,13 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "electra", UnmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
_, ok := object.(*ethpb.BeaconStateElectra)
if !ok {
return htrs
}
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoElectra(s.(*ethpb.BeaconStateElectra))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.Background())
@@ -33,8 +33,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// UnmarshalledSSZ unmarshalls serialized input.
-func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
case "ExecutionPayload":
obj = &enginev1.ExecutionPayloadDeneb{}
diff --git a/testing/spectest/shared/fulu/ssz_static/ssz_static.go b/testing/spectest/shared/fulu/ssz_static/ssz_static.go
index 5c65791c30..b13ce5686b 100644
--- a/testing/spectest/shared/fulu/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/fulu/ssz_static/ssz_static.go
@@ -18,13 +18,13 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "fulu", UnmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
_, ok := object.(*ethpb.BeaconStateFulu)
if !ok {
return htrs
}
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoFulu(s.(*ethpb.BeaconStateFulu))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.Background())
@@ -33,8 +33,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// UnmarshalledSSZ unmarshalls serialized input.
-func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
case "ExecutionPayload":
obj = &enginev1.ExecutionPayloadDeneb{}
diff --git a/testing/spectest/shared/gloas/ssz_static/ssz_static.go b/testing/spectest/shared/gloas/ssz_static/ssz_static.go
index db668752d7..0fea08594a 100644
--- a/testing/spectest/shared/gloas/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/gloas/ssz_static/ssz_static.go
@@ -15,15 +15,15 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "gloas", unmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
// TODO: Add custom HTR for BeaconStateGloas when state-native support is implemented
// For now, only use the default fastssz HTR methods
return htrs
}
// unmarshalledSSZ unmarshalls serialized input.
-func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) {
- var obj interface{}
+func unmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (any, error) {
+ var obj any
switch folderName {
// Gloas specific types
diff --git a/testing/spectest/shared/phase0/ssz_static/ssz_static.go b/testing/spectest/shared/phase0/ssz_static/ssz_static.go
index 5220ba08db..793e6447a5 100644
--- a/testing/spectest/shared/phase0/ssz_static/ssz_static.go
+++ b/testing/spectest/shared/phase0/ssz_static/ssz_static.go
@@ -17,10 +17,10 @@ func RunSSZStaticTests(t *testing.T, config string) {
common.RunSSZStaticTests(t, config, "phase0", unmarshalledSSZ, customHtr)
}
-func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR {
+func customHtr(t *testing.T, htrs []common.HTR, object any) []common.HTR {
switch object.(type) {
case *ethpb.BeaconState:
- htrs = append(htrs, func(s interface{}) ([32]byte, error) {
+ htrs = append(htrs, func(s any) ([32]byte, error) {
beaconState, err := state_native.InitializeFromProtoPhase0(s.(*ethpb.BeaconState))
require.NoError(t, err)
return beaconState.HashTreeRoot(context.TODO())
@@ -30,8 +30,8 @@ func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR
}
// unmarshalledSSZ unmarshalls serialized input.
-func unmarshalledSSZ(t *testing.T, serializedBytes []byte, objectName string) (interface{}, error) {
- var obj interface{}
+func unmarshalledSSZ(t *testing.T, serializedBytes []byte, objectName string) (any, error) {
+ var obj any
switch objectName {
case "Attestation":
obj = ðpb.Attestation{}
diff --git a/testing/spectest/utils/utils.go b/testing/spectest/utils/utils.go
index 5580936d7e..4ef52a4d68 100644
--- a/testing/spectest/utils/utils.go
+++ b/testing/spectest/utils/utils.go
@@ -22,7 +22,7 @@ var json = jsoniter.Config{
// UnmarshalYaml using a customized json encoder that supports "spec-name"
// override tag.
-func UnmarshalYaml(y []byte, dest interface{}) error {
+func UnmarshalYaml(y []byte, dest any) error {
j, err := yaml.YAMLToJSON(y)
if err != nil {
return err
diff --git a/testing/util/altair.go b/testing/util/altair.go
index a951c095f9..0d4fce1d99 100644
--- a/testing/util/altair.go
+++ b/testing/util/altair.go
@@ -88,7 +88,7 @@ func buildGenesisBeaconState(genesisTime uint64, preState state.BeaconState, eth
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -97,17 +97,17 @@ func buildGenesisBeaconState(genesisTime uint64, preState state.BeaconState, eth
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
diff --git a/testing/util/bellatrix_state.go b/testing/util/bellatrix_state.go
index 7c1f06d83e..1e034d0054 100644
--- a/testing/util/bellatrix_state.go
+++ b/testing/util/bellatrix_state.go
@@ -93,7 +93,7 @@ func buildGenesisBeaconStateBellatrix(genesisTime time.Time, preState state.Beac
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -102,17 +102,17 @@ func buildGenesisBeaconStateBellatrix(genesisTime time.Time, preState state.Beac
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
@@ -137,7 +137,7 @@ func buildGenesisBeaconStateBellatrix(genesisTime time.Time, preState state.Beac
}
scoresMissing := len(preState.Validators()) - len(scores)
if scoresMissing > 0 {
- for i := 0; i < scoresMissing; i++ {
+ for range scoresMissing {
scores = append(scores, 0)
}
}
diff --git a/testing/util/block.go b/testing/util/block.go
index d3dfbf7de9..b4eaa59d3a 100644
--- a/testing/util/block.go
+++ b/testing/util/block.go
@@ -279,7 +279,7 @@ func generateProposerSlashings(
numSlashings uint64,
) ([]*ethpb.ProposerSlashing, error) {
proposerSlashings := make([]*ethpb.ProposerSlashing, numSlashings)
- for i := uint64(0); i < numSlashings; i++ {
+ for i := range numSlashings {
proposerIndex, err := randValIndex(bState)
if err != nil {
return nil, err
@@ -407,7 +407,7 @@ func generateAttesterSlashings(
) ([]ethpb.AttSlashing, error) {
attesterSlashings := make([]ethpb.AttSlashing, numSlashings)
randGen := rand.NewDeterministicGenerator()
- for i := uint64(0); i < numSlashings; i++ {
+ for i := range numSlashings {
committeeIndex := randGen.Uint64() % helpers.SlotCommitteeCount(uint64(bState.NumValidators()))
committee, err := helpers.BeaconCommitteeFromState(context.Background(), bState, bState.Slot(), primitives.CommitteeIndex(committeeIndex))
if err != nil {
@@ -625,7 +625,7 @@ func HydrateV1BeaconBlockBody(b *v1.BeaconBlockBody) *v1.BeaconBlockBody {
return b
}
-func SaveBlock(tb assertions.AssertionTestingTB, ctx context.Context, db iface.NoHeadAccessDatabase, b interface{}) interfaces.SignedBeaconBlock {
+func SaveBlock(tb assertions.AssertionTestingTB, ctx context.Context, db iface.NoHeadAccessDatabase, b any) interfaces.SignedBeaconBlock {
wsb, err := blocks.NewSignedBeaconBlock(b)
require.NoError(tb, err)
require.NoError(tb, db.SaveBlock(ctx, wsb))
@@ -1293,7 +1293,7 @@ func generateWithdrawals(
numWithdrawals uint64,
) ([]*enginev1.Withdrawal, error) {
withdrawalRequests := make([]*enginev1.Withdrawal, numWithdrawals)
- for i := uint64(0); i < numWithdrawals; i++ {
+ for i := range numWithdrawals {
valIndex, err := randValIndex(bState)
if err != nil {
return nil, err
diff --git a/testing/util/capella_state.go b/testing/util/capella_state.go
index 86b6ca96e6..363a25a845 100644
--- a/testing/util/capella_state.go
+++ b/testing/util/capella_state.go
@@ -91,7 +91,7 @@ func buildGenesisBeaconStateCapella(genesisTime uint64, preState state.BeaconSta
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -100,17 +100,17 @@ func buildGenesisBeaconStateCapella(genesisTime uint64, preState state.BeaconSta
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
diff --git a/testing/util/deneb.go b/testing/util/deneb.go
index 86d3b0715e..787fbce359 100644
--- a/testing/util/deneb.go
+++ b/testing/util/deneb.go
@@ -194,7 +194,7 @@ func ExtendBlocksPlusBlobs(t *testing.T, blks []blocks.ROBlock, size int) ([]blo
blks = append(blks, blk)
}
- for i := 0; i < size; i++ {
+ for range size {
prev := blks[len(blks)-1]
blk, blb := GenerateTestDenebBlockWithSidecar(t, prev.Root(), prev.Block().Slot()+1, 6)
blobs = append(blobs, blb...)
diff --git a/testing/util/deneb_state.go b/testing/util/deneb_state.go
index 31879353f9..c41a68d4c1 100644
--- a/testing/util/deneb_state.go
+++ b/testing/util/deneb_state.go
@@ -91,7 +91,7 @@ func buildGenesisBeaconStateDeneb(genesisTime uint64, preState state.BeaconState
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -100,17 +100,17 @@ func buildGenesisBeaconStateDeneb(genesisTime uint64, preState state.BeaconState
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
diff --git a/testing/util/deposits.go b/testing/util/deposits.go
index 3eb567bb04..cc85653139 100644
--- a/testing/util/deposits.go
+++ b/testing/util/deposits.go
@@ -55,7 +55,7 @@ func DeterministicDepositsAndKeys(numDeposits uint64) ([]*ethpb.Deposit, []bls.S
privKeys = append(privKeys, secretKeys[:len(secretKeys)-1]...)
// Create the new deposits and add them to the trie.
- for i := uint64(0); i < numRequired; i++ {
+ for i := range numRequired {
balance := params.BeaconConfig().MaxEffectiveBalance
deposit, err := signedDeposit(secretKeys[i], publicKeys[i].Marshal(), publicKeys[i+1].Marshal(), balance)
if err != nil {
@@ -123,7 +123,7 @@ func DepositsWithBalance(balances []uint64) ([]*ethpb.Deposit, *trie.SparseMerkl
deposits := make([]*ethpb.Deposit, numDeposits)
// Create the new deposits and add them to the trie.
- for i := uint64(0); i < numDeposits; i++ {
+ for i := range numDeposits {
balance := params.BeaconConfig().MaxEffectiveBalance
// lint:ignore uintcast -- test code
if len(balances) == int(numDeposits) {
@@ -275,7 +275,7 @@ func DeterministicGenesisState(t testing.TB, numValidators uint64) (state.Beacon
func DepositTrieFromDeposits(deposits []*ethpb.Deposit) (*trie.SparseMerkleTrie, [][32]byte, error) {
encodedDeposits := make([][]byte, len(deposits))
roots := make([][32]byte, len(deposits))
- for i := 0; i < len(encodedDeposits); i++ {
+ for i := range encodedDeposits {
hashedDeposit, err := deposits[i].Data.HashTreeRoot()
if err != nil {
return nil, [][32]byte{}, errors.Wrap(err, "could not tree hash deposit data")
@@ -330,7 +330,7 @@ func DeterministicDepositsAndKeysSameValidator(numDeposits uint64) ([]*ethpb.Dep
privKeys = append(privKeys, secretKeys[:len(secretKeys)-1]...)
// Create the new deposits and add them to the trie. Always use the first validator to create deposit
- for i := uint64(0); i < numRequired; i++ {
+ for i := range numRequired {
withdrawalCreds := hash.Hash(publicKeys[1].Marshal())
withdrawalCreds[0] = params.BeaconConfig().BLSWithdrawalPrefixByte
diff --git a/testing/util/deposits_test.go b/testing/util/deposits_test.go
index c685d8ccc3..51ce54971e 100644
--- a/testing/util/deposits_test.go
+++ b/testing/util/deposits_test.go
@@ -103,7 +103,7 @@ func TestDepositsWithBalance_MatchesDeterministic(t *testing.T) {
entries := 64
resetCache()
balances := make([]uint64, entries)
- for i := 0; i < entries; i++ {
+ for i := range entries {
balances[i] = params.BeaconConfig().MaxEffectiveBalance
}
deposits, depositTrie, err := DepositsWithBalance(balances)
@@ -116,7 +116,7 @@ func TestDepositsWithBalance_MatchesDeterministic(t *testing.T) {
_, determDepositDataRoots, err := DeterministicDepositTrie(entries)
require.NoError(t, err)
- for i := 0; i < entries; i++ {
+ for i := range entries {
if !proto.Equal(deposits[i], determDeposits[i]) {
t.Errorf("Expected deposit %d to match", i)
}
diff --git a/testing/util/electra_block.go b/testing/util/electra_block.go
index 62b11517e9..0d046b060c 100644
--- a/testing/util/electra_block.go
+++ b/testing/util/electra_block.go
@@ -270,7 +270,7 @@ func generateWithdrawalRequests(
numRequests uint64,
) ([]*v1.WithdrawalRequest, error) {
withdrawalRequests := make([]*v1.WithdrawalRequest, numRequests)
- for i := uint64(0); i < numRequests; i++ {
+ for i := range numRequests {
valIndex, err := randValIndex(bState)
if err != nil {
return nil, err
@@ -310,7 +310,7 @@ func generateDepositRequests(
numRequests uint64,
) ([]*v1.DepositRequest, error) {
depositRequests := make([]*v1.DepositRequest, numRequests)
- for i := uint64(0); i < numRequests; i++ {
+ for i := range numRequests {
valIndex, err := randValIndex(bState)
if err != nil {
return nil, err
@@ -362,7 +362,7 @@ func generateConsolidationRequests(
numRequests uint64,
) ([]*v1.ConsolidationRequest, error) {
consolidationRequests := make([]*v1.ConsolidationRequest, numRequests)
- for i := uint64(0); i < numRequests; i++ {
+ for i := range numRequests {
valIndex, err := randValIndex(bState)
if err != nil {
return nil, err
diff --git a/testing/util/electra_state.go b/testing/util/electra_state.go
index 4dac18f127..72077e609c 100644
--- a/testing/util/electra_state.go
+++ b/testing/util/electra_state.go
@@ -120,7 +120,7 @@ func buildGenesisBeaconStateElectra(genesisTime uint64, preState state.BeaconSta
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -129,17 +129,17 @@ func buildGenesisBeaconStateElectra(genesisTime uint64, preState state.BeaconSta
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
diff --git a/testing/util/fulu_state.go b/testing/util/fulu_state.go
index 14e3472219..968f27fefa 100644
--- a/testing/util/fulu_state.go
+++ b/testing/util/fulu_state.go
@@ -115,7 +115,7 @@ func buildGenesisBeaconStateFulu(genesisTime uint64, preState state.BeaconState,
}
randaoMixes := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(randaoMixes); i++ {
+ for i := range randaoMixes {
h := make([]byte, 32)
copy(h, eth1Data.BlockHash)
randaoMixes[i] = h
@@ -124,17 +124,17 @@ func buildGenesisBeaconStateFulu(genesisTime uint64, preState state.BeaconState,
zeroHash := params.BeaconConfig().ZeroHash[:]
activeIndexRoots := make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector)
- for i := 0; i < len(activeIndexRoots); i++ {
+ for i := range activeIndexRoots {
activeIndexRoots[i] = zeroHash
}
blockRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(blockRoots); i++ {
+ for i := range blockRoots {
blockRoots[i] = zeroHash
}
stateRoots := make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot)
- for i := 0; i < len(stateRoots); i++ {
+ for i := range stateRoots {
stateRoots[i] = zeroHash
}
diff --git a/testing/util/helpers.go b/testing/util/helpers.go
index 9e55ee07ac..c6161f3d94 100644
--- a/testing/util/helpers.go
+++ b/testing/util/helpers.go
@@ -42,7 +42,7 @@ func RandaoReveal(beaconState state.ReadOnlyBeaconState, epoch primitives.Epoch,
// BlockSignature calculates the post-state root of the block and returns the signature.
func BlockSignature(
bState state.BeaconState,
- block interface{},
+ block any,
privKeys []bls.SecretKey,
) (bls.Signature, error) {
var wsb interfaces.ReadOnlySignedBeaconBlock
diff --git a/testing/util/lightclient.go b/testing/util/lightclient.go
index 58bbfce572..49ee757c14 100644
--- a/testing/util/lightclient.go
+++ b/testing/util/lightclient.go
@@ -1285,7 +1285,7 @@ func MockOptimisticUpdate() (interfaces.LightClientOptimisticUpdate, error) {
func MockFinalityUpdate() (interfaces.LightClientFinalityUpdate, error) {
finalityBranch := make([][]byte, fieldparams.FinalityBranchDepth)
- for i := 0; i < len(finalityBranch); i++ {
+ for i := range finalityBranch {
finalityBranch[i] = make([]byte, 32)
}
diff --git a/testing/util/state.go b/testing/util/state.go
index f537e254a7..1118407490 100644
--- a/testing/util/state.go
+++ b/testing/util/state.go
@@ -514,7 +514,7 @@ func NewBeaconStateFulu(options ...func(state *ethpb.BeaconStateFulu) error) (st
// trip testing.
func filledByteSlice2D(length, innerLen uint64) [][]byte {
b := make([][]byte, length)
- for i := uint64(0); i < length; i++ {
+ for i := range length {
b[i] = make([]byte, innerLen)
}
return b
@@ -524,10 +524,10 @@ func filledByteSlice2D(length, innerLen uint64) [][]byte {
// Example: 16 becomes 0x00...0f.
func PrepareRoots(size int) ([][]byte, error) {
roots := make([][]byte, size)
- for i := 0; i < size; i++ {
+ for i := range size {
roots[i] = make([]byte, fieldparams.RootLength)
}
- for j := 0; j < len(roots); j++ {
+ for j := range roots {
// Remove '0x' prefix and left-pad '0' to have 64 chars in total.
s := fmt.Sprintf("%064s", hexutil.EncodeUint64(uint64(j))[2:])
h, err := hexutil.Decode("0x" + s)
diff --git a/third_party/hid/usb.go b/third_party/hid/usb.go
index 4aa13353fe..9f54c80022 100644
--- a/third_party/hid/usb.go
+++ b/third_party/hid/usb.go
@@ -46,7 +46,7 @@ type DeviceInfo struct {
Interface int
// Raw low level libusb endpoint data for simplified communication
- rawDevice interface{}
+ rawDevice any
rawPort *uint8 // Pointer to differentiate between unset and port 0
rawReader *uint8 // Pointer to differentiate between unset and endpoint 0
rawWriter *uint8 // Pointer to differentiate between unset and endpoint 0
diff --git a/third_party/hid/usb_test.go b/third_party/hid/usb_test.go
index c05e21785c..7162e9887a 100644
--- a/third_party/hid/usb_test.go
+++ b/third_party/hid/usb_test.go
@@ -25,12 +25,12 @@ import (
// Tests that HID enumeration can be called concurrently from multiple threads.
func TestThreadedEnumerateHid(t *testing.T) {
var pend sync.WaitGroup
- for i := 0; i < 8; i++ {
+ for i := range 8 {
pend.Add(1)
go func(index int) {
defer pend.Done()
- for j := 0; j < 512; j++ {
+ for j := range 512 {
if _, err := EnumerateHid(uint16(index), 0); err != nil {
t.Errorf("thread %d, iter %d: failed to enumerate: %v", index, j, err)
}
@@ -48,12 +48,12 @@ func TestThreadedEnumerateRaw(t *testing.T) {
}
// Yay, we can actually test this
var pend sync.WaitGroup
- for i := 0; i < 8; i++ {
+ for i := range 8 {
pend.Add(1)
go func(index int) {
defer pend.Done()
- for j := 0; j < 512; j++ {
+ for j := range 512 {
if _, err := EnumerateRaw(uint16(index), 0); err != nil {
t.Errorf("thread %d, iter %d: failed to enumerate: %v", index, j, err)
}
@@ -70,12 +70,12 @@ func TestThreadedEnumerate(t *testing.T) {
t.Skip("Linux on Travis doesn't have usbfs, skipping test")
}
var pend sync.WaitGroup
- for i := 0; i < 8; i++ {
+ for i := range 8 {
pend.Add(1)
go func(index int) {
defer pend.Done()
- for j := 0; j < 512; j++ {
+ for j := range 512 {
if _, err := Enumerate(uint16(index), 0); err != nil {
t.Errorf("thread %d, iter %d: failed to enumerate: %v", index, j, err)
}
diff --git a/third_party/org_golang_x_tools-deletegopls.patch b/third_party/org_golang_x_tools-deletegopls.patch
new file mode 100644
index 0000000000..98512faa8e
--- /dev/null
+++ b/third_party/org_golang_x_tools-deletegopls.patch
@@ -0,0 +1,203162 @@
+diff -urN a/gopls/contributors.txt b/gopls/contributors.txt
+--- a/gopls/contributors.txt 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/contributors.txt 1969-12-31 18:00:00.000000000 -0600
+@@ -1,41 +0,0 @@
+-# This list is used to help generate the GitHub handles in the
+-# contributor shout-out of each gopls release note.
+-#
+-# Use a command such as this to convert a range of git commits:
+-#
+-# $ join -o 2.2 <(git log --format="format: %aE" gopls/v0.18.1..gopls/v0.19.0-pre.3 | sort -u) \
+-# <(grep '^[^#]' ~/w/xtools/gopls/contributors.txt | sort)
+-#
+-# Use -v 1 instead of -o 2.2 to detect any missing entries.
+-
+-acehinnnqru@gmail.com acehinnnqru
+-adonovan@google.com adonovan
+-albertofanjul@gmail.com albfan
+-alessandro.arzilli@gmail.com aarzilli
+-ashurbekovz23@gmail.com ashurbekovz
+-cuong.manhle.vn@gmail.com cuonglm
+-dmitshur@golang.org dmitshur
+-dneil@google.com neild
+-egonelbre@gmail.com egonelbre
+-email@ssnk.in shashank-priyadarshi
+-ethan.reesor@gmail.com firelizzard18
+-gobot@golang.org gopherbot
+-hxjiang@golang.org h9jiang
+-imcusg@gmail.com cuishuang
+-jacob.b.bailey@gmail.com jakebailey
+-jba@google.com jba
+-mkalil@google.com madelinekalil
+-mrnk3078@gmail.com karamaru-alpha
+-nguyenbao1917@gmail.com danztran
+-nick.ripley@datadoghq.com nsrip-dd
+-pjw@google.com pjweinb
+-rfindley@google.com findleyr
+-samthanawalla@google.com samthanawalla
+-sean@liao.dev seankhliao
+-tklauser@distanz.ch tklauser
+-veblomqvist@gmail.com vikblom
+-weingartner@google.com kwjw
+-xieyuschen@gmail.com xieyuschen
+-
+-# GitHub ID unknown:
+-# - shibinxf@gmail.com
+diff -urN a/gopls/doc/advanced.md b/gopls/doc/advanced.md
+--- a/gopls/doc/advanced.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/advanced.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,59 +0,0 @@
+----
+-title: "Gopls: Advanced topics"
+----
+-
+-This documentation is for advanced `gopls` users, who may want to test
+-unreleased versions or try out special features.
+-
+-## Installing unreleased versions
+-
+-To get a specific version of `gopls` (for example, to test a prerelease
+-version), run:
+-
+-```sh
+-$ go install golang.org/x/tools/gopls@vX.Y.Z
+-```
+-
+-Where `vX.Y.Z` is the desired version.
+-
+-### Unstable versions
+-
+-To update `gopls` to the latest **unstable** version, use the following
+-commands.
+-
+-```sh
+-# Create an empty go.mod file, only for tracking requirements.
+-cd $(mktemp -d)
+-go mod init gopls-unstable
+-
+-# Use 'go get' to add requirements and to ensure they work together.
+-go get -d golang.org/x/tools/gopls@master golang.org/x/tools@master
+-
+-go install golang.org/x/tools/gopls
+-```
+-
+-## Working on the Go source distribution
+-
+-If you are working on the [Go project] itself, the `go` command that `gopls`
+-invokes will have to correspond to the version of the source you are working
+-on. That is, if you have checked out the Go project to `$HOME/go`, your `go`
+-command should be the `$HOME/go/bin/go` executable that you built with
+-`make.bash` or equivalent.
+-
+-You can achieve this by adding the right version of `go` to your `PATH`
+-(`export PATH=$HOME/go/bin:$PATH` on Unix systems) or by configuring your
+-editor.
+-
+-To work on both `std` and `cmd` simultaneously, add a `go.work` file to
+-`GOROOT/src`:
+-
+-```
+-cd $(go env GOROOT)/src
+-go work init . cmd
+-```
+-
+-Note that you must work inside the `GOROOT/src` subdirectory, as the `go`
+-command does not recognize `go.work` files in a parent of `GOROOT/src`
+-(https://go.dev/issue/59429).
+-
+-[Go project]: https://go.googlesource.com/go
+diff -urN a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
+--- a/gopls/doc/analyzers.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/analyzers.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,4358 +0,0 @@
+----
+-title: "Gopls: Analyzers"
+----
+-
+-
+-
+-Gopls contains a driver for pluggable, modular static
+-[analyzers](https://pkg.go.dev/golang.org/x/tools/go/analysis#hdr-Analyzer),
+-such as those used by [go vet](https://pkg.go.dev/cmd/vet).
+-
+-Most analyzers report mistakes in your code;
+-some suggest "quick fixes" that can be directly applied in your editor.
+-Every time you edit your code, gopls re-runs its analyzers.
+-Analyzer diagnostics help you detect bugs sooner,
+-before you run your tests, or even before you save your files.
+-
+-This document describes the suite of analyzers available in gopls,
+-which aggregates analyzers from a variety of sources:
+-
+-- all the usual bug-finding analyzers from the `go vet` suite (e.g. `printf`; see [`go tool vet help`](https://pkg.go.dev/cmd/vet) for the complete list);
+-- a number of analyzers with more substantial dependencies that prevent them from being used in `go vet` (e.g. `nilness`);
+-- analyzers that augment compilation errors by suggesting quick fixes to common mistakes (e.g. `fillreturns`); and
+-- a handful of analyzers that suggest possible style improvements (e.g. `simplifyrange`).
+-
+-To enable or disable analyzers, use the [analyses](settings.md#analyses) setting.
+-
+-In addition, gopls includes the [`staticcheck` suite](https://staticcheck.dev/docs/checks).
+-When the [`staticcheck`](settings.md#staticcheck`) boolean option is
+-unset, slightly more than half of these analyzers are enabled by
+-default; this subset has been chosen for precision and efficiency. Set
+-`staticcheck` to `true` to enable the complete set, or to `false` to
+-disable the complete set.
+-
+-Staticcheck analyzers, like all other analyzers, can be explicitly
+-enabled or disabled using the `analyzers` configuration setting; this
+-setting takes precedence over the `staticcheck` setting, so,
+-regardless of what value of `staticcheck` you use (true/false/unset),
+-you can make adjustments to your preferred set of analyzers.
+-
+-
+-
+-
+-## `QF1001`: Apply De Morgan's law
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"QF1001": true}`.
+-
+-Package documentation: [QF1001](https://staticcheck.dev/docs/checks/#QF1001)
+-
+-
+-## `QF1002`: Convert untagged switch to tagged switch
+-
+-An untagged switch that compares a single variable against a series of values can be replaced with a tagged switch.
+-
+-Before:
+-
+- switch {
+- case x == 1 || x == 2, x == 3:
+- ...
+- case x == 4:
+- ...
+- default:
+- ...
+- }
+-
+-After:
+-
+- switch x {
+- case 1, 2, 3:
+- ...
+- case 4:
+- ...
+- default:
+- ...
+- }
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [QF1002](https://staticcheck.dev/docs/checks/#QF1002)
+-
+-
+-## `QF1003`: Convert if/else-if chain to tagged switch
+-
+-A series of if/else-if checks comparing the same variable against values can be replaced with a tagged switch.
+-
+-Before:
+-
+- if x == 1 || x == 2 {
+- ...
+- } else if x == 3 {
+- ...
+- } else {
+- ...
+- }
+-
+-After:
+-
+- switch x {
+- case 1, 2:
+- ...
+- case 3:
+- ...
+- default:
+- ...
+- }
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [QF1003](https://staticcheck.dev/docs/checks/#QF1003)
+-
+-
+-## `QF1004`: Use strings.ReplaceAll instead of strings.Replace with n == -1
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [QF1004](https://staticcheck.dev/docs/checks/#QF1004)
+-
+-
+-## `QF1005`: Expand call to math.Pow
+-
+-Some uses of math.Pow can be simplified to basic multiplication.
+-
+-Before:
+-
+- math.Pow(x, 2)
+-
+-After:
+-
+- x * x
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"QF1005": true}`.
+-
+-Package documentation: [QF1005](https://staticcheck.dev/docs/checks/#QF1005)
+-
+-
+-## `QF1006`: Lift if+break into loop condition
+-
+-Before:
+-
+- for {
+- if done {
+- break
+- }
+- ...
+- }
+-
+-After:
+-
+- for !done {
+- ...
+- }
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"QF1006": true}`.
+-
+-Package documentation: [QF1006](https://staticcheck.dev/docs/checks/#QF1006)
+-
+-
+-## `QF1007`: Merge conditional assignment into variable declaration
+-
+-Before:
+-
+- x := false
+- if someCondition {
+- x = true
+- }
+-
+-After:
+-
+- x := someCondition
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"QF1007": true}`.
+-
+-Package documentation: [QF1007](https://staticcheck.dev/docs/checks/#QF1007)
+-
+-
+-## `QF1008`: Omit embedded fields from selector expression
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"QF1008": true}`.
+-
+-Package documentation: [QF1008](https://staticcheck.dev/docs/checks/#QF1008)
+-
+-
+-## `QF1009`: Use time.Time.Equal instead of == operator
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [QF1009](https://staticcheck.dev/docs/checks/#QF1009)
+-
+-
+-## `QF1010`: Convert slice of bytes to string when printing it
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [QF1010](https://staticcheck.dev/docs/checks/#QF1010)
+-
+-
+-## `QF1011`: Omit redundant type from variable declaration
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"QF1011": true}`.
+-
+-Package documentation: [QF1011](https://staticcheck.dev/docs/checks/#)
+-
+-
+-## `QF1012`: Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: on.
+-
+-Package documentation: [QF1012](https://staticcheck.dev/docs/checks/#QF1012)
+-
+-
+-## `S1000`: Use plain channel send or receive instead of single-case select
+-
+-Select statements with a single case can be replaced with a simple send or receive.
+-
+-Before:
+-
+- select {
+- case x := <-ch:
+- fmt.Println(x)
+- }
+-
+-After:
+-
+- x := <-ch
+- fmt.Println(x)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1000](https://staticcheck.dev/docs/checks/#S1000)
+-
+-
+-## `S1001`: Replace for loop with call to copy
+-
+-Use copy() for copying elements from one slice to another. For arrays of identical size, you can use simple assignment.
+-
+-Before:
+-
+- for i, x := range src {
+- dst[i] = x
+- }
+-
+-After:
+-
+- copy(dst, src)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1001](https://staticcheck.dev/docs/checks/#S1001)
+-
+-
+-## `S1002`: Omit comparison with boolean constant
+-
+-Before:
+-
+- if x == true {}
+-
+-After:
+-
+- if x {}
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1002": true}`.
+-
+-Package documentation: [S1002](https://staticcheck.dev/docs/checks/#S1002)
+-
+-
+-## `S1003`: Replace call to strings.Index with strings.Contains
+-
+-Before:
+-
+- if strings.Index(x, y) != -1 {}
+-
+-After:
+-
+- if strings.Contains(x, y) {}
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1003](https://staticcheck.dev/docs/checks/#S1003)
+-
+-
+-## `S1004`: Replace call to bytes.Compare with bytes.Equal
+-
+-Before:
+-
+- if bytes.Compare(x, y) == 0 {}
+-
+-After:
+-
+- if bytes.Equal(x, y) {}
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1004](https://staticcheck.dev/docs/checks/#S1004)
+-
+-
+-## `S1005`: Drop unnecessary use of the blank identifier
+-
+-In many cases, assigning to the blank identifier is unnecessary.
+-
+-Before:
+-
+- for _ = range s {}
+- x, _ = someMap[key]
+- _ = <-ch
+-
+-After:
+-
+- for range s{}
+- x = someMap[key]
+- <-ch
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1005": true}`.
+-
+-Package documentation: [S1005](https://staticcheck.dev/docs/checks/#S1005)
+-
+-
+-## `S1006`: Use 'for { ... }' for infinite loops
+-
+-For infinite loops, using for { ... } is the most idiomatic choice.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1006": true}`.
+-
+-Package documentation: [S1006](https://staticcheck.dev/docs/checks/#S1006)
+-
+-
+-## `S1007`: Simplify regular expression by using raw string literal
+-
+-Raw string literals use backticks instead of quotation marks and do not support any escape sequences. This means that the backslash can be used freely, without the need of escaping.
+-
+-Since regular expressions have their own escape sequences, raw strings can improve their readability.
+-
+-Before:
+-
+- regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")
+-
+-After:
+-
+- regexp.Compile(`\A(\w+) profile: total \d+\n\z`)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1007](https://staticcheck.dev/docs/checks/#S1007)
+-
+-
+-## `S1008`: Simplify returning boolean expression
+-
+-Before:
+-
+- if {
+- return true
+- }
+- return false
+-
+-After:
+-
+- return
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1008": true}`.
+-
+-Package documentation: [S1008](https://staticcheck.dev/docs/checks/#S1008)
+-
+-
+-## `S1009`: Omit redundant nil check on slices, maps, and channels
+-
+-The len function is defined for all slices, maps, and channels, even nil ones, which have a length of zero. It is not necessary to check for nil before checking that their length is not zero.
+-
+-Before:
+-
+- if x != nil && len(x) != 0 {}
+-
+-After:
+-
+- if len(x) != 0 {}
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1009](https://staticcheck.dev/docs/checks/#S1009)
+-
+-
+-## `S1010`: Omit default slice index
+-
+-When slicing, the second index defaults to the length of the value, making s\[n:len(s)] and s\[n:] equivalent.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1010](https://staticcheck.dev/docs/checks/#S1010)
+-
+-
+-## `S1011`: Use a single append to concatenate two slices
+-
+-Before:
+-
+- for _, e := range y {
+- x = append(x, e)
+- }
+-
+- for i := range y {
+- x = append(x, y[i])
+- }
+-
+- for i := range y {
+- v := y[i]
+- x = append(x, v)
+- }
+-
+-After:
+-
+- x = append(x, y...)
+- x = append(x, y...)
+- x = append(x, y...)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1011": true}`.
+-
+-Package documentation: [S1011](https://staticcheck.dev/docs/checks/#S1011)
+-
+-
+-## `S1012`: Replace time.Now().Sub(x) with time.Since(x)
+-
+-The time.Since helper has the same effect as using time.Now().Sub(x) but is easier to read.
+-
+-Before:
+-
+- time.Now().Sub(x)
+-
+-After:
+-
+- time.Since(x)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1012](https://staticcheck.dev/docs/checks/#S1012)
+-
+-
+-## `S1016`: Use a type conversion instead of manually copying struct fields
+-
+-Two struct types with identical fields can be converted between each other. In older versions of Go, the fields had to have identical struct tags. Since Go 1.8, however, struct tags are ignored during conversions. It is thus not necessary to manually copy every field individually.
+-
+-Before:
+-
+- var x T1
+- y := T2{
+- Field1: x.Field1,
+- Field2: x.Field2,
+- }
+-
+-After:
+-
+- var x T1
+- y := T2(x)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1016": true}`.
+-
+-Package documentation: [S1016](https://staticcheck.dev/docs/checks/#S1016)
+-
+-
+-## `S1017`: Replace manual trimming with strings.TrimPrefix
+-
+-Instead of using strings.HasPrefix and manual slicing, use the strings.TrimPrefix function. If the string doesn't start with the prefix, the original string will be returned. Using strings.TrimPrefix reduces complexity, and avoids common bugs, such as off-by-one mistakes.
+-
+-Before:
+-
+- if strings.HasPrefix(str, prefix) {
+- str = str[len(prefix):]
+- }
+-
+-After:
+-
+- str = strings.TrimPrefix(str, prefix)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1017](https://staticcheck.dev/docs/checks/#S1017)
+-
+-
+-## `S1018`: Use 'copy' for sliding elements
+-
+-copy() permits using the same source and destination slice, even with overlapping ranges. This makes it ideal for sliding elements in a slice.
+-
+-Before:
+-
+- for i := 0; i < n; i++ {
+- bs[i] = bs[offset+i]
+- }
+-
+-After:
+-
+- copy(bs[:n], bs[offset:])
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1018](https://staticcheck.dev/docs/checks/#S1018)
+-
+-
+-## `S1019`: Simplify 'make' call by omitting redundant arguments
+-
+-The 'make' function has default values for the length and capacity arguments. For channels, the length defaults to zero, and for slices, the capacity defaults to the length.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1019](https://staticcheck.dev/docs/checks/#S1019)
+-
+-
+-## `S1020`: Omit redundant nil check in type assertion
+-
+-Before:
+-
+- if _, ok := i.(T); ok && i != nil {}
+-
+-After:
+-
+- if _, ok := i.(T); ok {}
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1020](https://staticcheck.dev/docs/checks/#S1020)
+-
+-
+-## `S1021`: Merge variable declaration and assignment
+-
+-Before:
+-
+- var x uint
+- x = 1
+-
+-After:
+-
+- var x uint = 1
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1021": true}`.
+-
+-Package documentation: [S1021](https://staticcheck.dev/docs/checks/#S1021)
+-
+-
+-## `S1023`: Omit redundant control flow
+-
+-Functions that have no return value do not need a return statement as the final statement of the function.
+-
+-Switches in Go do not have automatic fallthrough, unlike languages like C. It is not necessary to have a break statement as the final statement in a case block.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1023](https://staticcheck.dev/docs/checks/#S1023)
+-
+-
+-## `S1024`: Replace x.Sub(time.Now()) with time.Until(x)
+-
+-The time.Until helper has the same effect as using x.Sub(time.Now()) but is easier to read.
+-
+-Before:
+-
+- x.Sub(time.Now())
+-
+-After:
+-
+- time.Until(x)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1024](https://staticcheck.dev/docs/checks/#S1024)
+-
+-
+-## `S1025`: Don't use fmt.Sprintf("%s", x) unnecessarily
+-
+-In many instances, there are easier and more efficient ways of getting a value's string representation. Whenever a value's underlying type is a string already, or the type has a String method, they should be used directly.
+-
+-Given the following shared definitions
+-
+- type T1 string
+- type T2 int
+-
+- func (T2) String() string { return "Hello, world" }
+-
+- var x string
+- var y T1
+- var z T2
+-
+-we can simplify
+-
+- fmt.Sprintf("%s", x)
+- fmt.Sprintf("%s", y)
+- fmt.Sprintf("%s", z)
+-
+-to
+-
+- x
+- string(y)
+- z.String()
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1025": true}`.
+-
+-Package documentation: [S1025](https://staticcheck.dev/docs/checks/#S1025)
+-
+-
+-## `S1028`: Simplify error construction with fmt.Errorf
+-
+-Before:
+-
+- errors.New(fmt.Sprintf(...))
+-
+-After:
+-
+- fmt.Errorf(...)
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1028](https://staticcheck.dev/docs/checks/#S1028)
+-
+-
+-## `S1029`: Range over the string directly
+-
+-Ranging over a string will yield byte offsets and runes. If the offset isn't used, this is functionally equivalent to converting the string to a slice of runes and ranging over that. Ranging directly over the string will be more performant, however, as it avoids allocating a new slice, the size of which depends on the length of the string.
+-
+-Before:
+-
+- for _, r := range []rune(s) {}
+-
+-After:
+-
+- for _, r := range s {}
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"S1029": true}`.
+-
+-Package documentation: [S1029](https://staticcheck.dev/docs/checks/#S1029)
+-
+-
+-## `S1030`: Use bytes.Buffer.String or bytes.Buffer.Bytes
+-
+-bytes.Buffer has both a String and a Bytes method. It is almost never necessary to use string(buf.Bytes()) or \[]byte(buf.String()) – simply use the other method.
+-
+-The only exception to this are map lookups. Due to a compiler optimization, m\[string(buf.Bytes())] is more efficient than m\[buf.String()].
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1030](https://staticcheck.dev/docs/checks/#S1030)
+-
+-
+-## `S1031`: Omit redundant nil check around loop
+-
+-You can use range on nil slices and maps, the loop will simply never execute. This makes an additional nil check around the loop unnecessary.
+-
+-Before:
+-
+- if s != nil {
+- for _, x := range s {
+- ...
+- }
+- }
+-
+-After:
+-
+- for _, x := range s {
+- ...
+- }
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1031](https://staticcheck.dev/docs/checks/#S1031)
+-
+-
+-## `S1032`: Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)
+-
+-The sort.Ints, sort.Float64s and sort.Strings functions are easier to read than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x)) and sort.Sort(sort.StringSlice(x)).
+-
+-Before:
+-
+- sort.Sort(sort.StringSlice(x))
+-
+-After:
+-
+- sort.Strings(x)
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1032](https://staticcheck.dev/docs/checks/#S1032)
+-
+-
+-## `S1033`: Unnecessary guard around call to 'delete'
+-
+-Calling delete on a nil map is a no-op.
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: on.
+-
+-Package documentation: [S1033](https://staticcheck.dev/docs/checks/#S1033)
+-
+-
+-## `S1034`: Use result of type assertion to simplify cases
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: on.
+-
+-Package documentation: [S1034](https://staticcheck.dev/docs/checks/#S1034)
+-
+-
+-## `S1035`: Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header
+-
+-The methods on net/http.Header, namely Add, Del, Get and Set, already canonicalize the given header name.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1035](https://staticcheck.dev/docs/checks/#S1035)
+-
+-
+-## `S1036`: Unnecessary guard around map access
+-
+-When accessing a map key that doesn't exist yet, one receives a zero value. Often, the zero value is a suitable value, for example when using append or doing integer math.
+-
+-The following
+-
+- if _, ok := m["foo"]; ok {
+- m["foo"] = append(m["foo"], "bar")
+- } else {
+- m["foo"] = []string{"bar"}
+- }
+-
+-can be simplified to
+-
+- m["foo"] = append(m["foo"], "bar")
+-
+-and
+-
+- if _, ok := m2["k"]; ok {
+- m2["k"] += 4
+- } else {
+- m2["k"] = 4
+- }
+-
+-can be simplified to
+-
+- m["k"] += 4
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1036](https://staticcheck.dev/docs/checks/#S1036)
+-
+-
+-## `S1037`: Elaborate way of sleeping
+-
+-Using a select statement with a single case receiving from the result of time.After is a very elaborate way of sleeping that can much simpler be expressed with a simple call to time.Sleep.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1037](https://staticcheck.dev/docs/checks/#S1037)
+-
+-
+-## `S1038`: Unnecessarily complex way of printing formatted string
+-
+-Instead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1038](https://staticcheck.dev/docs/checks/#S1038)
+-
+-
+-## `S1039`: Unnecessary use of fmt.Sprint
+-
+-Calling fmt.Sprint with a single string argument is unnecessary and identical to using the string directly.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1039](https://staticcheck.dev/docs/checks/#S1039)
+-
+-
+-## `S1040`: Type assertion to current type
+-
+-The type assertion x.(SomeInterface), when x already has type SomeInterface, can only fail if x is nil. Usually, this is left-over code from when x had a different type and you can safely delete the type assertion. If you want to check that x is not nil, consider being explicit and using an actual if x == nil comparison instead of relying on the type assertion panicking.
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [S1040](https://staticcheck.dev/docs/checks/#S1040)
+-
+-
+-## `SA1000`: Invalid regular expression
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1000": true}`.
+-
+-Package documentation: [SA1000](https://staticcheck.dev/docs/checks/#SA1000)
+-
+-
+-## `SA1001`: Invalid template
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1001](https://staticcheck.dev/docs/checks/#SA1001)
+-
+-
+-## `SA1002`: Invalid format in time.Parse
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1002": true}`.
+-
+-Package documentation: [SA1002](https://staticcheck.dev/docs/checks/#SA1002)
+-
+-
+-## `SA1003`: Unsupported argument to functions in encoding/binary
+-
+-The encoding/binary package can only serialize types with known sizes. This precludes the use of the int and uint types, as their sizes differ on different architectures. Furthermore, it doesn't support serializing maps, channels, strings, or functions.
+-
+-Before Go 1.8, bool wasn't supported, either.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1003": true}`.
+-
+-Package documentation: [SA1003](https://staticcheck.dev/docs/checks/#SA1003)
+-
+-
+-## `SA1004`: Suspiciously small untyped constant in time.Sleep
+-
+-The time.Sleep function takes a time.Duration as its only argument. Durations are expressed in nanoseconds. Thus, calling time.Sleep(1) will sleep for 1 nanosecond. This is a common source of bugs, as sleep functions in other languages often accept seconds or milliseconds.
+-
+-The time package provides constants such as time.Second to express large durations. These can be combined with arithmetic to express arbitrary durations, for example 5 \* time.Second for 5 seconds.
+-
+-If you truly meant to sleep for a tiny amount of time, use n \* time.Nanosecond to signal to Staticcheck that you did mean to sleep for some amount of nanoseconds.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1004](https://staticcheck.dev/docs/checks/#SA1004)
+-
+-
+-## `SA1005`: Invalid first argument to exec.Command
+-
+-os/exec runs programs directly (using variants of the fork and exec system calls on Unix systems). This shouldn't be confused with running a command in a shell. The shell will allow for features such as input redirection, pipes, and general scripting. The shell is also responsible for splitting the user's input into a program name and its arguments. For example, the equivalent to
+-
+- ls / /tmp
+-
+-would be
+-
+- exec.Command("ls", "/", "/tmp")
+-
+-If you want to run a command in a shell, consider using something like the following – but be aware that not all systems, particularly Windows, will have a /bin/sh program:
+-
+- exec.Command("/bin/sh", "-c", "ls | grep Awesome")
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1005](https://staticcheck.dev/docs/checks/#SA1005)
+-
+-
+-## `SA1007`: Invalid URL in net/url.Parse
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1007": true}`.
+-
+-Package documentation: [SA1007](https://staticcheck.dev/docs/checks/#SA1007)
+-
+-
+-## `SA1008`: Non-canonical key in http.Header map
+-
+-Keys in http.Header maps are canonical, meaning they follow a specific combination of uppercase and lowercase letters. Methods such as http.Header.Add and http.Header.Del convert inputs into this canonical form before manipulating the map.
+-
+-When manipulating http.Header maps directly, as opposed to using the provided methods, care should be taken to stick to canonical form in order to avoid inconsistencies. The following piece of code demonstrates one such inconsistency:
+-
+- h := http.Header{}
+- h["etag"] = []string{"1234"}
+- h.Add("etag", "5678")
+- fmt.Println(h)
+-
+- // Output:
+- // map[Etag:[5678] etag:[1234]]
+-
+-The easiest way of obtaining the canonical form of a key is to use http.CanonicalHeaderKey.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1008](https://staticcheck.dev/docs/checks/#SA1008)
+-
+-
+-## `SA1010`: (*regexp.Regexp).FindAll called with n == 0, which will always return zero results
+-
+-If n >= 0, the function returns at most n matches/submatches. To return all results, specify a negative number.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1010": true}`.
+-
+-Package documentation: [SA1010](https://staticcheck.dev/docs/checks/#SA1010)
+-
+-
+-## `SA1011`: Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1011": true}`.
+-
+-Package documentation: [SA1011](https://staticcheck.dev/docs/checks/#SA1011)
+-
+-
+-## `SA1012`: A nil context.Context is being passed to a function, consider using context.TODO instead
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1012](https://staticcheck.dev/docs/checks/#SA1012)
+-
+-
+-## `SA1013`: io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1013](https://staticcheck.dev/docs/checks/#SA1013)
+-
+-
+-## `SA1014`: Non-pointer value passed to Unmarshal or Decode
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1014": true}`.
+-
+-Package documentation: [SA1014](https://staticcheck.dev/docs/checks/#SA1014)
+-
+-
+-## `SA1015`: Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions
+-
+-Before Go 1.23, time.Tickers had to be closed to be able to be garbage collected. Since time.Tick doesn't make it possible to close the underlying ticker, using it repeatedly would leak memory.
+-
+-Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1015": true}`.
+-
+-Package documentation: [SA1015](https://staticcheck.dev/docs/checks/#SA1015)
+-
+-
+-## `SA1016`: Trapping a signal that cannot be trapped
+-
+-Not all signals can be intercepted by a process. Specifically, on UNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are never passed to the process, but instead handled directly by the kernel. It is therefore pointless to try and handle these signals.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA1016](https://staticcheck.dev/docs/checks/#SA1016)
+-
+-
+-## `SA1017`: Channels used with os/signal.Notify should be buffered
+-
+-The os/signal package uses non-blocking channel sends when delivering signals. If the receiving end of the channel isn't ready and the channel is either unbuffered or full, the signal will be dropped. To avoid missing signals, the channel should be buffered and of the appropriate size. For a channel used for notification of just one signal value, a buffer of size 1 is sufficient.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1017": true}`.
+-
+-Package documentation: [SA1017](https://staticcheck.dev/docs/checks/#SA1017)
+-
+-
+-## `SA1018`: strings.Replace called with n == 0, which does nothing
+-
+-With n == 0, zero instances will be replaced. To replace all instances, use a negative number, or use strings.ReplaceAll.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1018": true}`.
+-
+-Package documentation: [SA1018](https://staticcheck.dev/docs/checks/#SA1018)
+-
+-
+-## `SA1020`: Using an invalid host:port pair with a net.Listen-related function
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1020": true}`.
+-
+-Package documentation: [SA1020](https://staticcheck.dev/docs/checks/#SA1020)
+-
+-
+-## `SA1021`: Using bytes.Equal to compare two net.IP
+-
+-A net.IP stores an IPv4 or IPv6 address as a slice of bytes. The length of the slice for an IPv4 address, however, can be either 4 or 16 bytes long, using different ways of representing IPv4 addresses. In order to correctly compare two net.IPs, the net.IP.Equal method should be used, as it takes both representations into account.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1021": true}`.
+-
+-Package documentation: [SA1021](https://staticcheck.dev/docs/checks/#SA1021)
+-
+-
+-## `SA1023`: Modifying the buffer in an io.Writer implementation
+-
+-Write must not modify the slice data, even temporarily.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1023": true}`.
+-
+-Package documentation: [SA1023](https://staticcheck.dev/docs/checks/#SA1023)
+-
+-
+-## `SA1024`: A string cutset contains duplicate characters
+-
+-The strings.TrimLeft and strings.TrimRight functions take cutsets, not prefixes. A cutset is treated as a set of characters to remove from a string. For example,
+-
+- strings.TrimLeft("42133word", "1234")
+-
+-will result in the string "word" – any characters that are 1, 2, 3 or 4 are cut from the left of the string.
+-
+-In order to remove one string from another, use strings.TrimPrefix instead.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1024": true}`.
+-
+-Package documentation: [SA1024](https://staticcheck.dev/docs/checks/#SA1024)
+-
+-
+-## `SA1025`: It is not possible to use (*time.Timer).Reset's return value correctly
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1025": true}`.
+-
+-Package documentation: [SA1025](https://staticcheck.dev/docs/checks/#SA1025)
+-
+-
+-## `SA1026`: Cannot marshal channels or functions
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1026": true}`.
+-
+-Package documentation: [SA1026](https://staticcheck.dev/docs/checks/#SA1026)
+-
+-
+-## `SA1027`: Atomic access to 64-bit variable must be 64-bit aligned
+-
+-On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to arrange for 64-bit alignment of 64-bit words accessed atomically. The first word in a variable or in an allocated struct, array, or slice can be relied upon to be 64-bit aligned.
+-
+-You can use the structlayout tool to inspect the alignment of fields in a struct.
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1027": true}`.
+-
+-Package documentation: [SA1027](https://staticcheck.dev/docs/checks/#SA1027)
+-
+-
+-## `SA1028`: sort.Slice can only be used on slices
+-
+-The first argument of sort.Slice must be a slice.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1028": true}`.
+-
+-Package documentation: [SA1028](https://staticcheck.dev/docs/checks/#SA1028)
+-
+-
+-## `SA1029`: Inappropriate key in call to context.WithValue
+-
+-The provided key must be comparable and should not be of type string or any other built-in type to avoid collisions between packages using context. Users of WithValue should define their own types for keys.
+-
+-To avoid allocating when assigning to an interface{}, context keys often have concrete type struct{}. Alternatively, exported context key variables' static type should be a pointer or interface.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1029": true}`.
+-
+-Package documentation: [SA1029](https://staticcheck.dev/docs/checks/#SA1029)
+-
+-
+-## `SA1030`: Invalid argument in call to a strconv function
+-
+-This check validates the format, number base and bit size arguments of the various parsing and formatting functions in strconv.
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1030": true}`.
+-
+-Package documentation: [SA1030](https://staticcheck.dev/docs/checks/#SA1030)
+-
+-
+-## `SA1031`: Overlapping byte slices passed to an encoder
+-
+-In an encoding function of the form Encode(dst, src), dst and src were found to reference the same memory. This can result in src bytes being overwritten before they are read, when the encoder writes more than one byte per src byte.
+-
+-Available since
+-
+- 2024.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1031": true}`.
+-
+-Package documentation: [SA1031](https://staticcheck.dev/docs/checks/#SA1031)
+-
+-
+-## `SA1032`: Wrong order of arguments to errors.Is
+-
+-The first argument of the function errors.Is is the error that we have and the second argument is the error we're trying to match against. For example:
+-
+- if errors.Is(err, io.EOF) { ... }
+-
+-This check detects some cases where the two arguments have been swapped. It flags any calls where the first argument is referring to a package-level error variable, such as
+-
+- if errors.Is(io.EOF, err) { /* this is wrong */ }
+-
+-Available since
+-
+- 2024.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA1032": true}`.
+-
+-Package documentation: [SA1032](https://staticcheck.dev/docs/checks/#SA1032)
+-
+-
+-## `SA2001`: Empty critical section, did you mean to defer the unlock?
+-
+-Empty critical sections of the kind
+-
+- mu.Lock()
+- mu.Unlock()
+-
+-are very often a typo, and the following was intended instead:
+-
+- mu.Lock()
+- defer mu.Unlock()
+-
+-Do note that sometimes empty critical sections can be useful, as a form of signaling to wait on another goroutine. Many times, there are simpler ways of achieving the same effect. When that isn't the case, the code should be amply commented to avoid confusion. Combining such comments with a //lint:ignore directive can be used to suppress this rare false positive.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA2001](https://staticcheck.dev/docs/checks/#SA2001)
+-
+-
+-## `SA2002`: Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA2002": true}`.
+-
+-Package documentation: [SA2002](https://staticcheck.dev/docs/checks/#SA2002)
+-
+-
+-## `SA2003`: Deferred Lock right after locking, likely meant to defer Unlock instead
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA2003": true}`.
+-
+-Package documentation: [SA2003](https://staticcheck.dev/docs/checks/#SA2003)
+-
+-
+-## `SA3000`: TestMain doesn't call os.Exit, hiding test failures
+-
+-Test executables (and in turn 'go test') exit with a non-zero status code if any tests failed. When specifying your own TestMain function, it is your responsibility to arrange for this, by calling os.Exit with the correct code. The correct code is returned by (\*testing.M).Run, so the usual way of implementing TestMain is to end it with os.Exit(m.Run()).
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA3000](https://staticcheck.dev/docs/checks/#SA3000)
+-
+-
+-## `SA3001`: Assigning to b.N in benchmarks distorts the results
+-
+-The testing package dynamically sets b.N to improve the reliability of benchmarks and uses it in computations to determine the duration of a single operation. Benchmark code must not alter b.N as this would falsify results.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA3001](https://staticcheck.dev/docs/checks/#SA3001)
+-
+-
+-## `SA4000`: Binary operator has identical expressions on both sides
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4000](https://staticcheck.dev/docs/checks/#SA4000)
+-
+-
+-## `SA4001`: &*x gets simplified to x, it does not copy x
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4001](https://staticcheck.dev/docs/checks/#SA4001)
+-
+-
+-## `SA4003`: Comparing unsigned values against negative values is pointless
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4003](https://staticcheck.dev/docs/checks/#SA4003)
+-
+-
+-## `SA4004`: The loop exits unconditionally after one iteration
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4004](https://staticcheck.dev/docs/checks/#SA4004)
+-
+-
+-## `SA4005`: Field assignment that will never be observed. Did you mean to use a pointer receiver?
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4005": true}`.
+-
+-Package documentation: [SA4005](https://staticcheck.dev/docs/checks/#SA4005)
+-
+-
+-## `SA4006`: A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4006": true}`.
+-
+-Package documentation: [SA4006](https://staticcheck.dev/docs/checks/#SA4006)
+-
+-
+-## `SA4008`: The variable in the loop condition never changes, are you incrementing the wrong variable?
+-
+-For example:
+-
+- for i := 0; i < 10; j++ { ... }
+-
+-This may also occur when a loop can only execute once because of unconditional control flow that terminates the loop. For example, when a loop body contains an unconditional break, return, or panic:
+-
+- func f() {
+- panic("oops")
+- }
+- func g() {
+- for i := 0; i < 10; i++ {
+- // f unconditionally calls panic, which means "i" is
+- // never incremented.
+- f()
+- }
+- }
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4008": true}`.
+-
+-Package documentation: [SA4008](https://staticcheck.dev/docs/checks/#SA4008)
+-
+-
+-## `SA4009`: A function argument is overwritten before its first use
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4009": true}`.
+-
+-Package documentation: [SA4009](https://staticcheck.dev/docs/checks/#SA4009)
+-
+-
+-## `SA4010`: The result of append will never be observed anywhere
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4010": true}`.
+-
+-Package documentation: [SA4010](https://staticcheck.dev/docs/checks/#SA4010)
+-
+-
+-## `SA4011`: Break statement with no effect. Did you mean to break out of an outer loop?
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4011](https://staticcheck.dev/docs/checks/#SA4011)
+-
+-
+-## `SA4012`: Comparing a value against NaN even though no value is equal to NaN
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4012": true}`.
+-
+-Package documentation: [SA4012](https://staticcheck.dev/docs/checks/#SA4012)
+-
+-
+-## `SA4013`: Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4013](https://staticcheck.dev/docs/checks/#SA4013)
+-
+-
+-## `SA4014`: An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4014](https://staticcheck.dev/docs/checks/#SA4014)
+-
+-
+-## `SA4015`: Calling functions like math.Ceil on floats converted from integers doesn't do anything useful
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4015": true}`.
+-
+-Package documentation: [SA4015](https://staticcheck.dev/docs/checks/#SA4015)
+-
+-
+-## `SA4016`: Certain bitwise operations, such as x ^ 0, do not do anything useful
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4016](https://staticcheck.dev/docs/checks/#SA4016)
+-
+-
+-## `SA4017`: Discarding the return values of a function without side effects, making the call pointless
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4017": true}`.
+-
+-Package documentation: [SA4017](https://staticcheck.dev/docs/checks/#SA4017)
+-
+-
+-## `SA4018`: Self-assignment of variables
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4018": true}`.
+-
+-Package documentation: [SA4018](https://staticcheck.dev/docs/checks/#SA4018)
+-
+-
+-## `SA4019`: Multiple, identical build constraints in the same file
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4019](https://staticcheck.dev/docs/checks/#SA4019)
+-
+-
+-## `SA4020`: Unreachable case clause in a type switch
+-
+-In a type switch like the following
+-
+- type T struct{}
+- func (T) Read(b []byte) (int, error) { return 0, nil }
+-
+- var v any = T{}
+-
+- switch v.(type) {
+- case io.Reader:
+- // ...
+- case T:
+- // unreachable
+- }
+-
+-the second case clause can never be reached because T implements io.Reader and case clauses are evaluated in source order.
+-
+-Another example:
+-
+- type T struct{}
+- func (T) Read(b []byte) (int, error) { return 0, nil }
+- func (T) Close() error { return nil }
+-
+- var v any = T{}
+-
+- switch v.(type) {
+- case io.Reader:
+- // ...
+- case io.ReadCloser:
+- // unreachable
+- }
+-
+-Even though T has a Close method and thus implements io.ReadCloser, io.Reader will always match first. The method set of io.Reader is a subset of io.ReadCloser. Thus it is impossible to match the second case without matching the first case.
+-
+-### Structurally equivalent interfaces {#hdr-Structurally_equivalent_interfaces}
+-
+-A special case of the previous example are structurally identical interfaces. Given these declarations
+-
+- type T error
+- type V error
+-
+- func doSomething() error {
+- err, ok := doAnotherThing()
+- if ok {
+- return T(err)
+- }
+-
+- return U(err)
+- }
+-
+-the following type switch will have an unreachable case clause:
+-
+- switch doSomething().(type) {
+- case T:
+- // ...
+- case V:
+- // unreachable
+- }
+-
+-T will always match before V because they are structurally equivalent and therefore doSomething()'s return value implements both.
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4020](https://staticcheck.dev/docs/checks/#SA4020)
+-
+-
+-## `SA4022`: Comparing the address of a variable against nil
+-
+-Code such as 'if &x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4022](https://staticcheck.dev/docs/checks/#SA4022)
+-
+-
+-## `SA4023`: Impossible comparison of interface value with untyped nil
+-
+-Under the covers, interfaces are implemented as two elements, a type T and a value V. V is a concrete value such as an int, struct or pointer, never an interface itself, and has type T. For instance, if we store the int value 3 in an interface, the resulting interface value has, schematically, (T=int, V=3). The value V is also known as the interface's dynamic value, since a given interface variable might hold different values V (and corresponding types T) during the execution of the program.
+-
+-An interface value is nil only if the V and T are both unset, (T=nil, V is not set), In particular, a nil interface will always hold a nil type. If we store a nil pointer of type \*int inside an interface value, the inner type will be \*int regardless of the value of the pointer: (T=\*int, V=nil). Such an interface value will therefore be non-nil even when the pointer value V inside is nil.
+-
+-This situation can be confusing, and arises when a nil value is stored inside an interface value such as an error return:
+-
+- func returnsError() error {
+- var p *MyError = nil
+- if bad() {
+- p = ErrBad
+- }
+- return p // Will always return a non-nil error.
+- }
+-
+-If all goes well, the function returns a nil p, so the return value is an error interface value holding (T=\*MyError, V=nil). This means that if the caller compares the returned error to nil, it will always look as if there was an error even if nothing bad happened. To return a proper nil error to the caller, the function must return an explicit nil:
+-
+- func returnsError() error {
+- if bad() {
+- return ErrBad
+- }
+- return nil
+- }
+-
+-It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as \*MyError, to help guarantee the error is created correctly. As an example, os.Open returns an error even though, if not nil, it's always of concrete type \*os.PathError.
+-
+-Similar situations to those described here can arise whenever interfaces are used. Just keep in mind that if any concrete value has been stored in the interface, the interface will not be nil. For more information, see The Laws of Reflection at [https://golang.org/doc/articles/laws\_of\_reflection.html](https://golang.org/doc/articles/laws_of_reflection.html).
+-
+-This text has been copied from [https://golang.org/doc/faq#nil\_error](https://golang.org/doc/faq#nil_error), licensed under the Creative Commons Attribution 3.0 License.
+-
+-Available since
+-
+- 2020.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4023": true}`.
+-
+-Package documentation: [SA4023](https://staticcheck.dev/docs/checks/#SA4023)
+-
+-
+-## `SA4024`: Checking for impossible return value from a builtin function
+-
+-Return values of the len and cap builtins cannot be negative.
+-
+-See [https://golang.org/pkg/builtin/#len](https://golang.org/pkg/builtin/#len) and [https://golang.org/pkg/builtin/#cap](https://golang.org/pkg/builtin/#cap).
+-
+-Example:
+-
+- if len(slice) < 0 {
+- fmt.Println("unreachable code")
+- }
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4024](https://staticcheck.dev/docs/checks/#SA4024)
+-
+-
+-## `SA4025`: Integer division of literals that results in zero
+-
+-When dividing two integer constants, the result will also be an integer. Thus, a division such as 2 / 3 results in 0. This is true for all of the following examples:
+-
+- _ = 2 / 3
+- const _ = 2 / 3
+- const _ float64 = 2 / 3
+- _ = float64(2 / 3)
+-
+-Staticcheck will flag such divisions if both sides of the division are integer literals, as it is highly unlikely that the division was intended to truncate to zero. Staticcheck will not flag integer division involving named constants, to avoid noisy positives.
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4025](https://staticcheck.dev/docs/checks/#SA4025)
+-
+-
+-## `SA4026`: Go constants cannot express negative zero
+-
+-In IEEE 754 floating point math, zero has a sign and can be positive or negative. This can be useful in certain numerical code.
+-
+-Go constants, however, cannot express negative zero. This means that the literals -0.0 and 0.0 have the same ideal value (zero) and will both represent positive zero at runtime.
+-
+-To explicitly and reliably create a negative zero, you can use the math.Copysign function: math.Copysign(0, -1).
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4026](https://staticcheck.dev/docs/checks/#SA4026)
+-
+-
+-## `SA4027`: (*net/url.URL).Query returns a copy, modifying it doesn't change the URL
+-
+-(\*net/url.URL).Query parses the current value of net/url.URL.RawQuery and returns it as a map of type net/url.Values. Subsequent changes to this map will not affect the URL unless the map gets encoded and assigned to the URL's RawQuery.
+-
+-As a consequence, the following code pattern is an expensive no-op: u.Query().Add(key, value).
+-
+-Available since
+-
+- 2021.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4027](https://staticcheck.dev/docs/checks/#SA4027)
+-
+-
+-## `SA4028`: x % 1 is always zero
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4028](https://staticcheck.dev/docs/checks/#SA4028)
+-
+-
+-## `SA4029`: Ineffective attempt at sorting slice
+-
+-sort.Float64Slice, sort.IntSlice, and sort.StringSlice are types, not functions. Doing x = sort.StringSlice(x) does nothing, especially not sort any values. The correct usage is sort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(), but there are more convenient helpers, namely sort.Float64s, sort.Ints, and sort.Strings.
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4029](https://staticcheck.dev/docs/checks/#SA4029)
+-
+-
+-## `SA4030`: Ineffective attempt at generating random number
+-
+-Functions in the math/rand package that accept upper limits, such as Intn, generate random numbers in the half-open interval \[0,n). In other words, the generated numbers will be >= 0 and \< n – they don't include n. rand.Intn(1) therefore doesn't generate 0 or 1, it always generates 0.
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4030](https://staticcheck.dev/docs/checks/#SA4030)
+-
+-
+-## `SA4031`: Checking never-nil value against nil
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA4031": true}`.
+-
+-Package documentation: [SA4031](https://staticcheck.dev/docs/checks/#SA4031)
+-
+-
+-## `SA4032`: Comparing runtime.GOOS or runtime.GOARCH against impossible value
+-
+-Available since
+-
+- 2024.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA4032](https://staticcheck.dev/docs/checks/#SA4032)
+-
+-
+-## `SA5000`: Assignment to nil map
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5000": true}`.
+-
+-Package documentation: [SA5000](https://staticcheck.dev/docs/checks/#SA5000)
+-
+-
+-## `SA5001`: Deferring Close before checking for a possible error
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA5001](https://staticcheck.dev/docs/checks/#SA5001)
+-
+-
+-## `SA5002`: The empty for loop ('for {}') spins and can block the scheduler
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5002": true}`.
+-
+-Package documentation: [SA5002](https://staticcheck.dev/docs/checks/#SA5002)
+-
+-
+-## `SA5003`: Defers in infinite loops will never execute
+-
+-Defers are scoped to the surrounding function, not the surrounding block. In a function that never returns, i.e. one containing an infinite loop, defers will never execute.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA5003](https://staticcheck.dev/docs/checks/#SA5003)
+-
+-
+-## `SA5004`: 'for { select { ...' with an empty default branch spins
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA5004](https://staticcheck.dev/docs/checks/#SA5004)
+-
+-
+-## `SA5005`: The finalizer references the finalized object, preventing garbage collection
+-
+-A finalizer is a function associated with an object that runs when the garbage collector is ready to collect said object, that is when the object is no longer referenced by anything.
+-
+-If the finalizer references the object, however, it will always remain as the final reference to that object, preventing the garbage collector from collecting the object. The finalizer will never run, and the object will never be collected, leading to a memory leak. That is why the finalizer should instead use its first argument to operate on the object. That way, the number of references can temporarily go to zero before the object is being passed to the finalizer.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5005": true}`.
+-
+-Package documentation: [SA5005](https://staticcheck.dev/docs/checks/#SA5005)
+-
+-
+-## `SA5007`: Infinite recursive call
+-
+-A function that calls itself recursively needs to have an exit condition. Otherwise it will recurse forever, until the system runs out of memory.
+-
+-This issue can be caused by simple bugs such as forgetting to add an exit condition. It can also happen "on purpose". Some languages have tail call optimization which makes certain infinite recursive calls safe to use. Go, however, does not implement TCO, and as such a loop should be used instead.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5007": true}`.
+-
+-Package documentation: [SA5007](https://staticcheck.dev/docs/checks/#SA5007)
+-
+-
+-## `SA5008`: Invalid struct tag
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: on.
+-
+-Package documentation: [SA5008](https://staticcheck.dev/docs/checks/#SA5008)
+-
+-
+-## `SA5010`: Impossible type assertion
+-
+-Some type assertions can be statically proven to be impossible. This is the case when the method sets of both arguments of the type assertion conflict with each other, for example by containing the same method with different signatures.
+-
+-The Go compiler already applies this check when asserting from an interface value to a concrete type. If the concrete type misses methods from the interface, or if function signatures don't match, then the type assertion can never succeed.
+-
+-This check applies the same logic when asserting from one interface to another. If both interface types contain the same method but with different signatures, then the type assertion can never succeed, either.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5010": true}`.
+-
+-Package documentation: [SA5010](https://staticcheck.dev/docs/checks/#SA5010)
+-
+-
+-## `SA5011`: Possible nil pointer dereference
+-
+-A pointer is being dereferenced unconditionally, while also being checked against nil in another place. This suggests that the pointer may be nil and dereferencing it may panic. This is commonly a result of improperly ordered code or missing return statements. Consider the following examples:
+-
+- func fn(x *int) {
+- fmt.Println(*x)
+-
+- // This nil check is equally important for the previous dereference
+- if x != nil {
+- foo(*x)
+- }
+- }
+-
+- func TestFoo(t *testing.T) {
+- x := compute()
+- if x == nil {
+- t.Errorf("nil pointer received")
+- }
+-
+- // t.Errorf does not abort the test, so if x is nil, the next line will panic.
+- foo(*x)
+- }
+-
+-Staticcheck tries to deduce which functions abort control flow. For example, it is aware that a function will not continue execution after a call to panic or log.Fatal. However, sometimes this detection fails, in particular in the presence of conditionals. Consider the following example:
+-
+- func Log(msg string, level int) {
+- fmt.Println(msg)
+- if level == levelFatal {
+- os.Exit(1)
+- }
+- }
+-
+- func Fatal(msg string) {
+- Log(msg, levelFatal)
+- }
+-
+- func fn(x *int) {
+- if x == nil {
+- Fatal("unexpected nil pointer")
+- }
+- fmt.Println(*x)
+- }
+-
+-Staticcheck will flag the dereference of x, even though it is perfectly safe. Staticcheck is not able to deduce that a call to Fatal will exit the program. For the time being, the easiest workaround is to modify the definition of Fatal like so:
+-
+- func Fatal(msg string) {
+- Log(msg, levelFatal)
+- panic("unreachable")
+- }
+-
+-We also hard-code functions from common logging packages such as logrus. Please file an issue if we're missing support for a popular package.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5011": true}`.
+-
+-Package documentation: [SA5011](https://staticcheck.dev/docs/checks/#SA5011)
+-
+-
+-## `SA5012`: Passing odd-sized slice to function expecting even size
+-
+-Some functions that take slices as parameters expect the slices to have an even number of elements. Often, these functions treat elements in a slice as pairs. For example, strings.NewReplacer takes pairs of old and new strings, and calling it with an odd number of elements would be an error.
+-
+-Available since
+-
+- 2020.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA5012": true}`.
+-
+-Package documentation: [SA5012](https://staticcheck.dev/docs/checks/#SA5012)
+-
+-
+-## `SA6000`: Using regexp.Match or related in a loop, should use regexp.Compile
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA6000": true}`.
+-
+-Package documentation: [SA6000](https://staticcheck.dev/docs/checks/#SA6000)
+-
+-
+-## `SA6001`: Missing an optimization opportunity when indexing maps by byte slices
+-
+-Map keys must be comparable, which precludes the use of byte slices. This usually leads to using string keys and converting byte slices to strings.
+-
+-Normally, a conversion of a byte slice to a string needs to copy the data and causes allocations. The compiler, however, recognizes m\[string(b)] and uses the data of b directly, without copying it, because it knows that the data can't change during the map lookup. This leads to the counter-intuitive situation that
+-
+- k := string(b)
+- println(m[k])
+- println(m[k])
+-
+-will be less efficient than
+-
+- println(m[string(b)])
+- println(m[string(b)])
+-
+-because the first version needs to copy and allocate, while the second one does not.
+-
+-For some history on this optimization, check out commit f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA6001": true}`.
+-
+-Package documentation: [SA6001](https://staticcheck.dev/docs/checks/#SA6001)
+-
+-
+-## `SA6002`: Storing non-pointer values in sync.Pool allocates memory
+-
+-A sync.Pool is used to avoid unnecessary allocations and reduce the amount of work the garbage collector has to do.
+-
+-When passing a value that is not a pointer to a function that accepts an interface, the value needs to be placed on the heap, which means an additional allocation. Slices are a common thing to put in sync.Pools, and they're structs with 3 fields (length, capacity, and a pointer to an array). In order to avoid the extra allocation, one should store a pointer to the slice instead.
+-
+-See the comments on [https://go-review.googlesource.com/c/go/+/24371](https://go-review.googlesource.com/c/go/+/24371) that discuss this problem.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA6002": true}`.
+-
+-Package documentation: [SA6002](https://staticcheck.dev/docs/checks/#SA6002)
+-
+-
+-## `SA6003`: Converting a string to a slice of runes before ranging over it
+-
+-You may want to loop over the runes in a string. Instead of converting the string to a slice of runes and looping over that, you can loop over the string itself. That is,
+-
+- for _, r := range s {}
+-
+-and
+-
+- for _, r := range []rune(s) {}
+-
+-will yield the same values. The first version, however, will be faster and avoid unnecessary memory allocations.
+-
+-Do note that if you are interested in the indices, ranging over a string and over a slice of runes will yield different indices. The first one yields byte offsets, while the second one yields indices in the slice of runes.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA6003": true}`.
+-
+-Package documentation: [SA6003](https://staticcheck.dev/docs/checks/#SA6003)
+-
+-
+-## `SA6005`: Inefficient string comparison with strings.ToLower or strings.ToUpper
+-
+-Converting two strings to the same case and comparing them like so
+-
+- if strings.ToLower(s1) == strings.ToLower(s2) {
+- ...
+- }
+-
+-is significantly more expensive than comparing them with strings.EqualFold(s1, s2). This is due to memory usage as well as computational complexity.
+-
+-strings.ToLower will have to allocate memory for the new strings, as well as convert both strings fully, even if they differ on the very first byte. strings.EqualFold, on the other hand, compares the strings one character at a time. It doesn't need to create two intermediate strings and can return as soon as the first non-matching character has been found.
+-
+-For a more in-depth explanation of this issue, see [https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/](https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/)
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: on.
+-
+-Package documentation: [SA6005](https://staticcheck.dev/docs/checks/#SA6005)
+-
+-
+-## `SA6006`: Using io.WriteString to write []byte
+-
+-Using io.WriteString to write a slice of bytes, as in
+-
+- io.WriteString(w, string(b))
+-
+-is both unnecessary and inefficient. Converting from \[]byte to string has to allocate and copy the data, and we could simply use w.Write(b) instead.
+-
+-Available since
+-
+- 2024.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA6006](https://staticcheck.dev/docs/checks/#SA6006)
+-
+-
+-## `SA9001`: Defers in range loops may not run when you expect them to
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA9001": true}`.
+-
+-Package documentation: [SA9001](https://staticcheck.dev/docs/checks/#SA9001)
+-
+-
+-## `SA9002`: Using a non-octal os.FileMode that looks like it was meant to be in octal.
+-
+-Available since
+-
+- 2017.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA9002](https://staticcheck.dev/docs/checks/#SA9002)
+-
+-
+-## `SA9003`: Empty body in an if or else branch
+-
+-Available since
+-
+- 2017.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA9003": true}`.
+-
+-Package documentation: [SA9003](https://staticcheck.dev/docs/checks/#SA9003)
+-
+-
+-## `SA9004`: Only the first constant has an explicit type
+-
+-In a constant declaration such as the following:
+-
+- const (
+- First byte = 1
+- Second = 2
+- )
+-
+-the constant Second does not have the same type as the constant First. This construct shouldn't be confused with
+-
+- const (
+- First byte = iota
+- Second
+- )
+-
+-where First and Second do indeed have the same type. The type is only passed on when no explicit value is assigned to the constant.
+-
+-When declaring enumerations with explicit values it is therefore important not to write
+-
+- const (
+- EnumFirst EnumType = 1
+- EnumSecond = 2
+- EnumThird = 3
+- )
+-
+-This discrepancy in types can cause various confusing behaviors and bugs.
+-
+-### Wrong type in variable declarations {#hdr-Wrong_type_in_variable_declarations}
+-
+-The most obvious issue with such incorrect enumerations expresses itself as a compile error:
+-
+- package pkg
+-
+- const (
+- EnumFirst uint8 = 1
+- EnumSecond = 2
+- )
+-
+- func fn(useFirst bool) {
+- x := EnumSecond
+- if useFirst {
+- x = EnumFirst
+- }
+- }
+-
+-fails to compile with
+-
+- ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
+-
+-### Losing method sets {#hdr-Losing_method_sets}
+-
+-A more subtle issue occurs with types that have methods and optional interfaces. Consider the following:
+-
+- package main
+-
+- import "fmt"
+-
+- type Enum int
+-
+- func (e Enum) String() string {
+- return "an enum"
+- }
+-
+- const (
+- EnumFirst Enum = 1
+- EnumSecond = 2
+- )
+-
+- func main() {
+- fmt.Println(EnumFirst)
+- fmt.Println(EnumSecond)
+- }
+-
+-This code will output
+-
+- an enum
+- 2
+-
+-as EnumSecond has no explicit type, and thus defaults to int.
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA9004](https://staticcheck.dev/docs/checks/#SA9004)
+-
+-
+-## `SA9005`: Trying to marshal a struct with no public fields nor custom marshaling
+-
+-The encoding/json and encoding/xml packages only operate on exported fields in structs, not unexported ones. It is usually an error to try to (un)marshal structs that only consist of unexported fields.
+-
+-This check will not flag calls involving types that define custom marshaling behavior, e.g. via MarshalJSON methods. It will also not flag empty structs.
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA9005": true}`.
+-
+-Package documentation: [SA9005](https://staticcheck.dev/docs/checks/#SA9005)
+-
+-
+-## `SA9006`: Dubious bit shifting of a fixed size integer value
+-
+-Bit shifting a value past its size will always clear the value.
+-
+-For instance:
+-
+- v := int8(42)
+- v >>= 8
+-
+-will always result in 0.
+-
+-This check flags bit shifting operations on fixed size integer values only. That is, int, uint and uintptr are never flagged to avoid potential false positives in somewhat exotic but valid bit twiddling tricks:
+-
+- // Clear any value above 32 bits if integers are more than 32 bits.
+- func f(i int) int {
+- v := i >> 32
+- v = v << 32
+- return i-v
+- }
+-
+-Available since
+-
+- 2020.2
+-
+-
+-Default: on.
+-
+-Package documentation: [SA9006](https://staticcheck.dev/docs/checks/#SA9006)
+-
+-
+-## `SA9007`: Deleting a directory that shouldn't be deleted
+-
+-It is virtually never correct to delete system directories such as /tmp or the user's home directory. However, it can be fairly easy to do by mistake, for example by mistakenly using os.TempDir instead of ioutil.TempDir, or by forgetting to add a suffix to the result of os.UserHomeDir.
+-
+-Writing
+-
+- d := os.TempDir()
+- defer os.RemoveAll(d)
+-
+-in your unit tests will have a devastating effect on the stability of your system.
+-
+-This check flags attempts at deleting the following directories:
+-
+-\- os.TempDir - os.UserCacheDir - os.UserConfigDir - os.UserHomeDir
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA9007": true}`.
+-
+-Package documentation: [SA9007](https://staticcheck.dev/docs/checks/#SA9007)
+-
+-
+-## `SA9008`: else branch of a type assertion is probably not reading the right value
+-
+-When declaring variables as part of an if statement (like in 'if foo := ...; foo {'), the same variables will also be in the scope of the else branch. This means that in the following example
+-
+- if x, ok := x.(int); ok {
+- // ...
+- } else {
+- fmt.Printf("unexpected type %T", x)
+- }
+-
+-x in the else branch will refer to the x from x, ok :=; it will not refer to the x that is being type-asserted. The result of a failed type assertion is the zero value of the type that is being asserted to, so x in the else branch will always have the value 0 and the type int.
+-
+-Available since
+-
+- 2022.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"SA9008": true}`.
+-
+-Package documentation: [SA9008](https://staticcheck.dev/docs/checks/#SA9008)
+-
+-
+-## `SA9009`: Ineffectual Go compiler directive
+-
+-A potential Go compiler directive was found, but is ineffectual as it begins with whitespace.
+-
+-Available since
+-
+- 2024.1
+-
+-
+-Default: on.
+-
+-Package documentation: [SA9009](https://staticcheck.dev/docs/checks/#SA9009)
+-
+-
+-## `ST1000`: Incorrect or missing package comment
+-
+-Packages must have a package comment that is formatted according to the guidelines laid out in [https://go.dev/wiki/CodeReviewComments#package-comments](https://go.dev/wiki/CodeReviewComments#package-comments).
+-
+-Available since
+-
+- 2019.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1000": true}`.
+-
+-Package documentation: [ST1000](https://staticcheck.dev/docs/checks/#ST1000)
+-
+-
+-## `ST1001`: Dot imports are discouraged
+-
+-Dot imports that aren't in external test packages are discouraged.
+-
+-The dot\_import\_whitelist option can be used to whitelist certain imports.
+-
+-Quoting Go Code Review Comments:
+-
+-> The import . form can be useful in tests that, due to circular > dependencies, cannot be made part of the package being tested: > > package foo\_test > > import ( > "bar/testutil" // also imports "foo" > . "foo" > ) > > In this case, the test file cannot be in package foo because it > uses bar/testutil, which imports foo. So we use the import . > form to let the file pretend to be part of package foo even though > it is not. Except for this one case, do not use import . in your > programs. It makes the programs much harder to read because it is > unclear whether a name like Quux is a top-level identifier in the > current package or in an imported package.
+-
+-Available since
+-
+- 2019.1
+-
+-Options
+-
+- dot_import_whitelist
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1001": true}`.
+-
+-Package documentation: [ST1001](https://staticcheck.dev/docs/checks/#ST1001)
+-
+-
+-## `ST1003`: Poorly chosen identifier
+-
+-Identifiers, such as variable and package names, follow certain rules.
+-
+-See the following links for details:
+-
+-\- [https://go.dev/doc/effective\_go#package-names](https://go.dev/doc/effective_go#package-names) - [https://go.dev/doc/effective\_go#mixed-caps](https://go.dev/doc/effective_go#mixed-caps) - [https://go.dev/wiki/CodeReviewComments#initialisms](https://go.dev/wiki/CodeReviewComments#initialisms) - [https://go.dev/wiki/CodeReviewComments#variable-names](https://go.dev/wiki/CodeReviewComments#variable-names)
+-
+-Available since
+-
+- 2019.1, non-default
+-
+-Options
+-
+- initialisms
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1003": true}`.
+-
+-Package documentation: [ST1003](https://staticcheck.dev/docs/checks/#ST1003)
+-
+-
+-## `ST1005`: Incorrectly formatted error string
+-
+-Error strings follow a set of guidelines to ensure uniformity and good composability.
+-
+-Quoting Go Code Review Comments:
+-
+-> Error strings should not be capitalized (unless beginning with > proper nouns or acronyms) or end with punctuation, since they are > usually printed following other context. That is, use > fmt.Errorf("something bad") not fmt.Errorf("Something bad"), so > that log.Printf("Reading %s: %v", filename, err) formats without a > spurious capital letter mid-message.
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1005": true}`.
+-
+-Package documentation: [ST1005](https://staticcheck.dev/docs/checks/#ST1005)
+-
+-
+-## `ST1006`: Poorly chosen receiver name
+-
+-Quoting Go Code Review Comments:
+-
+-> The name of a method's receiver should be a reflection of its > identity; often a one or two letter abbreviation of its type > suffices (such as "c" or "cl" for "Client"). Don't use generic > names such as "me", "this" or "self", identifiers typical of > object-oriented languages that place more emphasis on methods as > opposed to functions. The name need not be as descriptive as that > of a method argument, as its role is obvious and serves no > documentary purpose. It can be very short as it will appear on > almost every line of every method of the type; familiarity admits > brevity. Be consistent, too: if you call the receiver "c" in one > method, don't call it "cl" in another.
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1006": true}`.
+-
+-Package documentation: [ST1006](https://staticcheck.dev/docs/checks/#ST1006)
+-
+-
+-## `ST1008`: A function's error value should be its last return value
+-
+-A function's error value should be its last return value.
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1008": true}`.
+-
+-Package documentation: [ST1008](https://staticcheck.dev/docs/checks/#ST1008)
+-
+-
+-## `ST1011`: Poorly chosen name for variable of type time.Duration
+-
+-time.Duration values represent an amount of time, which is represented as a count of nanoseconds. An expression like 5 \* time.Microsecond yields the value 5000. It is therefore not appropriate to suffix a variable of type time.Duration with any time unit, such as Msec or Milli.
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1011": true}`.
+-
+-Package documentation: [ST1011](https://staticcheck.dev/docs/checks/#ST1011)
+-
+-
+-## `ST1012`: Poorly chosen name for error variable
+-
+-Error variables that are part of an API should be called errFoo or ErrFoo.
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1012": true}`.
+-
+-Package documentation: [ST1012](https://staticcheck.dev/docs/checks/#ST1012)
+-
+-
+-## `ST1013`: Should use constants for HTTP error codes, not magic numbers
+-
+-HTTP has a tremendous number of status codes. While some of those are well known (200, 400, 404, 500), most of them are not. The net/http package provides constants for all status codes that are part of the various specifications. It is recommended to use these constants instead of hard-coding magic numbers, to vastly improve the readability of your code.
+-
+-Available since
+-
+- 2019.1
+-
+-Options
+-
+- http_status_code_whitelist
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1013": true}`.
+-
+-Package documentation: [ST1013](https://staticcheck.dev/docs/checks/#ST1013)
+-
+-
+-## `ST1015`: A switch's default case should be the first or last case
+-
+-Available since
+-
+- 2019.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1015": true}`.
+-
+-Package documentation: [ST1015](https://staticcheck.dev/docs/checks/#ST1015)
+-
+-
+-## `ST1016`: Use consistent method receiver names
+-
+-Available since
+-
+- 2019.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1016": true}`.
+-
+-Package documentation: [ST1016](https://staticcheck.dev/docs/checks/#ST1016)
+-
+-
+-## `ST1017`: Don't use Yoda conditions
+-
+-Yoda conditions are conditions of the kind 'if 42 == x', where the literal is on the left side of the comparison. These are a common idiom in languages in which assignment is an expression, to avoid bugs of the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of bug, we prefer the more idiomatic 'if x == 42'.
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1017": true}`.
+-
+-Package documentation: [ST1017](https://staticcheck.dev/docs/checks/#ST1017)
+-
+-
+-## `ST1018`: Avoid zero-width and control characters in string literals
+-
+-Available since
+-
+- 2019.2
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1018": true}`.
+-
+-Package documentation: [ST1018](https://staticcheck.dev/docs/checks/#ST1018)
+-
+-
+-## `ST1019`: Importing the same package multiple times
+-
+-Go allows importing the same package multiple times, as long as different import aliases are being used. That is, the following bit of code is valid:
+-
+- import (
+- "fmt"
+- fumpt "fmt"
+- format "fmt"
+- _ "fmt"
+- )
+-
+-However, this is very rarely done on purpose. Usually, it is a sign of code that got refactored, accidentally adding duplicate import statements. It is also a rarely known feature, which may contribute to confusion.
+-
+-Do note that sometimes, this feature may be used intentionally (see for example [https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d](https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)) – if you want to allow this pattern in your code base, you're advised to disable this check.
+-
+-Available since
+-
+- 2020.1
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1019": true}`.
+-
+-Package documentation: [ST1019](https://staticcheck.dev/docs/checks/#ST1019)
+-
+-
+-## `ST1020`: The documentation of an exported function should start with the function's name
+-
+-Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared.
+-
+-If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep.
+-
+-See [https://go.dev/doc/effective\_go#commentary](https://go.dev/doc/effective_go#commentary) for more information on how to write good documentation.
+-
+-Available since
+-
+- 2020.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1020": true}`.
+-
+-Package documentation: [ST1020](https://staticcheck.dev/docs/checks/#ST1020)
+-
+-
+-## `ST1021`: The documentation of an exported type should start with type's name
+-
+-Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared.
+-
+-If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep.
+-
+-See [https://go.dev/doc/effective\_go#commentary](https://go.dev/doc/effective_go#commentary) for more information on how to write good documentation.
+-
+-Available since
+-
+- 2020.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1021": true}`.
+-
+-Package documentation: [ST1021](https://staticcheck.dev/docs/checks/#ST1021)
+-
+-
+-## `ST1022`: The documentation of an exported variable or constant should start with variable's name
+-
+-Doc comments work best as complete sentences, which allow a wide variety of automated presentations. The first sentence should be a one-sentence summary that starts with the name being declared.
+-
+-If every doc comment begins with the name of the item it describes, you can use the doc subcommand of the go tool and run the output through grep.
+-
+-See [https://go.dev/doc/effective\_go#commentary](https://go.dev/doc/effective_go#commentary) for more information on how to write good documentation.
+-
+-Available since
+-
+- 2020.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1022": true}`.
+-
+-Package documentation: [ST1022](https://staticcheck.dev/docs/checks/#ST1022)
+-
+-
+-## `ST1023`: Redundant type in variable declaration
+-
+-Available since
+-
+- 2021.1, non-default
+-
+-
+-Default: off. Enable by setting `"analyses": {"ST1023": true}`.
+-
+-Package documentation: [ST1023](https://staticcheck.dev/docs/checks/#)
+-
+-
+-## `any`: replace interface{} with any
+-
+-The any analyzer suggests replacing uses of the empty interface type, \`interface{}\`, with the \`any\` alias, which was introduced in Go 1.18. This is a purely stylistic change that makes code more readable.
+-
+-
+-Default: on.
+-
+-Package documentation: [any](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any)
+-
+-
+-## `appendclipped`: simplify append chains using slices.Concat
+-
+-The appendclipped analyzer suggests replacing chains of append calls with a single call to slices.Concat, which was added in Go 1.21. For example, append(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2).
+-
+-In the simple case of appending to a newly allocated slice, such as append(\[]T(nil), s...), the analyzer suggests the more concise slices.Clone(s). For byte slices, it will prefer bytes.Clone if the "bytes" package is already imported.
+-
+-This fix is only applied when the base of the append tower is a "clipped" slice, meaning its length and capacity are equal (e.g. x\[:0:0] or \[]T{}). This is to avoid changing program behavior by eliminating intended side effects on the base slice's underlying array.
+-
+-This analyzer is currently disabled by default as the transformation does not preserve the nilness of the base slice in all cases; see [https://go.dev/issue/73557](https://go.dev/issue/73557).
+-
+-
+-Default: off. Enable by setting `"analyses": {"appendclipped": true}`.
+-
+-Package documentation: [appendclipped](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped)
+-
+-
+-## `appends`: check for missing values after append
+-
+-This checker reports calls to append that pass no values to be appended to the slice.
+-
+- s := []string{"a", "b", "c"}
+- _ = append(s)
+-
+-Such calls are always no-ops and often indicate an underlying mistake.
+-
+-
+-Default: on.
+-
+-Package documentation: [appends](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends)
+-
+-
+-## `asmdecl`: report mismatches between assembly files and Go declarations
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [asmdecl](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/asmdecl)
+-
+-
+-## `assign`: check for useless assignments
+-
+-This checker reports assignments of the form x = x or a\[i] = a\[i]. These are almost always useless, and even when they aren't they are usually a mistake.
+-
+-
+-Default: on.
+-
+-Package documentation: [assign](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign)
+-
+-
+-## `atomic`: check for common mistakes using the sync/atomic package
+-
+-The atomic checker looks for assignment statements of the form:
+-
+- x = atomic.AddUint64(&x, 1)
+-
+-which are not atomic.
+-
+-
+-Default: on.
+-
+-Package documentation: [atomic](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic)
+-
+-
+-## `atomicalign`: check for non-64-bits-aligned arguments to sync/atomic functions
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [atomicalign](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomicalign)
+-
+-
+-## `bloop`: replace for-range over b.N with b.Loop
+-
+-The bloop analyzer suggests replacing benchmark loops of the form \`for i := 0; i \< b.N; i++\` or \`for range b.N\` with the more modern \`for b.Loop()\`, which was added in Go 1.24.
+-
+-This change makes benchmark code more readable and also removes the need for manual timer control, so any preceding calls to b.StartTimer, b.StopTimer, or b.ResetTimer within the same function will also be removed.
+-
+-Caveats: The b.Loop() method is designed to prevent the compiler from optimizing away the benchmark loop, which can occasionally result in slower execution due to increased allocations in some specific cases.
+-
+-
+-Default: on.
+-
+-Package documentation: [bloop](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#bloop)
+-
+-
+-## `bools`: check for common mistakes involving boolean operators
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [bools](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools)
+-
+-
+-## `buildtag`: check //go:build and // +build directives
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [buildtag](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildtag)
+-
+-
+-## `cgocall`: detect some violations of the cgo pointer passing rules
+-
+-Check for invalid cgo pointer passing. This looks for code that uses cgo to call C code passing values whose types are almost always invalid according to the cgo pointer sharing rules. Specifically, it warns about attempts to pass a Go chan, map, func, or slice to C, either directly, or via a pointer, array, or struct.
+-
+-
+-Default: on.
+-
+-Package documentation: [cgocall](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall)
+-
+-
+-## `composites`: check for unkeyed composite literals
+-
+-This analyzer reports a diagnostic for composite literals of struct types imported from another package that do not use the field-keyed syntax. Such literals are fragile because the addition of a new field (even if unexported) to the struct will cause compilation to fail.
+-
+-As an example,
+-
+- err = &net.DNSConfigError{err}
+-
+-should be replaced by:
+-
+- err = &net.DNSConfigError{Err: err}
+-
+-
+-Default: on.
+-
+-Package documentation: [composites](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite)
+-
+-
+-## `copylocks`: check for locks erroneously passed by value
+-
+-Inadvertently copying a value containing a lock, such as sync.Mutex or sync.WaitGroup, may cause both copies to malfunction. Generally such values should be referred to through a pointer.
+-
+-
+-Default: on.
+-
+-Package documentation: [copylocks](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylock)
+-
+-
+-## `deepequalerrors`: check for calls of reflect.DeepEqual on error values
+-
+-The deepequalerrors checker looks for calls of the form:
+-
+- reflect.DeepEqual(err1, err2)
+-
+-where err1 and err2 are errors. Using reflect.DeepEqual to compare errors is discouraged.
+-
+-
+-Default: on.
+-
+-Package documentation: [deepequalerrors](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors)
+-
+-
+-## `defers`: report common mistakes in defer statements
+-
+-The defers analyzer reports a diagnostic when a defer statement would result in a non-deferred call to time.Since, as experience has shown that this is nearly always a mistake.
+-
+-For example:
+-
+- start := time.Now()
+- ...
+- defer recordLatency(time.Since(start)) // error: call to time.Since is not deferred
+-
+-The correct code is:
+-
+- defer func() { recordLatency(time.Since(start)) }()
+-
+-
+-Default: on.
+-
+-Package documentation: [defers](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers)
+-
+-
+-## `deprecated`: check for use of deprecated identifiers
+-
+-The deprecated analyzer looks for deprecated symbols and package imports.
+-
+-See [https://go.dev/wiki/Deprecated](https://go.dev/wiki/Deprecated) to learn about Go's convention for documenting and signaling deprecated identifiers.
+-
+-
+-Default: on.
+-
+-Package documentation: [deprecated](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated)
+-
+-
+-## `directive`: check Go toolchain directives such as //go:debug
+-
+-This analyzer checks for problems with known Go toolchain directives in all Go source files in a package directory, even those excluded by //go:build constraints, and all non-Go source files too.
+-
+-For //go:debug (see [https://go.dev/doc/godebug](https://go.dev/doc/godebug)), the analyzer checks that the directives are placed only in Go source files, only above the package comment, and only in package main or \*\_test.go files.
+-
+-Support for other known directives may be added in the future.
+-
+-This analyzer does not check //go:build, which is handled by the buildtag analyzer.
+-
+-
+-Default: on.
+-
+-Package documentation: [directive](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/directive)
+-
+-
+-## `embed`: check //go:embed directive usage
+-
+-This analyzer checks that the embed package is imported if //go:embed directives are present, providing a suggested fix to add the import if it is missing.
+-
+-This analyzer also checks that //go:embed directives precede the declaration of a single variable.
+-
+-
+-Default: on.
+-
+-Package documentation: [embed](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective)
+-
+-
+-## `errorsas`: report passing non-pointer or non-error values to errors.As
+-
+-The errorsas analyzer reports calls to errors.As where the type of the second argument is not a pointer to a type implementing error.
+-
+-
+-Default: on.
+-
+-Package documentation: [errorsas](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas)
+-
+-
+-## `errorsastype`: replace errors.As with errors.AsType[T]
+-
+-This analyzer suggests fixes to simplify uses of [errors.As](/errors#As) of this form:
+-
+- var myerr *MyErr
+- if errors.As(err, &myerr) {
+- handle(myerr)
+- }
+-
+-by using the less error-prone generic [errors.AsType](/errors#AsType) function, introduced in Go 1.26:
+-
+- if myerr, ok := errors.AsType[*MyErr](err); ok {
+- handle(myerr)
+- }
+-
+-The fix is only offered if the var declaration has the form shown and there are no uses of myerr outside the if statement.
+-
+-
+-Default: on.
+-
+-Package documentation: [errorsastype](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype)
+-
+-
+-## `fillreturns`: suggest fixes for errors due to an incorrect number of return values
+-
+-This checker provides suggested fixes for type errors of the type "wrong number of return values (want %d, got %d)". For example:
+-
+- func m() (int, string, *bool, error) {
+- return
+- }
+-
+-will turn into
+-
+- func m() (int, string, *bool, error) {
+- return 0, "", nil, nil
+- }
+-
+-This functionality is similar to [https://github.com/sqs/goreturns](https://github.com/sqs/goreturns).
+-
+-
+-Default: on.
+-
+-Package documentation: [fillreturns](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns)
+-
+-
+-## `fmtappendf`: replace []byte(fmt.Sprintf) with fmt.Appendf
+-
+-The fmtappendf analyzer suggests replacing \`\[]byte(fmt.Sprintf(...))\` with \`fmt.Appendf(nil, ...)\`. This avoids the intermediate allocation of a string by Sprintf, making the code more efficient. The suggestion also applies to fmt.Sprint and fmt.Sprintln.
+-
+-
+-Default: on.
+-
+-Package documentation: [fmtappendf](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#fmtappendf)
+-
+-
+-## `forvar`: remove redundant re-declaration of loop variables
+-
+-The forvar analyzer removes unnecessary shadowing of loop variables. Before Go 1.22, it was common to write \`for \_, x := range s { x := x ... }\` to create a fresh variable for each iteration. Go 1.22 changed the semantics of \`for\` loops, making this pattern redundant. This analyzer removes the unnecessary \`x := x\` statement.
+-
+-This fix only applies to \`range\` loops.
+-
+-
+-Default: on.
+-
+-Package documentation: [forvar](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar)
+-
+-
+-## `framepointer`: report assembly that clobbers the frame pointer before saving it
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [framepointer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer)
+-
+-
+-## `hostport`: check format of addresses passed to net.Dial
+-
+-This analyzer flags code that produce network address strings using fmt.Sprintf, as in this example:
+-
+- addr := fmt.Sprintf("%s:%d", host, 12345) // "will not work with IPv6"
+- ...
+- conn, err := net.Dial("tcp", addr) // "when passed to dial here"
+-
+-The analyzer suggests a fix to use the correct approach, a call to net.JoinHostPort:
+-
+- addr := net.JoinHostPort(host, "12345")
+- ...
+- conn, err := net.Dial("tcp", addr)
+-
+-A similar diagnostic and fix are produced for a format string of "%s:%s".
+-
+-
+-Default: on.
+-
+-Package documentation: [hostport](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/hostport)
+-
+-
+-## `httpresponse`: check for mistakes using HTTP responses
+-
+-A common mistake when using the net/http package is to defer a function call to close the http.Response Body before checking the error that determines whether the response is valid:
+-
+- resp, err := http.Head(url)
+- defer resp.Body.Close()
+- if err != nil {
+- log.Fatal(err)
+- }
+- // (defer statement belongs here)
+-
+-This checker helps uncover latent nil dereference bugs by reporting a diagnostic for such mistakes.
+-
+-
+-Default: on.
+-
+-Package documentation: [httpresponse](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse)
+-
+-
+-## `ifaceassert`: detect impossible interface-to-interface type assertions
+-
+-This checker flags type assertions v.(T) and corresponding type-switch cases in which the static type V of v is an interface that cannot possibly implement the target interface T. This occurs when V and T contain methods with the same name but different signatures. Example:
+-
+- var v interface {
+- Read()
+- }
+- _ = v.(io.Reader)
+-
+-The Read method in v has a different signature than the Read method in io.Reader, so this assertion cannot succeed.
+-
+-
+-Default: on.
+-
+-Package documentation: [ifaceassert](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert)
+-
+-
+-## `infertypeargs`: check for unnecessary type arguments in call expressions
+-
+-Explicit type arguments may be omitted from call expressions if they can be inferred from function arguments, or from other type arguments:
+-
+- func f[T any](T) {}
+-
+- func _() {
+- f[string]("foo") // string could be inferred
+- }
+-
+-
+-Default: on.
+-
+-Package documentation: [infertypeargs](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs)
+-
+-
+-## `inline`: apply fixes based on 'go:fix inline' comment directives
+-
+-The inline analyzer inlines functions and constants that are marked for inlining.
+-
+-\## Functions
+-
+-Given a function that is marked for inlining, like this one:
+-
+- //go:fix inline
+- func Square(x int) int { return Pow(x, 2) }
+-
+-this analyzer will recommend that calls to the function elsewhere, in the same or other packages, should be inlined.
+-
+-Inlining can be used to move off of a deprecated function:
+-
+- // Deprecated: prefer Pow(x, 2).
+- //go:fix inline
+- func Square(x int) int { return Pow(x, 2) }
+-
+-It can also be used to move off of an obsolete package, as when the import path has changed or a higher major version is available:
+-
+- package pkg
+-
+- import pkg2 "pkg/v2"
+-
+- //go:fix inline
+- func F() { pkg2.F(nil) }
+-
+-Replacing a call pkg.F() by pkg2.F(nil) can have no effect on the program, so this mechanism provides a low-risk way to update large numbers of calls. We recommend, where possible, expressing the old API in terms of the new one to enable automatic migration.
+-
+-The inliner takes care to avoid behavior changes, even subtle ones, such as changes to the order in which argument expressions are evaluated. When it cannot safely eliminate all parameter variables, it may introduce a "binding declaration" of the form
+-
+- var params = args
+-
+-to evaluate argument expressions in the correct order and bind them to parameter variables. Since the resulting code transformation may be stylistically suboptimal, such inlinings may be disabled by specifying the -inline.allow\_binding\_decl=false flag to the analyzer driver.
+-
+-(In cases where it is not safe to "reduce" a call—that is, to replace a call f(x) by the body of function f, suitably substituted—the inliner machinery is capable of replacing f by a function literal, func(){...}(). However, the inline analyzer discards all such "literalizations" unconditionally, again on grounds of style.)
+-
+-\## Constants
+-
+-Given a constant that is marked for inlining, like this one:
+-
+- //go:fix inline
+- const Ptr = Pointer
+-
+-this analyzer will recommend that uses of Ptr should be replaced with Pointer.
+-
+-As with functions, inlining can be used to replace deprecated constants and constants in obsolete packages.
+-
+-A constant definition can be marked for inlining only if it refers to another named constant.
+-
+-The "//go:fix inline" comment must appear before a single const declaration on its own, as above; before a const declaration that is part of a group, as in this case:
+-
+- const (
+- C = 1
+- //go:fix inline
+- Ptr = Pointer
+- )
+-
+-or before a group, applying to every constant in the group:
+-
+- //go:fix inline
+- const (
+- Ptr = Pointer
+- Val = Value
+- )
+-
+-The proposal [https://go.dev/issue/32816](https://go.dev/issue/32816) introduces the "//go:fix inline" directives.
+-
+-You can use this command to apply inline fixes en masse:
+-
+- $ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./...
+-
+-
+-Default: on.
+-
+-Package documentation: [inline](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline)
+-
+-
+-## `loopclosure`: check references to loop variables from within nested functions
+-
+-This analyzer reports places where a function literal references the iteration variable of an enclosing loop, and the loop calls the function in such a way (e.g. with go or defer) that it may outlive the loop iteration and possibly observe the wrong value of the variable.
+-
+-Note: An iteration variable can only outlive a loop iteration in Go versions \<=1.21. In Go 1.22 and later, the loop variable lifetimes changed to create a new iteration variable per loop iteration. (See go.dev/issue/60078.)
+-
+-In this example, all the deferred functions run after the loop has completed, so all observe the final value of v \[\
+-## `lostcancel`: check cancel func returned by context.WithCancel is called
+-
+-The cancellation function returned by context.WithCancel, WithTimeout, WithDeadline and variants such as WithCancelCause must be called, or the new context will remain live until its parent context is cancelled. (The background context is never cancelled.)
+-
+-
+-Default: on.
+-
+-Package documentation: [lostcancel](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel)
+-
+-
+-## `maprange`: checks for unnecessary calls to maps.Keys and maps.Values in range statements
+-
+-Consider a loop written like this:
+-
+- for val := range maps.Values(m) {
+- fmt.Println(val)
+- }
+-
+-This should instead be written without the call to maps.Values:
+-
+- for _, val := range m {
+- fmt.Println(val)
+- }
+-
+-golang.org/x/exp/maps returns slices for Keys/Values instead of iterators, but unnecessary calls should similarly be removed:
+-
+- for _, key := range maps.Keys(m) {
+- fmt.Println(key)
+- }
+-
+-should be rewritten as:
+-
+- for key := range m {
+- fmt.Println(key)
+- }
+-
+-
+-Default: on.
+-
+-Package documentation: [maprange](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/maprange)
+-
+-
+-## `mapsloop`: replace explicit loops over maps with calls to maps package
+-
+-The mapsloop analyzer replaces loops of the form
+-
+- for k, v := range x { m[k] = v }
+-
+-with a single call to a function from the \`maps\` package, added in Go 1.23. Depending on the context, this could be \`maps.Copy\`, \`maps.Insert\`, \`maps.Clone\`, or \`maps.Collect\`.
+-
+-The transformation to \`maps.Clone\` is applied conservatively, as it preserves the nilness of the source map, which may be a subtle change in behavior if the original code did not handle a nil map in the same way.
+-
+-
+-Default: on.
+-
+-Package documentation: [mapsloop](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop)
+-
+-
+-## `minmax`: replace if/else statements with calls to min or max
+-
+-The minmax analyzer simplifies conditional assignments by suggesting the use of the built-in \`min\` and \`max\` functions, introduced in Go 1.21. For example,
+-
+- if a < b { x = a } else { x = b }
+-
+-is replaced by
+-
+- x = min(a, b).
+-
+-This analyzer avoids making suggestions for floating-point types, as the behavior of \`min\` and \`max\` with NaN values can differ from the original if/else statement.
+-
+-
+-Default: on.
+-
+-Package documentation: [minmax](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#minmax)
+-
+-
+-## `newexpr`: simplify code by using go1.26's new(expr)
+-
+-This analyzer finds declarations of functions of this form:
+-
+- func varOf(x int) *int { return &x }
+-
+-and suggests a fix to turn them into inlinable wrappers around go1.26's built-in new(expr) function:
+-
+- func varOf(x int) *int { return new(x) }
+-
+-In addition, this analyzer suggests a fix for each call to one of the functions before it is transformed, so that
+-
+- use(varOf(123))
+-
+-is replaced by:
+-
+- use(new(123))
+-
+-(Wrapper functions such as varOf are common when working with Go serialization packages such as for JSON or protobuf, where pointers are often used to express optionality.)
+-
+-
+-Default: on.
+-
+-Package documentation: [newexpr](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr)
+-
+-
+-## `nilfunc`: check for useless comparisons between functions and nil
+-
+-A useless comparison is one like f == nil as opposed to f() == nil.
+-
+-
+-Default: on.
+-
+-Package documentation: [nilfunc](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc)
+-
+-
+-## `nilness`: check for redundant or impossible nil comparisons
+-
+-The nilness checker inspects the control-flow graph of each function in a package and reports nil pointer dereferences, degenerate nil pointers, and panics with nil values. A degenerate comparison is of the form x==nil or x!=nil where x is statically known to be nil or non-nil. These are often a mistake, especially in control flow related to errors. Panics with nil values are checked because they are not detectable by
+-
+- if r := recover(); r != nil {
+-
+-This check reports conditions such as:
+-
+- if f == nil { // impossible condition (f is a function)
+- }
+-
+-and:
+-
+- p := &v
+- ...
+- if p != nil { // tautological condition
+- }
+-
+-and:
+-
+- if p == nil {
+- print(*p) // nil dereference
+- }
+-
+-and:
+-
+- if p == nil {
+- panic(p)
+- }
+-
+-Sometimes the control flow may be quite complex, making bugs hard to spot. In the example below, the err.Error expression is guaranteed to panic because, after the first return, err must be nil. The intervening loop is just a distraction.
+-
+- ...
+- err := g.Wait()
+- if err != nil {
+- return err
+- }
+- partialSuccess := false
+- for _, err := range errs {
+- if err == nil {
+- partialSuccess = true
+- break
+- }
+- }
+- if partialSuccess {
+- reportStatus(StatusMessage{
+- Code: code.ERROR,
+- Detail: err.Error(), // "nil dereference in dynamic method call"
+- })
+- return nil
+- }
+-
+-...
+-
+-
+-Default: on.
+-
+-Package documentation: [nilness](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness)
+-
+-
+-## `nonewvars`: suggested fixes for "no new vars on left side of :="
+-
+-This checker provides suggested fixes for type errors of the type "no new vars on left side of :=". For example:
+-
+- z := 1
+- z := 2
+-
+-will turn into
+-
+- z := 1
+- z = 2
+-
+-
+-Default: on.
+-
+-Package documentation: [nonewvars](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars)
+-
+-
+-## `noresultvalues`: suggested fixes for unexpected return values
+-
+-This checker provides suggested fixes for type errors of the type "no result values expected" or "too many return values". For example:
+-
+- func z() { return nil }
+-
+-will turn into
+-
+- func z() { return }
+-
+-
+-Default: on.
+-
+-Package documentation: [noresultvalues](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues)
+-
+-
+-## `omitzero`: suggest replacing omitempty with omitzero for struct fields
+-
+-The omitzero analyzer identifies uses of the \`omitempty\` JSON struct tag on fields that are themselves structs. The \`omitempty\` tag has no effect on struct-typed fields. The analyzer offers two suggestions: either remove the tag, or replace it with \`omitzero\` (added in Go 1.24), which correctly omits the field if the struct value is zero.
+-
+-Replacing \`omitempty\` with \`omitzero\` is a change in behavior. The original code would always encode the struct field, whereas the modified code will omit it if it is a zero-value.
+-
+-
+-Default: on.
+-
+-Package documentation: [omitzero](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero)
+-
+-
+-## `printf`: check consistency of Printf format strings and arguments
+-
+-The check applies to calls of the formatting functions such as [fmt.Printf](/fmt#Printf) and [fmt.Sprintf](/fmt#Sprintf), as well as any detected wrappers of those functions such as [log.Printf](/log#Printf). It reports a variety of mistakes such as syntax errors in the format string and mismatches (of number and type) between the verbs and their arguments.
+-
+-See the documentation of the fmt package for the complete set of format operators and their operand types.
+-
+-
+-Default: on.
+-
+-Package documentation: [printf](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf)
+-
+-
+-## `rangeint`: replace 3-clause for loops with for-range over integers
+-
+-The rangeint analyzer suggests replacing traditional for loops such as
+-
+- for i := 0; i < n; i++ { ... }
+-
+-with the more idiomatic Go 1.22 style:
+-
+- for i := range n { ... }
+-
+-This transformation is applied only if (a) the loop variable is not modified within the loop body and (b) the loop's limit expression is not modified within the loop, as \`for range\` evaluates its operand only once.
+-
+-
+-Default: on.
+-
+-Package documentation: [rangeint](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#rangeint)
+-
+-
+-## `recursiveiter`: check for inefficient recursive iterators
+-
+-This analyzer reports when a function that returns an iterator (iter.Seq or iter.Seq2) calls itself as the operand of a range statement, as this is inefficient.
+-
+-When implementing an iterator (e.g. iter.Seq\[T]) for a recursive data type such as a tree or linked list, it is tempting to recursively range over the iterator for each child element.
+-
+-Here's an example of a naive iterator over a binary tree:
+-
+- type tree struct {
+- value int
+- left, right *tree
+- }
+-
+- func (t *tree) All() iter.Seq[int] {
+- return func(yield func(int) bool) {
+- if t != nil {
+- for elem := range t.left.All() { // "inefficient recursive iterator"
+- if !yield(elem) {
+- return
+- }
+- }
+- if !yield(t.value) {
+- return
+- }
+- for elem := range t.right.All() { // "inefficient recursive iterator"
+- if !yield(elem) {
+- return
+- }
+- }
+- }
+- }
+- }
+-
+-Though it correctly enumerates the elements of the tree, it hides a significant performance problem--two, in fact. Consider a balanced tree of N nodes. Iterating the root node will cause All to be called once on every node of the tree. This results in a chain of nested active range-over-func statements when yield(t.value) is called on a leaf node.
+-
+-The first performance problem is that each range-over-func statement must typically heap-allocate a variable, so iteration of the tree allocates as many variables as there are elements in the tree, for a total of O(N) allocations, all unnecessary.
+-
+-The second problem is that each call to yield for a leaf of the tree causes each of the enclosing range loops to receive a value, which they then immediately pass on to their respective yield function. This results in a chain of log(N) dynamic yield calls per element, a total of O(N\*log N) dynamic calls overall, when only O(N) are necessary.
+-
+-A better implementation strategy for recursive iterators is to first define the "every" operator for your recursive data type, where every(f) reports whether an arbitrary predicate f(x) is true for every element x in the data type. For our tree, the every function would be:
+-
+- func (t *tree) every(f func(int) bool) bool {
+- return t == nil ||
+- t.left.every(f) && f(t.value) && t.right.every(f)
+- }
+-
+-For example, this use of the every operator prints whether every element in the tree is an even number:
+-
+- even := func(x int) bool { return x&1 == 0 }
+- println(t.every(even))
+-
+-Then the iterator can be simply expressed as a trivial wrapper around the every operator:
+-
+- func (t *tree) All() iter.Seq[int] {
+- return func(yield func(int) bool) {
+- _ = t.every(yield)
+- }
+- }
+-
+-In effect, tree.All computes whether yield returns true for each element, short-circuiting if it ever returns false, then discards the final boolean result.
+-
+-This has much better performance characteristics: it makes one dynamic call per element of the tree, and it doesn't heap-allocate anything. It is also clearer.
+-
+-
+-Default: on.
+-
+-Package documentation: [recursiveiter](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/recursiveiter)
+-
+-
+-## `reflecttypefor`: replace reflect.TypeOf(x) with TypeFor[T]()
+-
+-This analyzer suggests fixes to replace uses of reflect.TypeOf(x) with reflect.TypeFor, introduced in go1.22, when the desired runtime type is known at compile time, for example:
+-
+- reflect.TypeOf(uint32(0)) -> reflect.TypeFor[uint32]()
+- reflect.TypeOf((*ast.File)(nil)) -> reflect.TypeFor[*ast.File]()
+-
+-It also offers a fix to simplify the construction below, which uses reflect.TypeOf to return the runtime type for an interface type,
+-
+- reflect.TypeOf((*io.Reader)(nil)).Elem()
+-
+-to:
+-
+- reflect.TypeFor[io.Reader]()
+-
+-No fix is offered in cases when the runtime type is dynamic, such as:
+-
+- var r io.Reader = ...
+- reflect.TypeOf(r)
+-
+-or when the operand has potential side effects.
+-
+-
+-Default: on.
+-
+-Package documentation: [reflecttypefor](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#reflecttypefor)
+-
+-
+-## `shadow`: check for possible unintended shadowing of variables
+-
+-This analyzer check for shadowed variables. A shadowed variable is a variable declared in an inner scope with the same name and type as a variable in an outer scope, and where the outer variable is mentioned after the inner one is declared.
+-
+-(This definition can be refined; the module generates too many false positives and is not yet enabled by default.)
+-
+-For example:
+-
+- func BadRead(f *os.File, buf []byte) error {
+- var err error
+- for {
+- n, err := f.Read(buf) // shadows the function variable 'err'
+- if err != nil {
+- break // causes return of wrong value
+- }
+- foo(buf)
+- }
+- return err
+- }
+-
+-
+-Default: off. Enable by setting `"analyses": {"shadow": true}`.
+-
+-Package documentation: [shadow](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow)
+-
+-
+-## `shift`: check for shifts that equal or exceed the width of the integer
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [shift](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shift)
+-
+-
+-## `sigchanyzer`: check for unbuffered channel of os.Signal
+-
+-This checker reports call expression of the form
+-
+- signal.Notify(c <-chan os.Signal, sig ...os.Signal),
+-
+-where c is an unbuffered channel, which can be at risk of missing the signal.
+-
+-
+-Default: on.
+-
+-Package documentation: [sigchanyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer)
+-
+-
+-## `simplifycompositelit`: check for composite literal simplifications
+-
+-An array, slice, or map composite literal of the form:
+-
+- []T{T{}, T{}}
+-
+-will be simplified to:
+-
+- []T{{}, {}}
+-
+-This is one of the simplifications that "gofmt -s" applies.
+-
+-This analyzer ignores generated code.
+-
+-
+-Default: on.
+-
+-Package documentation: [simplifycompositelit](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit)
+-
+-
+-## `simplifyrange`: check for range statement simplifications
+-
+-A range of the form:
+-
+- for x, _ = range v {...}
+-
+-will be simplified to:
+-
+- for x = range v {...}
+-
+-A range of the form:
+-
+- for _ = range v {...}
+-
+-will be simplified to:
+-
+- for range v {...}
+-
+-This is one of the simplifications that "gofmt -s" applies.
+-
+-This analyzer ignores generated code.
+-
+-
+-Default: on.
+-
+-Package documentation: [simplifyrange](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange)
+-
+-
+-## `simplifyslice`: check for slice simplifications
+-
+-A slice expression of the form:
+-
+- s[a:len(s)]
+-
+-will be simplified to:
+-
+- s[a:]
+-
+-This is one of the simplifications that "gofmt -s" applies.
+-
+-This analyzer ignores generated code.
+-
+-
+-Default: on.
+-
+-Package documentation: [simplifyslice](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice)
+-
+-
+-## `slicescontains`: replace loops with slices.Contains or slices.ContainsFunc
+-
+-The slicescontains analyzer simplifies loops that check for the existence of an element in a slice. It replaces them with calls to \`slices.Contains\` or \`slices.ContainsFunc\`, which were added in Go 1.21.
+-
+-If the expression for the target element has side effects, this transformation will cause those effects to occur only once, not once per tested slice element.
+-
+-
+-Default: on.
+-
+-Package documentation: [slicescontains](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicescontains)
+-
+-
+-## `slicesdelete`: replace append-based slice deletion with slices.Delete
+-
+-The slicesdelete analyzer suggests replacing the idiom
+-
+- s = append(s[:i], s[j:]...)
+-
+-with the more explicit
+-
+- s = slices.Delete(s, i, j)
+-
+-introduced in Go 1.21.
+-
+-This analyzer is disabled by default. The \`slices.Delete\` function zeros the elements between the new length and the old length of the slice to prevent memory leaks, which is a subtle difference in behavior compared to the append-based idiom; see [https://go.dev/issue/73686](https://go.dev/issue/73686).
+-
+-
+-Default: off. Enable by setting `"analyses": {"slicesdelete": true}`.
+-
+-Package documentation: [slicesdelete](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete)
+-
+-
+-## `slicessort`: replace sort.Slice with slices.Sort for basic types
+-
+-The slicessort analyzer simplifies sorting slices of basic ordered types. It replaces
+-
+- sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
+-
+-with the simpler \`slices.Sort(s)\`, which was added in Go 1.21.
+-
+-
+-Default: on.
+-
+-Package documentation: [slicessort](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicessort)
+-
+-
+-## `slog`: check for invalid structured logging calls
+-
+-The slog checker looks for calls to functions from the log/slog package that take alternating key-value pairs. It reports calls where an argument in a key position is neither a string nor a slog.Attr, and where a final key is missing its value. For example,it would report
+-
+- slog.Warn("message", 11, "k") // slog.Warn arg "11" should be a string or a slog.Attr
+-
+-and
+-
+- slog.Info("message", "k1", v1, "k2") // call to slog.Info missing a final value
+-
+-
+-Default: on.
+-
+-Package documentation: [slog](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog)
+-
+-
+-## `sortslice`: check the argument type of sort.Slice
+-
+-sort.Slice requires an argument of a slice type. Check that the interface{} value passed to sort.Slice is actually a slice.
+-
+-
+-Default: on.
+-
+-Package documentation: [sortslice](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice)
+-
+-
+-## `stditerators`: use iterators instead of Len/At-style APIs
+-
+-This analyzer suggests a fix to replace each loop of the form:
+-
+- for i := 0; i < x.Len(); i++ {
+- use(x.At(i))
+- }
+-
+-or its "for elem := range x.Len()" equivalent by a range loop over an iterator offered by the same data type:
+-
+- for elem := range x.All() {
+- use(x.At(i)
+- }
+-
+-where x is one of various well-known types in the standard library.
+-
+-
+-Default: on.
+-
+-Package documentation: [stditerators](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stditerators)
+-
+-
+-## `stdmethods`: check signature of methods of well-known interfaces
+-
+-Sometimes a type may be intended to satisfy an interface but may fail to do so because of a mistake in its method signature. For example, the result of this WriteTo method should be (int64, error), not error, to satisfy io.WriterTo:
+-
+- type myWriterTo struct{...}
+- func (myWriterTo) WriteTo(w io.Writer) error { ... }
+-
+-This check ensures that each method whose name matches one of several well-known interface methods from the standard library has the correct signature for that interface.
+-
+-Checked method names include:
+-
+- Format GobEncode GobDecode MarshalJSON MarshalXML
+- Peek ReadByte ReadFrom ReadRune Scan Seek
+- UnmarshalJSON UnreadByte UnreadRune WriteByte
+- WriteTo
+-
+-
+-Default: on.
+-
+-Package documentation: [stdmethods](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods)
+-
+-
+-## `stdversion`: report uses of too-new standard library symbols
+-
+-The stdversion analyzer reports references to symbols in the standard library that were introduced by a Go release higher than the one in force in the referring file. (Recall that the file's Go version is defined by the 'go' directive its module's go.mod file, or by a "//go:build go1.X" build tag at the top of the file.)
+-
+-The analyzer does not report a diagnostic for a reference to a "too new" field or method of a type that is itself "too new", as this may have false positives, for example if fields or methods are accessed through a type alias that is guarded by a Go version constraint.
+-
+-
+-Default: on.
+-
+-Package documentation: [stdversion](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion)
+-
+-
+-## `stringintconv`: check for string(int) conversions
+-
+-This checker flags conversions of the form string(x) where x is an integer (but not byte or rune) type. Such conversions are discouraged because they return the UTF-8 representation of the Unicode code point x, and not a decimal string representation of x as one might expect. Furthermore, if x denotes an invalid code point, the conversion cannot be statically rejected.
+-
+-For conversions that intend on using the code point, consider replacing them with string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the string representation of the value in the desired base.
+-
+-
+-Default: on.
+-
+-Package documentation: [stringintconv](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv)
+-
+-
+-## `stringsbuilder`: replace += with strings.Builder
+-
+-This analyzer replaces repeated string += string concatenation operations with calls to Go 1.10's strings.Builder.
+-
+-For example:
+-
+- var s = "["
+- for x := range seq {
+- s += x
+- s += "."
+- }
+- s += "]"
+- use(s)
+-
+-is replaced by:
+-
+- var s strings.Builder
+- s.WriteString("[")
+- for x := range seq {
+- s.WriteString(x)
+- s.WriteString(".")
+- }
+- s.WriteString("]")
+- use(s.String())
+-
+-This avoids quadratic memory allocation and improves performance.
+-
+-The analyzer requires that all references to s except the final one are += operations. To avoid warning about trivial cases, at least one must appear within a loop. The variable s must be a local variable, not a global or parameter.
+-
+-The sole use of the finished string must be the last reference to the variable s. (It may appear within an intervening loop or function literal, since even s.String() is called repeatedly, it does not allocate memory.)
+-
+-
+-Default: on.
+-
+-Package documentation: [stringsbuilder](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringbuilder)
+-
+-
+-## `stringscutprefix`: replace HasPrefix/TrimPrefix with CutPrefix
+-
+-The stringscutprefix analyzer simplifies a common pattern where code first checks for a prefix with \`strings.HasPrefix\` and then removes it with \`strings.TrimPrefix\`. It replaces this two-step process with a single call to \`strings.CutPrefix\`, introduced in Go 1.20. The analyzer also handles the equivalent functions in the \`bytes\` package.
+-
+-For example, this input:
+-
+- if strings.HasPrefix(s, prefix) {
+- use(strings.TrimPrefix(s, prefix))
+- }
+-
+-is fixed to:
+-
+- if after, ok := strings.CutPrefix(s, prefix); ok {
+- use(after)
+- }
+-
+-The analyzer also offers fixes to use CutSuffix in a similar way. This input:
+-
+- if strings.HasSuffix(s, suffix) {
+- use(strings.TrimSuffix(s, suffix))
+- }
+-
+-is fixed to:
+-
+- if before, ok := strings.CutSuffix(s, suffix); ok {
+- use(before)
+- }
+-
+-
+-Default: on.
+-
+-Package documentation: [stringscutprefix](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringscutprefix)
+-
+-
+-## `stringsseq`: replace ranging over Split/Fields with SplitSeq/FieldsSeq
+-
+-The stringsseq analyzer improves the efficiency of iterating over substrings. It replaces
+-
+- for range strings.Split(...)
+-
+-with the more efficient
+-
+- for range strings.SplitSeq(...)
+-
+-which was added in Go 1.24 and avoids allocating a slice for the substrings. The analyzer also handles strings.Fields and the equivalent functions in the bytes package.
+-
+-
+-Default: on.
+-
+-Package documentation: [stringsseq](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringsseq)
+-
+-
+-## `structtag`: check that struct field tags conform to reflect.StructTag.Get
+-
+-Also report certain struct tags (json, xml) used with unexported fields.
+-
+-
+-Default: on.
+-
+-Package documentation: [structtag](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/structtag)
+-
+-
+-## `testingcontext`: replace context.WithCancel with t.Context in tests
+-
+-The testingcontext analyzer simplifies context management in tests. It replaces the manual creation of a cancellable context,
+-
+- ctx, cancel := context.WithCancel(context.Background())
+- defer cancel()
+-
+-with a single call to t.Context(), which was added in Go 1.24.
+-
+-This change is only suggested if the \`cancel\` function is not used for any other purpose.
+-
+-
+-Default: on.
+-
+-Package documentation: [testingcontext](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#testingcontext)
+-
+-
+-## `testinggoroutine`: report calls to (*testing.T).Fatal from goroutines started by a test
+-
+-Functions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and Skip{,f,Now} methods of \*testing.T, must be called from the test goroutine itself. This checker detects calls to these functions that occur within a goroutine started by the test. For example:
+-
+- func TestFoo(t *testing.T) {
+- go func() {
+- t.Fatal("oops") // error: (*T).Fatal called from non-test goroutine
+- }()
+- }
+-
+-
+-Default: on.
+-
+-Package documentation: [testinggoroutine](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine)
+-
+-
+-## `tests`: check for common mistaken usages of tests and examples
+-
+-The tests checker walks Test, Benchmark, Fuzzing and Example functions checking malformed names, wrong signatures and examples documenting non-existent identifiers.
+-
+-Please see the documentation for package testing in golang.org/pkg/testing for the conventions that are enforced for Tests, Benchmarks, and Examples.
+-
+-
+-Default: on.
+-
+-Package documentation: [tests](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests)
+-
+-
+-## `timeformat`: check for calls of (time.Time).Format or time.Parse with 2006-02-01
+-
+-The timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm) format. Internationally, "yyyy-dd-mm" does not occur in common calendar date standards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.
+-
+-
+-Default: on.
+-
+-Package documentation: [timeformat](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat)
+-
+-
+-## `unmarshal`: report passing non-pointer or non-interface values to unmarshal
+-
+-The unmarshal analysis reports calls to functions such as json.Unmarshal in which the argument type is not a pointer or an interface.
+-
+-
+-Default: on.
+-
+-Package documentation: [unmarshal](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal)
+-
+-
+-## `unreachable`: check for unreachable code
+-
+-The unreachable analyzer finds statements that execution can never reach because they are preceded by a return statement, a call to panic, an infinite loop, or similar constructs.
+-
+-
+-Default: on.
+-
+-Package documentation: [unreachable](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable)
+-
+-
+-## `unsafeptr`: check for invalid conversions of uintptr to unsafe.Pointer
+-
+-The unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer to convert integers to pointers. A conversion from uintptr to unsafe.Pointer is invalid if it implies that there is a uintptr-typed word in memory that holds a pointer value, because that word will be invisible to stack copying and to the garbage collector.
+-
+-
+-Default: on.
+-
+-Package documentation: [unsafeptr](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr)
+-
+-
+-## `unusedfunc`: check for unused functions, methods, etc
+-
+-The unusedfunc analyzer reports functions and methods that are never referenced outside of their own declaration.
+-
+-A function is considered unused if it is unexported and not referenced (except within its own declaration).
+-
+-A method is considered unused if it is unexported, not referenced (except within its own declaration), and its name does not match that of any method of an interface type declared within the same package.
+-
+-The tool may report false positives in some situations, for example:
+-
+- - for a declaration of an unexported function that is referenced from another package using the go:linkname mechanism, if the declaration's doc comment does not also have a go:linkname comment.
+-
+- (Such code is in any case strongly discouraged: linkname annotations, if they must be used at all, should be used on both the declaration and the alias.)
+-
+- - for compiler intrinsics in the "runtime" package that, though never referenced, are known to the compiler and are called indirectly by compiled object code.
+-
+- - for functions called only from assembly.
+-
+- - for functions called only from files whose build tags are not selected in the current build configuration.
+-
+-Since these situations are relatively common in the low-level parts of the runtime, this analyzer ignores the standard library. See [https://go.dev/issue/71686](https://go.dev/issue/71686) and [https://go.dev/issue/74130](https://go.dev/issue/74130) for further discussion of these limitations.
+-
+-The unusedfunc algorithm is not as precise as the golang.org/x/tools/cmd/deadcode tool, but it has the advantage that it runs within the modular analysis framework, enabling near real-time feedback within gopls.
+-
+-The unusedfunc analyzer also reports unused types, vars, and constants. Enums--constants defined with iota--are ignored since even the unused values must remain present to preserve the logical ordering.
+-
+-
+-Default: on.
+-
+-Package documentation: [unusedfunc](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedfunc)
+-
+-
+-## `unusedparams`: check for unused parameters of functions
+-
+-The unusedparams analyzer checks functions to see if there are any parameters that are not being used.
+-
+-To ensure soundness, it ignores:
+-
+- - "address-taken" functions, that is, functions that are used as a value rather than being called directly; their signatures may be required to conform to a func type.
+- - exported functions or methods, since they may be address-taken in another package.
+- - unexported methods whose name matches an interface method declared in the same package, since the method's signature may be required to conform to the interface type.
+- - functions with empty bodies, or containing just a call to panic.
+- - parameters that are unnamed, or named "\_", the blank identifier.
+-
+-The analyzer suggests a fix of replacing the parameter name by "\_", but in such cases a deeper fix can be obtained by invoking the "Refactor: remove unused parameter" code action, which will eliminate the parameter entirely, along with all corresponding arguments at call sites, while taking care to preserve any side effects in the argument expressions; see [https://github.com/golang/tools/releases/tag/gopls%2Fv0.14](https://github.com/golang/tools/releases/tag/gopls%2Fv0.14).
+-
+-This analyzer ignores generated code.
+-
+-
+-Default: on.
+-
+-Package documentation: [unusedparams](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams)
+-
+-
+-## `unusedresult`: check for unused results of calls to some functions
+-
+-Some functions like fmt.Errorf return a result and have no side effects, so it is always a mistake to discard the result. Other functions may return an error that must not be ignored, or a cleanup operation that must be called. This analyzer reports calls to functions like these when the result of the call is ignored.
+-
+-The set of functions may be controlled using flags.
+-
+-
+-Default: on.
+-
+-Package documentation: [unusedresult](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult)
+-
+-
+-## `unusedvariable`: check for unused variables and suggest fixes
+-
+-
+-
+-Default: on.
+-
+-Package documentation: [unusedvariable](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable)
+-
+-
+-## `unusedwrite`: checks for unused writes
+-
+-The analyzer reports instances of writes to struct fields and arrays that are never read. Specifically, when a struct object or an array is copied, its elements are copied implicitly by the compiler, and any element write to this copy does nothing with the original object.
+-
+-For example:
+-
+- type T struct { x int }
+-
+- func f(input []T) {
+- for i, v := range input { // v is a copy
+- v.x = i // unused write to field x
+- }
+- }
+-
+-Another example is about non-pointer receiver:
+-
+- type T struct { x int }
+-
+- func (t T) f() { // t is a copy
+- t.x = i // unused write to field x
+- }
+-
+-
+-Default: on.
+-
+-Package documentation: [unusedwrite](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite)
+-
+-
+-## `waitgroup`: check for misuses of sync.WaitGroup
+-
+-This analyzer detects mistaken calls to the (\*sync.WaitGroup).Add method from inside a new goroutine, causing Add to race with Wait:
+-
+- // WRONG
+- var wg sync.WaitGroup
+- go func() {
+- wg.Add(1) // "WaitGroup.Add called from inside new goroutine"
+- defer wg.Done()
+- ...
+- }()
+- wg.Wait() // (may return prematurely before new goroutine starts)
+-
+-The correct code calls Add before starting the goroutine:
+-
+- // RIGHT
+- var wg sync.WaitGroup
+- wg.Add(1)
+- go func() {
+- defer wg.Done()
+- ...
+- }()
+- wg.Wait()
+-
+-
+-Default: on.
+-
+-Package documentation: [waitgroup](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup)
+-
+-
+-## `waitgroup`: replace wg.Add(1)/go/wg.Done() with wg.Go
+-
+-The waitgroup analyzer simplifies goroutine management with \`sync.WaitGroup\`. It replaces the common pattern
+-
+- wg.Add(1)
+- go func() {
+- defer wg.Done()
+- ...
+- }()
+-
+-with a single call to
+-
+- wg.Go(func(){ ... })
+-
+-which was added in Go 1.25.
+-
+-
+-Default: on.
+-
+-Package documentation: [waitgroup](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#waitgroup)
+-
+-
+-## `yield`: report calls to yield where the result is ignored
+-
+-After a yield function returns false, the caller should not call the yield function again; generally the iterator should return promptly.
+-
+-This example fails to check the result of the call to yield, causing this analyzer to report a diagnostic:
+-
+- yield(1) // yield may be called again (on L2) after returning false
+- yield(2)
+-
+-The corrected code is either this:
+-
+- if yield(1) { yield(2) }
+-
+-or simply:
+-
+- _ = yield(1) && yield(2)
+-
+-It is not always a mistake to ignore the result of yield. For example, this is a valid single-element iterator:
+-
+- yield(1) // ok to ignore result
+- return
+-
+-It is only a mistake when the yield call that returned false may be followed by another call.
+-
+-
+-Default: on.
+-
+-Package documentation: [yield](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/yield)
+-
+-
+Binary files a/gopls/doc/assets/add-test-for-func.png and b/gopls/doc/assets/add-test-for-func.png differ
+diff -urN a/gopls/doc/assets/assets.go b/gopls/doc/assets/assets.go
+--- a/gopls/doc/assets/assets.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/assets/assets.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,7 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// Package assets is an empty package to appease "go test ./...",
+-// as run by our CI builders, which doesn't like an empty module.
+-package assets
+Binary files a/gopls/doc/assets/browse-assembly.png and b/gopls/doc/assets/browse-assembly.png differ
+Binary files a/gopls/doc/assets/browse-free-symbols.png and b/gopls/doc/assets/browse-free-symbols.png differ
+Binary files a/gopls/doc/assets/browse-pkg-doc.png and b/gopls/doc/assets/browse-pkg-doc.png differ
+Binary files a/gopls/doc/assets/code-action-doc.png and b/gopls/doc/assets/code-action-doc.png differ
+Binary files a/gopls/doc/assets/convert-string-interpreted.png and b/gopls/doc/assets/convert-string-interpreted.png differ
+Binary files a/gopls/doc/assets/convert-string-raw.png and b/gopls/doc/assets/convert-string-raw.png differ
+Binary files a/gopls/doc/assets/diagnostic-analysis.png and b/gopls/doc/assets/diagnostic-analysis.png differ
+Binary files a/gopls/doc/assets/diagnostic-typeerror.png and b/gopls/doc/assets/diagnostic-typeerror.png differ
+Binary files a/gopls/doc/assets/document-highlight.png and b/gopls/doc/assets/document-highlight.png differ
+Binary files a/gopls/doc/assets/documentlink.png and b/gopls/doc/assets/documentlink.png differ
+Binary files a/gopls/doc/assets/extract-function-after.png and b/gopls/doc/assets/extract-function-after.png differ
+Binary files a/gopls/doc/assets/extract-function-before.png and b/gopls/doc/assets/extract-function-before.png differ
+Binary files a/gopls/doc/assets/extract-to-new-file-after.png and b/gopls/doc/assets/extract-to-new-file-after.png differ
+Binary files a/gopls/doc/assets/extract-to-new-file-before.png and b/gopls/doc/assets/extract-to-new-file-before.png differ
+Binary files a/gopls/doc/assets/extract-val-all-before.png and b/gopls/doc/assets/extract-val-all-before.png differ
+Binary files a/gopls/doc/assets/extract-var-after.png and b/gopls/doc/assets/extract-var-after.png differ
+Binary files a/gopls/doc/assets/extract-var-all-after.png and b/gopls/doc/assets/extract-var-all-after.png differ
+Binary files a/gopls/doc/assets/extract-var-before.png and b/gopls/doc/assets/extract-var-before.png differ
+Binary files a/gopls/doc/assets/fill-struct-after.png and b/gopls/doc/assets/fill-struct-after.png differ
+Binary files a/gopls/doc/assets/fill-struct-before.png and b/gopls/doc/assets/fill-struct-before.png differ
+Binary files a/gopls/doc/assets/fill-switch-after.png and b/gopls/doc/assets/fill-switch-after.png differ
+Binary files a/gopls/doc/assets/fill-switch-before.png and b/gopls/doc/assets/fill-switch-before.png differ
+Binary files a/gopls/doc/assets/fill-switch-enum-after.png and b/gopls/doc/assets/fill-switch-enum-after.png differ
+Binary files a/gopls/doc/assets/fill-switch-enum-before.png and b/gopls/doc/assets/fill-switch-enum-before.png differ
+Binary files a/gopls/doc/assets/foldingrange.png and b/gopls/doc/assets/foldingrange.png differ
+diff -urN a/gopls/doc/assets/go.mod b/gopls/doc/assets/go.mod
+--- a/gopls/doc/assets/go.mod 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/assets/go.mod 1969-12-31 18:00:00.000000000 -0600
+@@ -1,7 +0,0 @@
+-// This module contains no Go code, but serves to carve out a hole in
+-// its parent module to avoid bloating it with large image files that
+-// would otherwise be downloaded by "go install golang.org/x/tools/gopls@latest".
+-
+-module golang.org/x/tools/gopls/doc/assets
+-
+-go 1.24.0
+Binary files a/gopls/doc/assets/hover-basic.png and b/gopls/doc/assets/hover-basic.png differ
+Binary files a/gopls/doc/assets/hover-doclink.png and b/gopls/doc/assets/hover-doclink.png differ
+Binary files a/gopls/doc/assets/hover-embed.png and b/gopls/doc/assets/hover-embed.png differ
+Binary files a/gopls/doc/assets/hover-field-tag.png and b/gopls/doc/assets/hover-field-tag.png differ
+Binary files a/gopls/doc/assets/hover-linkname.png and b/gopls/doc/assets/hover-linkname.png differ
+Binary files a/gopls/doc/assets/hover-size-field.png and b/gopls/doc/assets/hover-size-field.png differ
+Binary files a/gopls/doc/assets/hover-size-struct.png and b/gopls/doc/assets/hover-size-struct.png differ
+Binary files a/gopls/doc/assets/hover-size-wasteful.png and b/gopls/doc/assets/hover-size-wasteful.png differ
+Binary files a/gopls/doc/assets/inlayhint-parameternames.png and b/gopls/doc/assets/inlayhint-parameternames.png differ
+Binary files a/gopls/doc/assets/inline-after.png and b/gopls/doc/assets/inline-after.png differ
+Binary files a/gopls/doc/assets/inline-before.png and b/gopls/doc/assets/inline-before.png differ
+Binary files a/gopls/doc/assets/invert-if-after.png and b/gopls/doc/assets/invert-if-after.png differ
+Binary files a/gopls/doc/assets/invert-if-before.png and b/gopls/doc/assets/invert-if-before.png differ
+Binary files a/gopls/doc/assets/outgoingcalls.png and b/gopls/doc/assets/outgoingcalls.png differ
+Binary files a/gopls/doc/assets/remove-unusedparam-after.png and b/gopls/doc/assets/remove-unusedparam-after.png differ
+Binary files a/gopls/doc/assets/remove-unusedparam-before.png and b/gopls/doc/assets/remove-unusedparam-before.png differ
+Binary files a/gopls/doc/assets/rename-conflict.png and b/gopls/doc/assets/rename-conflict.png differ
+Binary files a/gopls/doc/assets/signature-help.png and b/gopls/doc/assets/signature-help.png differ
+Binary files a/gopls/doc/assets/splitpkg-deps.png and b/gopls/doc/assets/splitpkg-deps.png differ
+Binary files a/gopls/doc/assets/splitpkg.png and b/gopls/doc/assets/splitpkg.png differ
+Binary files a/gopls/doc/assets/subtypes.png and b/gopls/doc/assets/subtypes.png differ
+Binary files a/gopls/doc/assets/supertypes.png and b/gopls/doc/assets/supertypes.png differ
+Binary files a/gopls/doc/assets/zeroconfig.png and b/gopls/doc/assets/zeroconfig.png differ
+diff -urN a/gopls/doc/codelenses.md b/gopls/doc/codelenses.md
+--- a/gopls/doc/codelenses.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/codelenses.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,155 +0,0 @@
+----
+-title: "Gopls: Code lenses"
+----
+-
+-A "code lens" is a command associated with a range of a source file.
+-The VS Code manual describes code lenses as
+-"[actionable, contextual information, interspersed in your source
+-code](https://code.visualstudio.com/blogs/2017/02/12/code-lens-roundup)".
+-The LSP [`textDocument/codeLens`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens) operation requests the
+-current set of code lenses for a file.
+-
+-Gopls generates code lenses from a number of sources.
+-This document describes them.
+-
+-They can be enabled and disabled using the
+-[`codelenses`](settings.md#codelenses) setting.
+-Their features are subject to change.
+-
+-Client support:
+-- **VS Code**: Code Lenses appear as small text links above a line of source code.
+-- **Emacs + eglot**: Not supported, but prototype exists at https://github.com/joaotavora/eglot/pull/71.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls codelens`. For example, `gopls codelens -exec file.go:123 "run test"` runs the test at the specified line.
+-
+-
+-
+-
+-## `generate`: Run `go generate`
+-
+-
+-This codelens source annotates any `//go:generate` comments
+-with commands to run `go generate` in this directory, on
+-all directories recursively beneath this one.
+-
+-See [Generating code](https://go.dev/blog/generate) for
+-more details.
+-
+-
+-Default: on
+-
+-File type: Go
+-
+-## `regenerate_cgo`: Re-generate cgo declarations
+-
+-
+-This codelens source annotates an `import "C"` declaration
+-with a command to re-run the [cgo
+-command](https://pkg.go.dev/cmd/cgo) to regenerate the
+-corresponding Go declarations.
+-
+-Use this after editing the C code in comments attached to
+-the import, or in C header files included by it.
+-
+-
+-Default: on
+-
+-File type: Go
+-
+-## `test`: Run tests and benchmarks
+-
+-
+-This codelens source annotates each `Test` and `Benchmark`
+-function in a `*_test.go` file with a command to run it.
+-
+-This source is off by default because VS Code has
+-a client-side custom UI for testing, and because progress
+-notifications are not a great UX for streamed test output.
+-See:
+-- golang/go#67400 for a discussion of this feature.
+-- https://github.com/joaotavora/eglot/discussions/1402
+- for an alternative approach.
+-
+-
+-Default: off
+-
+-File type: Go
+-
+-## `run_govulncheck`: Run govulncheck (legacy)
+-
+-
+-This codelens source annotates the `module` directive in a go.mod file
+-with a command to run Govulncheck asynchronously.
+-
+-[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that
+-computes the set of functions reachable within your application, including
+-dependencies; queries a database of known security vulnerabilities; and
+-reports any potential problems it finds.
+-
+-
+-Default: on
+-
+-File type: go.mod
+-
+-## `tidy`: Tidy go.mod file
+-
+-
+-This codelens source annotates the `module` directive in a
+-go.mod file with a command to run [`go mod
+-tidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures
+-that the go.mod file matches the source code in the module.
+-
+-
+-Default: on
+-
+-File type: go.mod
+-
+-## `upgrade_dependency`: Update dependencies
+-
+-
+-This codelens source annotates the `module` directive in a
+-go.mod file with commands to:
+-
+-- check for available upgrades,
+-- upgrade direct dependencies, and
+-- upgrade all dependencies transitively.
+-
+-
+-Default: on
+-
+-File type: go.mod
+-
+-## `vendor`: Update vendor directory
+-
+-
+-This codelens source annotates the `module` directive in a
+-go.mod file with a command to run [`go mod
+-vendor`](https://go.dev/ref/mod#go-mod-vendor), which
+-creates or updates the directory named `vendor` in the
+-module root so that it contains an up-to-date copy of all
+-necessary package dependencies.
+-
+-
+-Default: on
+-
+-File type: go.mod
+-
+-## `vulncheck`: Run govulncheck
+-
+-**This setting is experimental and may be deleted.**
+-
+-
+-This codelens source annotates the `module` directive in a go.mod file
+-with a command to run govulncheck synchronously.
+-
+-[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that
+-computes the set of functions reachable within your application, including
+-dependencies; queries a database of known security vulnerabilities; and
+-reports any potential problems it finds.
+-
+-
+-Default: off
+-
+-File type: go.mod
+-
+-
+diff -urN a/gopls/doc/command-line.md b/gopls/doc/command-line.md
+--- a/gopls/doc/command-line.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/command-line.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,37 +0,0 @@
+----
+-title: "Gopls: Command-line interface"
+----
+-
+-The `gopls` command provides a number of subcommands that expose much
+-of the server's functionality. However, the interface is currently
+-**experimental** and **subject to change at any point.**
+-It is not efficient, complete, flexible, or officially supported.
+-
+-Its primary use is as a debugging aid.
+-For example, this command reports the location of references to the
+-symbol at the specified file/line/column:
+-
+-```
+-$ gopls references ./gopls/main.go:35:8
+-Log: Loading packages...
+-Info: Finished loading packages.
+-/home/gopher/xtools/go/packages/gopackages/main.go:27:7-11
+-/home/gopher/xtools/gopls/internal/cmd/integration_test.go:1062:7-11
+-/home/gopher/xtools/gopls/internal/test/integration/bench/bench_test.go:59:8-12
+-/home/gopher/xtools/gopls/internal/test/integration/regtest.go:140:8-12
+-/home/gopher/xtools/gopls/main.go:35:7-11
+-```
+-
+-See https://go.dev/issue/63693 for a discussion of its future.
+-
+-Learn about available commands and flags by running `gopls help`.
+-
+-Positions within files are specified as `file.go:line:column` triples,
+-where the line and column start at 1, and columns are measured in
+-bytes of the UTF-8 encoding.
+-Alternatively, positions may be specified by the byte offset within
+-the UTF-8 encoding of the file, starting from zero, for example
+-`file.go:#1234`.
+-(When working in non-ASCII files, beware that your editor may report a
+-position's offset within its file using a different measure such as
+-UTF-16 codes, Unicode code points, or graphemes).
+diff -urN a/gopls/doc/contributing.md b/gopls/doc/contributing.md
+--- a/gopls/doc/contributing.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/contributing.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,227 +0,0 @@
+----
+-title: "Gopls: Contributing"
+----
+-
+-Contributions are welcome! However, development is fast moving,
+-and we are limited in our capacity to review contributions.
+-So, before sending a CL, please please please:
+-
+-- **file an issue** for a bug or feature request, if one does not
+- exist already. This allows us to identify redundant requests, or to
+- merge a specific problem into a more general one, and to assess the
+- importance of the problem.
+-- **claim it for yourself** by commenting on the issue or, if you are
+- able, by assigning the issue to yourself. This helps us avoid two
+- people working on the same problem.
+-- **propose an implementation plan** in the issue tracker for CLs of
+- any complexity. It is much more efficient to discuss the plan at a
+- high level before we start getting bogged down in the details of
+- a code review.
+-
+-When you send a CL, it should include:
+-
+-- a **CL description** that summarizes the change,
+- motivates why it is necessary,
+- explains it at a high level,
+- contrasts it with more obvious or simpler approaches, and
+- links to relevant issues;
+-- **tests** (integration tests or marker tests);
+-- **documentation**, for new or modified features; and
+-- **release notes**, for new features or significant changes.
+-
+-During code review, please address all reviewer comments.
+-Some comments result in straightforward code changes;
+-others demand a more complex response.
+-When a reviewer asks a question, the best response is
+-often not to respond to it directly, but to change the
+-code to avoid raising the question,
+-for example by making the code self-explanatory.
+-It's fine to disagree with a comment,
+-point out a reviewer's mistake,
+-or offer to address a comment in a follow-up change,
+-leaving a `TODO` comment in the current CL.
+-But please don't dismiss or quietly ignore a comment without action,
+-as it may lead reviewers to repeat themselves,
+-or to serious problems being neglected.
+-
+-For more detail, see the Go project's
+-[contribution guidelines](https://golang.org/doc/contribute.html).
+-
+-## Finding issues
+-
+-All `gopls` issues are labeled as such (see the [`gopls` label][issue-gopls]).
+-Issues that are suitable for contributors are additionally tagged with the
+-[`help-wanted` label][issue-wanted].
+-
+-Before you begin working on an issue, please leave a comment that you are
+-claiming it.
+-
+-## Getting started
+-
+-[](https://pkg.go.dev/golang.org/x/tools/gopls/internal)
+-
+-Most of the `gopls` logic is in the `golang.org/x/tools/gopls/internal` directory.
+-See [design/implementation.md](./design/implementation.md) for an overview of the code organization.
+-
+-## Build
+-
+-To build a version of `gopls` with your changes applied:
+-
+-```bash
+-cd /path/to/tools/gopls
+-go install
+-```
+-
+-To confirm that you are testing with the correct `gopls` version, check that
+-your `gopls` version looks like this:
+-
+-```bash
+-$ gopls version
+-golang.org/x/tools/gopls master
+- golang.org/x/tools/gopls@(devel)
+-```
+-
+-## Getting help
+-
+-The best way to contact the gopls team directly is via the
+-[#gopls-dev](https://app.slack.com/client/T029RQSE6/CRWSN9NCD) channel on the
+-gophers slack. Please feel free to ask any questions about your contribution or
+-about contributing in general.
+-
+-
+-## Error handling
+-
+-It is important for the user experience that, whenever practical,
+-minor logic errors in a particular feature don't cause the server to
+-crash.
+-
+-The representation of a Go program is complex. The import graph of
+-package metadata, the syntax trees of parsed files, and their
+-associated type information together form a huge API surface area.
+-Even when the input is valid, there are many edge cases to consider,
+-and this grows by an order of magnitude when you consider missing
+-imports, parse errors, and type errors.
+-
+-What should you do when your logic must handle an error that you
+-believe "can't happen"?
+-
+-- If it's possible to return an error, then use the `bug.Errorf`
+- function to return an error to the user, but also record the bug in
+- gopls' cache so that it is less likely to be ignored.
+-
+-- If it's safe to proceed, you can call `bug.Reportf` to record the
+- error and continue as normal.
+-
+-- If there's no way to proceed, call `bug.Fatalf` to record the error
+- and then stop the program with `log.Fatalf`. You can also use
+- `bug.Panicf` if there's a chance that a recover handler might save
+- the situation.
+-
+-- Only if you can prove locally that an error is impossible should you
+- call `log.Fatal`. If the error may happen for some input, however
+- unlikely, then you should use one of the approaches above. Also, if
+- the proof of safety depends on invariants broadly distributed across
+- the code base, then you should instead use `bug.Panicf`.
+-
+-Note also that panicking is preferable to `log.Fatal` because it
+-allows VS Code's crash reporting to recognize and capture the stack.
+-
+-Bugs reported through `bug.Errorf` and friends are retrieved using the
+-`gopls bug` command, which opens a GitHub Issue template and populates
+-it with a summary of each bug and its frequency.
+-The text of the bug is rather fastidiously printed to stdout to avoid
+-sharing user names and error message strings (which could contain
+-project identifiers) with GitHub.
+-Users are invited to share it if they are willing.
+-
+-## Testing
+-
+-The normal command you should use to run the tests after a change is:
+-
+-```bash
+-gopls$ go test -short ./...
+-```
+-
+-(The `-short` flag skips some slow-running ones. The trybot builders
+-run the complete set, on a wide range of platforms.)
+-
+-Gopls tests are a mix of two kinds.
+-
+-- [Marker tests](https://golang.org/x/tools/gopls/internal/test/marker) express each test scenario
+- in a standalone text file that contains the target .go, go.mod, and
+- go.work files, in which special annotations embedded in comments
+- drive the test. These tests are generally easy to write and fast
+- to iterate, but have limitations on what they can express.
+-
+-- [Integration tests](https://golang.org/x/tools/gopls/internal/test/integration) are regular Go
+- `func Test(*testing.T)` functions that make a series of calls to an
+- API for a fake LSP-enabled client editor. The API allows you to open
+- and edit a file, navigate to a definition, invoke other LSP
+- operations, and assert properties about the state.
+-
+- Due to the asynchronous nature of the LSP, integration tests make
+- assertions about states that the editor must achieve eventually,
+- even when the program goes wrong quickly, it may take a while before
+- the error is reported as a failure to achieve the desired state
+- within several minutes. We recommend that you set
+- `GOPLS_INTEGRATION_TEST_TIMEOUT=10s` to reduce the timeout for
+- integration tests when debugging.
+-
+- When they fail, the integration tests print the log of the LSP
+- session between client and server. Though verbose, they are very
+- helpful for debugging once you know how to read them.
+-
+-Don't hesitate to [reach out](#getting-help) to the gopls team if you
+-need help.
+-
+-### CI
+-
+-When you mail your CL and you or a fellow contributor assigns the
+-`Run-TryBot=1` label in Gerrit, the
+-[TryBots](https://golang.org/doc/contribute.html#trybots) will run tests in
+-both the `golang.org/x/tools` and `golang.org/x/tools/gopls` modules, as
+-described above.
+-
+-Furthermore, an additional "gopls-CI" pass will be run by _Kokoro_, which is a
+-Jenkins-like Google infrastructure for running Dockerized tests. This allows us
+-to run gopls tests in various environments that would be difficult to add to
+-the TryBots. Notably, Kokoro runs tests on
+-[older Go versions](index.md#supported-go-versions) that are no longer supported
+-by the TryBots. Per that policy, support for these older Go versions is
+-best-effort, and test failures may be skipped rather than fixed.
+-
+-Kokoro runs are triggered by the `Run-TryBot=1` label, just like TryBots, but
+-unlike TryBots they do not automatically re-run if the "gopls-CI" result is
+-removed in Gerrit. To force a re-run of the Kokoro CI on a CL containing the
+-`Run-TryBot=1` label, you can reply in Gerrit with the comment "kokoro rerun".
+-
+-## Debugging
+-
+-The easiest way to debug your change is to run a single `gopls` test with a
+-debugger.
+-
+-See also [Troubleshooting](troubleshooting.md#troubleshooting).
+-
+-
+-
+-[issue-gopls]: https://github.com/golang/go/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Agopls "gopls issues"
+-[issue-wanted]: https://github.com/golang/go/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Agopls+label%3A"help+wanted" "help wanted"
+-
+-## Documentation
+-
+-Each CL that adds or changes a feature should include, in addition to
+-a test that exercises the new behavior:
+-
+-- a **release note** that briefly explains the change, and
+-- **comprehensive documentation** in the [index of features](features/).
+-
+-The release note should go in the file named for the forthcoming
+-release, for example [release/v0.16.0.md](release/v0.16.0.md). (Create
+-the file if your feature is the first to be added after a release.)
+-
+-## Design documentation
+-
+-* [Integrating `gopls` with an editor](design/integrating.md)
+-* [Design requirements and decisions](design/design.md)
+-* [Implementation overview](design/implementation.md)
+diff -urN a/gopls/doc/daemon.md b/gopls/doc/daemon.md
+--- a/gopls/doc/daemon.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/daemon.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,185 +0,0 @@
+----
+-title: "Gopls: Running as a daemon"
+----
+-
+-**Note: this feature is new. If you encounter bugs, please [file an
+-issue](troubleshooting.md#file-an-issue).**
+-
+-If you just want to try this out, skip ahead to the [quickstart](#quickstart).
+-
+-## Background: gopls execution modes
+-
+-Gopls was originally implemented as an LSP sidecar: a process started by
+-editors or editor plugins, and communicated with using jsonrpc 2.0 over
+-stdin/stdout. By executing as a stateful process, gopls can maintain a
+-significant amount of cache and can eagerly perform analysis on the source code
+-being edited.
+-
+-This execution mode does not work as well when there are many separate editor
+-processes or when editor processes are short-lived, as is often the case for
+-users of non-IDE editors such as Vim or Emacs. Having many processes means
+-having many caches, consuming a significant amount of system resources. Using
+-short-lived sessions means paying a start-up cost each time a session is
+-created.
+-
+-To support these types of workflows, a new mode of gopls execution is supported
+-wherein a single, persistent, shared gopls "daemon" process is responsible for
+-managing all gopls sessions. In this mode, editors still start a gopls sidecar,
+-but this sidecar merely acts as a thin "forwarder", responsible for forwarding
+-the LSP to the shared gopls instance and recording metrics, logs, and rpc
+-traces.
+-
+-## Quickstart
+-
+-To use a shared gopls instance you must either manage the daemon process
+-yourself, or let the gopls forwarder processes start the shared daemon as
+-needed.
+-
+-### Running with `-remote=auto`
+-
+-Automatic management of the daemon is easiest, and can be done by passing the
+-flag `-remote=auto` to the gopls process started by your editor. This will
+-cause this process to auto-start the gopls daemon if needed, connect to it, and
+-forward the LSP. For example, here is a reasonable gopls invocation, that sets
+-some additional flags for easier [debugging](#debugging):
+-
+-```bash
+-gopls -remote=auto -logfile=auto -debug=:0 -remote.debug=:0 -rpc.trace
+-```
+-
+-Note that the shared gopls process will automatically shut down after one
+-minute with no connected clients.
+-
+-### Managing the daemon manually
+-
+-To manage the gopls daemon process via external means rather than having the
+-forwarders manage it, you must start a gopls daemon process with the
+-`-listen=` flag, and then pass `-remote=` to the gopls processes
+-started by your editor.
+-
+-For example, to host the daemon on the TCP port `37374`, do:
+-
+-```bash
+-gopls -listen=:37374 -logfile=auto -debug=:0
+-```
+-
+-And then from the editor, run
+-
+-```bash
+-gopls -remote=:37374 -logfile=auto -debug=:0 -rpc.trace
+-```
+-
+-If you are on a POSIX system, you can also use unix domain sockets by prefixing
+-the flag values with `unix;`. For example:
+-
+-```bash
+-gopls -listen="unix;/tmp/gopls-daemon-socket" -logfile=auto -debug=:0
+-```
+-
+-And connect via:
+-
+-```bash
+-gopls -remote="unix;/tmp/gopls-daemon-socket" -logfile=auto -debug=:0 -rpc.trace
+-```
+-
+-(Note that these flag values MUST be enclosed in quotes, because ';' is a
+-special shell character. For this reason, this syntax is subject to change in
+-the future.)
+-
+-## Debugging
+-
+-Debugging a shared gopls session is more complicated than a singleton session,
+-because there are now two gopls processes involved with handling the LSP. Here
+-are some tips:
+-
+-### Finding logfiles and debug addresses
+-
+-When running in daemon mode, you can use the `gopls inspect sessions` command
+-to find the logfile and debug port for your gopls daemon instance (as well as
+-for all its connected clients). By default, this inspects the default daemon
+-(i.e. `-remote=auto`). To inspect a different daemon, use the `-remote` flag
+-explicitly: `gopls -remote=localhost:12345 inspect sessions`.
+-
+-This works whether or not you have enabled `-remote.debug`.
+-
+-### Traversing debug pages
+-
+-When `-debug=:0` is passed to gopls, it runs a webserver that serves stateful
+-debug pages (see [troubleshooting.md](troubleshooting.md)). You can find the
+-actual port hosting these pages by either using the `gopls inspect sessions`
+-command, or by checking the start of the logfile -- it will be one of the first
+-log messages. For example, if using `-logfile=auto`, find the debug address by
+-checking `head /tmp/gopls-.log`.
+-
+-By default, the gopls daemon is not started with `-debug`. To enable it, set
+-the `-remote.debug` flag on the forwarder instance, so that it invokes gopls
+-with `-debug` when starting the daemon.
+-
+-The debug pages of the forwarder process will have a link to the debug pages of
+-the daemon server process. Correspondingly, the debug pages of the daemon
+-process will have a link to each of its clients.
+-
+-This can help you find metrics, traces, and log files for all of the various
+-servers and clients.
+-
+-### Using logfiles
+-
+-The gopls daemon is started with logging disabled by default. To customize
+-this, pass `-remote.logfile` to the gopls forwarder. Using
+-`-remote.logfile=auto`, the daemon will log to a default location (on posix
+-systems: `/tmp/gopls-daemon-.log`).
+-
+-The gopls daemon does not log session-scoped messages: those are instead
+-reflected back to the forwarder so that they can be accessed by the editor.
+-Daemon logs will only contain global messages, for example logs when sessions
+-connect and disconnect.
+-
+-It is recommended to start the forwarder gopls process with `-rpc.trace`, so
+-that its logfile will contain rpc trace logs specific to the LSP session.
+-
+-## Using multiple shared gopls instances
+-
+-There may be environments where it is desirable to have more than one shared
+-gopls instance. If managing the daemon manually, this can be done by simply
+-choosing different `-listen` addresses for each distinct daemon process.
+-
+-On POSIX systems, there is also support for automatic management of distinct
+-shared gopls processes: distinct daemons can be selected by passing
+-`-remote="auto;"`. Any gopls forwarder passing the same value for ``
+-will use the same shared daemon.
+-
+-## FAQ
+-
+-**Q: Why am I not saving as much memory as I expected when using a shared gopls?**
+-
+-A: As described in [implementation.md](design/implementation.md), gopls has a
+-concept of view/session/cache. Each session and view map onto exactly one
+-editor session (because they contain things like edited but unsaved buffers).
+-The cache contains things that are independent of any editor session, and can
+-therefore be shared.
+-
+-When, for example, three editor session are sharing a single gopls process,
+-they will share the cache but will each have their own session and view. The
+-memory savings in this mode, when compared to three separate gopls processes,
+-corresponds to the amount of cache overlap across sessions.
+-
+-Because this hasn't mattered much in the past, it is likely that there is state
+-that can be moved out of the session/view, and into the cache, thereby
+-increasing the amount of memory savings in the shared mode.
+-
+-**Q: How do I customize the daemon instance when using `-remote=auto`?**
+-
+-The daemon may be customized using flags of the form `-remote.*` on the
+-forwarder gopls. This causes the forwarder to invoke gopls with these settings
+-when starting the daemon. As of writing, we expose the following configuration:
+-
+-* `-remote.logfile`: the location of the daemon logfile
+-* `-remote.debug`: the daemon's debug address
+-* `-remote.listen.timeout`: the amount of time the daemon should wait for new
+- connections while there are no current connections, before shutting down.
+- Must be set to a valid `time.Duration` (e.g. `30s` or `5m`). If `0`, listen
+- indefinitely. Default: `1m`.
+-
+-Note that once the daemon is already running, setting these flags will not
+-change its configuration. These flags only matter for the forwarder process
+-that actually starts the daemon.
+diff -urN a/gopls/doc/default.tmpl b/gopls/doc/default.tmpl
+--- a/gopls/doc/default.tmpl 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/default.tmpl 1969-12-31 18:00:00.000000000 -0600
+@@ -1,4 +0,0 @@
+-{{- /* For golang.org/x/website/cmd/golangorg */ -}}
+-{{define "layout"}}
+-{{doclayout .}}
+-{{end}}
+diff -urN a/gopls/doc/design/architecture.svg b/gopls/doc/design/architecture.svg
+--- a/gopls/doc/design/architecture.svg 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/design/architecture.svg 1969-12-31 18:00:00.000000000 -0600
+@@ -1 +0,0 @@
+-
+\ No newline at end of file
+diff -urN a/gopls/doc/design/design.md b/gopls/doc/design/design.md
+--- a/gopls/doc/design/design.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/design/design.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,437 +0,0 @@
+----
+-title: "Gopls: Design"
+----
+-
+-## _A note from the future_
+-
+-What follows below is the original design document for gopls, aggregated from
+-various sources spanning 2018 and 2019. Since then, all of the features listed
+-below have been implemented, along with many others. The first two goals have
+-been achieved: gopls is a full implementation of the LSP, and the default
+-backend for VS Code Go and many other editors. The third goal has only been
+-partially realized: while gopls has gained many features, it is not extensible
+-in the sense used in this document: the only way to extend gopls is to modify
+-gopls. The fourth goal is not achieved: while some notable companies are able
+-to use gopls with Bazel, the experience is subpar, and the Go command is the
+-only officially supported build system.
+-
+-On the other hand, two of the explicit non-goals have been reconsidered. One is
+-minor: syntax highlighting is now supported in the LSP by way of semantic
+-tokens. The other is major: as gopls gained popularity, it became apparent that
+-its memory footprint was a problem. The size of developer workspaces was
+-increasing faster than the RAM available in typically development environments
+-(particularly with containerized development). Gopls now uses a hybrid of
+-on-disk indexes and in-memory caches, described in more detail in our
+-[blog post on scalability](https://go.dev/blog/gopls-scalability).
+-
+-Notably, in anticipating difficulties this doc turned out to be prescient.
+-Gopls has indeed struggled against the core standary library packages upon
+-which it is built, and its user experience is still limited by the LSP.
+-Nevertheless, sticking with the standard library and LSP was the right
+-approach, as despite our small team these decisions have helped gopls keep up
+-with the evolving Go language (i.e. generics), and to integrate with many new
+-text editors.
+-
+-Gopls development continues, more than four years later, with a focus on
+-simplicity, reliability, and extensibility. The new, opt-in
+-[Go telemetry](https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.0)
+-will help us attain a higher standard of stability in our releases than we've
+-been able to achieve through Github issues alone. Furthermore, telemetry will
+-allow us to focus on high-priority features, and deprecate historical
+-workarounds that burden the codebase. With greater velocity, we look forward
+-to working with the community on improved refactoring, static analysis, and
+-whatever else the future brings.
+-
+-- _Rob Findley (rfindley@google.com), 2023_
+-
+-## Goals
+-
+-* `gopls` should **become the default editor backend** for the major editors used by Go programmers, fully supported by the Go team.
+-* `gopls` will be a **full implementation of LSP**, as described in the [LSP specification], to standardize as many of its features as possible.
+-* `gopls` will be **clean and extensible** so that it can encompass additional features in the future, allowing Go tooling to become best in class once more.
+-* `gopls` will **support alternate build systems and file layouts**, allowing Go development to be simpler and more powerful in any environment.
+-
+-## Context
+-
+-While Go has a number of excellent and useful command-line tools that enhance the developer experience, it has become clear that integrating these tools with IDEs can pose challenges.
+-
+-Support of these tools has relied on the goodwill of community members, and they have been put under a large burden of support at times as the language, toolchain and environments change. As a result many tools have ceased to work, have had support problems, or become confusing with forks and replacements, or provided an experience that is not as good as it could be.
+-See the section below on [existing solutions](#existing-solutions) for more problems and details.
+-
+-This is fine for tools used occasionally, but for core IDE features, this is not acceptable.
+-Autocompletion, jump to definition, formatting, and other such features should always work, as they are key for Go development.
+-
+-The Go team will create an editor backend that works in any build system.
+-It will also be able to improve upon the latency of Go tools, since each tool will no longer have to individually run the type-checker on each invocation, instead there will be a long-running process and data can be shared between the definitions, completions, diagnostics, and other features.
+-
+-By taking ownership of these tools and packaging them together in the form of gopls, the Go team will ensure that the Go development experience isn’t unnecessarily complicated for Go users.
+-Having one editor backend will simplify the lives of Go developers, the Go team, and the maintainers of Go editor plugins.
+-
+-See Rebecca's excellent GopherCon keynote [talk] and [slides] for some more context.
+-
+-## Non-Goals
+-
+-* Command line speed
+-
+- Although gopls will have a command line mode, it will be optimized for long running and not command responsiveness, as such it may not be the right tool for things like CI systems.
+- For such cases there will have to be an alternate tool using the same underlying libraries for consistency.
+-
+-* Low memory environments
+-
+- In order to do a good job of processing large projects with very low latencies gopls will be holding a lot of information in memory.
+- It is presumed that developers are normally working on systems with significant RAM and this will not be a problem.
+- In general this is upheld by the large memory usage of existing IDE solutions (like IntelliJ)
+-
+-* Syntax highlighting
+-
+- At the moment there is no editor that delegates this functionality to a separate binary, and no standard way of doing it.
+-
+-## Existing solutions
+-
+-Every year the Go team conducts a survey, asking developers about their experiences with the language.
+-
+-One question that is asked is “How do you feel about your editor?”.
+-
+-The responses told a very negative story. Some categorized quotes:
+-
+-* Setup
+- * "Hard to install and configure"
+- * "Inadequate documentation"
+-* Performance
+- * "Performance is very poor"
+- * "Pretty slow in large projects"
+-* Reliability
+- * "Features work one day, but not the next"
+- * "Tooling is not updated with new language features"
+-
+-Each editor has its own plugin that shells out to a variety of tools, many of which break with new Go releases or because they are no longer maintained.
+-
+-The individual tools each have to do the work to understand the code and all its transitive dependencies.
+-
+-Each feature is a different tool, with a different set of patterns for its command line, a different way to accept input and parse output, a different way of specifying source code locations.
+-To support its existing feature set, VSCode installed 24 different command line tools, many of which have options or forks to configure. When looking at the set of tools that needed to be migrated to modules, across all the editors, there were 63 separate tools.
+-
+-All these tools need to understand the code, and they use the same standard libraries to do it. Those libraries are optimized for these kinds of tools, but even so processing that much code takes a lot of time time. Almost none of the tools are capable of returning results within 100ms.
+-As developers type in their editor, multiple of these features need to activate, which means they are not just paying the cost once, but many times. The overall effect is an editing experience that feels sluggish, and features that are either not enabled or sometimes produce results that appear so slowly they are no longer useful when they arrive. This is a problem that increases with the size of the code base, which means it is getting worse over time, and is especially bad for the kinds of large code bases companies are dealing with as they use Go for more major tasks.
+-
+-## Requirements
+-
+-### Complete feature set
+-
+-For gopls to be considered a success it has to implement the full feature set discussed [below](#Features).
+-This is the set of features that users need in order to feel as productive as they were with the tooling it is replacing. It does not include every feature of previous implementations, there are some features that are almost never used that should be dropped (like guru's pointer analysis) and some other features that do not easily fit and will have to be worked around (replacing the save hook/linter).
+-
+-### Equivalent or better experience
+-
+-For all of those features, the user experience must match or exceed the current one available in all editors.
+-This is an easy statement to make, but a hard one to validate or measure. Many of the possible measures fail to capture the experience.
+-
+-For instance, if an attempt was made to measure the latency of a jump to definition call, the results would be fairly consistent from the old godef tool. From the gopls implementation there may be a much larger range of latencies, with the best being orders of magnitude faster, and the worse slightly worse, because gopls attempts to do far more work, but manages to cache it across calls.
+-
+-Or for a completion call, it might be slower but produce a better first match such that users accept it more often, resulting in an overall better experience.
+-
+-For the most part this has to rely on user reports. If users are refusing to switch because the experience is not better, it is clearly not done, if they are switching but most people are complaining, there are probably enough areas that are better to make the switch compelling but other areas which are worse. If most people are switching and either staying silent or being positive, it is probably done. When writing tools, the user is all that matters.
+-
+-### Solid community of contributors
+-
+-The scope and scale of the problem gopls is trying to solve is untenable for the core Go team, it is going to require a strong community to make it all happen.
+-
+-This implies the code must be easy to contribute to, and easy for many developers to work on in parallel. The functionality needs to be well decoupled, and have a thorough testing story.
+-
+-### Latencies that fall within user tolerance
+-
+-There has been a lot of research on acceptable latencies for user actions.
+-
+-The main result that affects gopls is that feedback in direct response to continuous user actions needs to be under 100ms to be imperceptible, and anything above 200ms aggravates the user.
+-This means in general the aim has to be <100ms for anything that happens as the developer types.
+-There will always be cases where gopls fails to meet this deadline, and there needs to be ways to make the user experience okay in those cases, but in general the point of this deadline is to inform the basic architecture design, any solution that cannot theoretically meet this goal in the long term is the wrong answer.
+-
+-### Easy to configure
+-
+-Developers are very particular, and have very differing desires in their coding experience. gopls is going to have to support a significant amount of flexibility, in order to meet those desires.
+-The default settings however with no configuration at all must be the one that is best experience for most users, and where possible the features must be flexible without configuration so that the client can easily make the choices about treatment without changing its communication with gopls.
+-
+-## Difficulties
+-
+-### Volume of data
+-
+-
+-* Small:
+-* Medium:
+-* Large:
+-* Corporate mono-repo: Much much bigger
+-
+-Parsing and type checking large amounts of code is quite expensive, and the converted forms use a lot of space. As gopls has to keep updating this information while the developer types, it needs to manage how it caches the converted forms very carefully to balance memory use vs speed.
+-
+-### Cache invalidation
+-
+-The basic unit of operation for the type checking is the package, but the basic unit of operation for an editor is the file.
+-gopls needs to be able to map files to packages efficiently, so that when files change it knows which packages need to be updated (along with any other packages that transitively depended on them).
+-This is made especially difficult by the fact that changing the content of a file can modify which packages it is considered part of (either by changing the package declaration or the build tags), a file can be in more than one package, and changes can be made to files without using the editor, in which case it will not notify us of the changes.
+-
+-### Inappropriate core functionality
+-
+-The base libraries for Go (things like [go/token], [go/ast] and [go/types]) are all designed for compiler-like applications.
+-They tend to worry more about throughput than memory use, they have structures that are intended to grow and then be thrown away at program exit, and they are not designed to keep going in the presence of errors in the source they are handling.
+-They also have no abilities to do incremental changes.
+-
+-Making a long running service work well with those libraries is a very large challenge, but writing new libraries would be far more work, and cause a significant long term cost as both sets of libraries would have to be maintained. Right now it is more important to get a working tool into the hands of users. In the long term this decision may have to be revisited, new low level libraries may be the only way to keep pushing the capabilities forwards.
+-
+-### Build system capabilities
+-
+-gopls is supposed to be build system agnostic, but it must use the build system to discover how files map to packages. When it tries to do so, even when the functionality is the same, the costs (in time, CPU and memory) are very different, and can significantly impact the user experience. Designing how gopls interacts with the build system to try to minimize or hide these differences is hard.
+-
+-### Build tags
+-
+-The build tag system in Go is quite powerful, and has many use cases. Source files can exclude themselves using powerful boolean logic on the set of active tags.
+-It is however designed for specifying the set of active tags on the command line, and the libraries are all designed to cope with only one valid combination at a time. There is also no way to work out the set of valid combinations.
+-
+-Type checking a file requires knowledge of all the other files in the same package, and that set of files is modified by the build tags. The set of exported identifiers of a package is also affected by which files are in the package, and thus its build tags.
+-
+-This means that even for files or packages that have no build tag controls it is not possible to produce correct results without knowing the set of build tags to consider.
+-This makes it very hard to produce useful results when viewing a file.
+-
+-### Features not supported by LSP
+-
+-There are some things it would be good to be able to do that do not fit easily into the existing LSP protocol.
+-For instance, displaying control flow information, automatic struct tags, complex refactoring...
+-
+-Each feature will have to be considered carefully, and either propose a change to LSP, or add a way to have gopls specific extensions to the protocol that are still easy to use in all the editor plugins.
+-
+-To avoid these at the start, only core LSP features will be implemented, as they are sufficient to meet the baseline requirements anyway, but the potential features need to be kept in mind in the core architecture.
+-
+-### Distribution
+-
+-Making sure that users are using the right version of gopls is going to be a problem. Each editor plugin is probably going to install the tools in its own way, some will choose to install it system wide, some will keep their own copy.
+-
+-Because it is a brand new tool, it will be changing rapidly. If users are not informed they are on an old version they will be experiencing problems that have already been fixed, which is worse for them, and then probably reporting them, which wastes time for the gopls team. There needs to be a mechanism for gopls to check if is up to date, and a recommended way to install an up to date version.
+-
+-### Debugging user problems
+-
+-gopls is essentially a very stateful long running server on the developer's machine. Its basic operation is affected by many things, from the users environment to the contents of the local build cache. The data it is operating on is often a confidential code base that cannot be shared.
+-All of these things make it hard for users to report a bug usefully, or create a minimal reproduction.
+-
+-There needs to be easy ways for users to report what information they can, and ways to attempt to reproduce problems without their entire state. This is also needed to produce regression tests.
+-
+-## Basic design decisions
+-
+-There are some fundamental architecture decisions that affect much of the rest of the design of the tool, making fundamental trade offs that impact the user experience.
+-
+-### Process lifetime: *managed by the editor*
+-
+-Processing a large code base to fully type check and then analyze it within the latency requirements is not feasible, and is one of the primary problems with the existing solutions. This remains true even if the computed information was cached on disk, as running analyzers and type checkers ends up requiring the full AST of all files in the dependency graph.
+-It is theoretically possible to do better, but only with a major re-write of the existing parsing and type checking libraries, something that is not feasible at this time.
+-
+-This implies that gopls should be a long running process, that is able to cache and pre-calculate results in memory so that when a request arrives it can produce the answer much faster.
+-
+-It could run as a daemon on the user's machine, but there are a lot of issues with managing a daemon. It may well be the right choice in the long term, and it should be allowed for in the fundamental architecture design, but to start with it will instead have a process that lasts as long as the editor that starts it, and that can easily be restarted.
+-
+-### Caching: *in memory*
+-
+-Persistent disk caches are very expensive to maintain, and require solving a lot of extra problems.
+-Although building the information required is expensive compared to the latencies required of the requests, it is fairly minor compared to the startup times of an editor, so it is expected that rebuilding the information when gopls is restarted will be acceptable.
+-
+-The advantage gained from this is that gopls becomes stateless across restarts which means if it has issues or gets its state confused, a simple restart will often fix the problem.
+-It also means that when users report problems, the entire state of the on disk cache is not needed to diagnose and reproduce the issue.
+-
+-### Communication: *stdin/stdout JSON*
+-
+-The LSP specification defines the JSON messages that are normally used, but it does not define how those message should be sent, and there are implementations of the LSP that do not use JSON (for instance, Protocol buffers are an option).
+-
+-The constraints on gopls are that it must be easy to integrate into *every editor* on *all operating systems*, and that it should not have large external dependencies.
+-
+-JSON is part of the Go standard library, and is also the native language of LSP, so it makes the most sense. By far the best supported communication mechanism is the standard input and output of a process, and the common client implementations all have ways of using [JSON rpc 2] in this mode. There were no complete and low dependency implementations of this protocol in Go, but it is a fairly small protocol on top of the JSON library that can be implemented with a moderate effort, and would be a generally useful library to have anyway.
+-
+-In the future it is expected to run in separated client server mode, so writing it in a way that could use sockets instead of stdin/stdout from the start was the best way to make sure it remained possible. It was also a huge debugging aid to be able to run the gopls server by hand and watch/debug it outside the editor.
+-
+-### Running other tools: *no*
+-
+-
+-
+-## Features
+-
+-
+-
+-There is a set of features that gopls needs to expose to be a comprehensive IDE solution.
+-The following is the minimum set of features, along with their existing solutions and how they should map to the LSP.
+-
+-### Introspection
+-
+-Introspection features tell developers information about their code while they work. They do not make or suggest changes.
+-
+----
+-Diagnostics | Static analysis results of the code, including compilation and lint errors
+------------ | ---
+-Requires | Full go/analysis run, which needs full AST, type and SSA information
+-LSP | [`textDocument/publishDiagnostics`]
+-Previous | `go build`, `go vet`, `golint`, [errcheck], [staticcheck]
+-| | This is one of the most important IDE features, allowing fast turn around without having to run compilers and checkers in the shell. Often used to power problem lists, gutter markers and squiggle underlines in the IDE. There is some complicated design work to do in order to let users customize the set of checks being run, preferably without having to recompile the main LSP binary.
+-
+----
+-Hover | Information about the code under the cursor.
+--------- | ---
+-Requires | AST and type information for the file and all dependencies
+-LSP | [`textDocument/hover`]
+-Previous | [godoc], [gogetdoc]
+-| | Used when reading code to display information known to the compiler but not always obvious from the code. For instance it may return the types of identifiers, or the documentation.
+-
+----
+-Signature help | Function parameter information and documentation
+--------------- | ---
+-Requires | AST and type information for the file and all dependencies
+-LSP | [`textDocument/signatureHelp`]
+-Previous | [gogetdoc]
+-| | As a function call is being typed into code, it is helpful to know the parameters of that call to enable the developer to call it correctly.
+-
+-### Navigation
+-
+-Navigation features are designed to make it easier for a developer to find their way round a code base.
+-
+----
+-Definition | Select an identifier, and jump to the code where that identifier was defined.
+----------- | ---
+-Requires | Full type information for file and all dependencies
+-LSP | [`textDocument/declaration`]
+-| | [`textDocument/definition`]
+-| | [`textDocument/typeDefinition`]
+-Previous | [godef] |
+-| | Asking the editor to open the place where a symbol was defined is one of the most commonly used code navigation tools inside an IDE when available. It is especially valuable when exploring an unfamiliar code base. Due to a limitation of the compiler output, it is not possible to use the binary data for this task (specifically it does not know column information) and thus it must parse from source.
+-
+----
+-Implementation | Reports the types that implement an interface
+--------------- | ---
+-Requires | Full workspace type knowledge
+-LSP | [`textDocument/implementation`]
+-Previous | [impl]
+-| | This feature is hard to scale up to large code bases, and is going to take thought to get right. It may be feasible to implemented a more limited form in the meantime.
+-
+----
+-Document symbols | Provides the set of top level symbols in the current file.
+----------------- | ---
+-Requires | AST of the current file only
+-LSP | [`textDocument/documentSymbol`]
+-Previous | [go-outline], [go-symbols]
+-| | Used to drive things like outline mode.
+-
+----
+-References | Find all references to the symbol under the cursor.
+----------- | ---
+-Requires | AST and type information for the **reverse** transitive closure
+-LSP | [`textDocument/references`]
+-Previous | [guru]
+-| | This requires knowledge of every package that could possible depend on any packages the current file is part of. In the past this has been implemented either by global knowledge, which does not scale, or by specifying a "scope" which confused users to the point where they just did not use the tools. gopls is probably going to need a more powerful solution in the long term, but to start with automatically limiting the scope may produce acceptable results. This would probably be the module if known, or some sensible parent directory otherwise.
+-
+----
+-Folding | Report logical hierarchies of blocks
+--------- | ---
+-Requires | AST of the current file only
+-LSP | [`textDocument/foldingRange`]
+-Previous | [go-outline]
+-| | This is normally used to provide expand and collapse behavior in editors.
+-
+----
+-Selection | Report regions of logical selection around the cursor
+---------- | ---
+-Requires | AST of the current file only
+-LSP | [`textDocument/selectionRange`]
+-Previous | [guru]
+-| | Used in editor features like expand selection.
+-
+-
+-### Edit assistance
+-
+-These features suggest or apply edits to the code for the user, including refactoring features, for which there are many potential use cases.
+-Refactoring is one of the places where Go tools could potentially be very strong, but have not been so far, and thus there is huge potential for improvements in the developer experience.
+-There is not yet a clear understanding of the kinds of refactoring people need or how they should express them however, and there are weaknesses in the LSP protocol around this.
+-This means it may be much more of a research project.
+-
+-
+----
+-Format | Fix the formatting of the file
+--------- | ---
+-Requires | AST of current file
+-LSP | [`textDocument/formatting`]
+-| | [`textDocument/rangeFormatting`]
+-| | [`textDocument/onTypeFormatting`]
+-Previous | [gofmt], [goimports], [goreturns]
+-| | It will use the standard format package. Current limitations are that it does not work on malformed code. It may need some very careful changes to the formatter to allow for formatting an invalid AST or changes to force the AST to a valid mode. These changes would improve range and file mode as well, but are basically vital to onTypeFormatting
+-
+----
+-Imports | Rewrite the imports block automatically to match the symbols used.
+--------- | ---
+-Requires | AST of the current file and full symbol knowledge for all candidate packages.
+-LSP | [`textDocument/codeAction`]
+-Previous | [goimports], [goreturns]
+-| | This needs knowledge of packages that are not yet in use, and the ability to find those packages by name. It also needs exported symbol information for all the packages it discovers. It should be implemented using the standard imports package, but there may need to be exposed a more fine grained API than just a file rewrite for some of the interactions.
+-
+----
+-Autocompletion | Makes suggestions to complete the entity currently being typed.
+--------------- | ---
+-Requires | AST and type information for the file and all dependencies Also full exported symbol knowledge for all packages.
+-LSP | [`textDocument/completion`]
+-| | [`completionItem/resolve`]
+-Previous | [gocode]
+-| | Autocomplete is one of the most complicated features, and the more it knows the better its suggestions can be. For instance it can autocomplete into packages that are not yet being imported if it has their public symbols. It can make better suggestions of options if it knows what kind of program you are writing. It can suggest better arguments if it knows how you normally call a function. It can suggest entire patterns of code if it knows they are common. Unlike many other features, which have a specific task, and once it is doing that task the feature is done, autocomplete will never be finished. Balancing and improving both the candidates and how they are ranked will be a research problem for a long time to come.
+-
+----
+-Rename | Rename an identifier
+--------- | ---
+-Requires | AST and type information for the **reverse** transitive closure
+-LSP | [`textDocument/rename`]
+-| | [`textDocument/prepareRename`]
+-Previous | golang.org/x/tools/cmd/gorename
+-| | This uses the same information that find references does, with all the same problems and limitations. It is slightly worse because the changes it suggests make it intolerant of incorrect results. It is also dangerous using it to change the public API of a package.
+-
+----
+-Suggested fixes | Suggestions that can be manually or automatically accepted to change the code
+---------------- | ---
+-Requires | Full go/analysis run, which needs full AST, type and SSA information
+-LSP | [`textDocument/codeAction`]
+-Previous | N/A
+-| | This is a brand new feature powered by the new go/analysis engine, and it should allow a huge amount of automated refactoring.
+-
+-[LSP specification]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/
+-[talk]: https://www.youtube.com/watch?v=EFJfdWzBHwE
+-[slides]: https://github.com/gophercon/2019-talks/blob/master/RebeccaStambler-GoPleaseStopBreakingMyEditor/slides.pdf "Go, please stop breaking my editor!"
+-[JSON rpc 2]: https://www.jsonrpc.org/specification
+-
+-[errcheck]: https://github.com/kisielk/errcheck
+-[go-outline]: https://github.com/lukehoban/go-outline
+-[go-symbols]: https://github.com/acroca/go-symbols
+-[gocode]: https://github.com/stamblerre/gocode
+-[godef]: https://github.com/rogpeppe/godef
+-[godoc]: https://golang.org/cmd/godoc
+-[gofmt]: https://golang.org/cmd/gofmt
+-[gogetdoc]: https://github.com/zmb3/gogetdoc
+-[goimports]: https://pkg.go.dev/golang.org/x/tools/cmd/goimports
+-[goreturns]: https://github.com/sqs/goreturns
+-[gotags]: https://github.com/jstemmer/gotags
+-[guru]: https://pkg.go.dev/golang.org/x/tools/cmd/guru
+-[impl]: https://github.com/josharian/impl
+-[staticcheck]: https://staticcheck.io/docs/
+-[go/types]: https://golang.org/pkg/go/types/
+-[go/ast]: https://golang.org/pkg/go/ast/
+-[go/token]: https://golang.org/pkg/go/token/
+-
+-[`completionItem/resolve`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#completionItem_resolve
+-[`textDocument/codeAction`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_codeAction
+-[`textDocument/completion`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_completion
+-[`textDocument/declaration`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_declaration
+-[`textDocument/definition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_definition
+-[`textDocument/documentLink`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentLink
+-[`textDocument/documentSymbol`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentSymbol
+-[`textDocument/foldingRange`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_foldingRange
+-[`textDocument/formatting`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_formatting
+-[`textDocument/highlight`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_highlight
+-[`textDocument/hover`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_hover
+-[`textDocument/implementation`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_implementation
+-[`textDocument/onTypeFormatting`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_onTypeFormatting
+-[`textDocument/prepareRename`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_prepareRename
+-[`textDocument/publishDiagnostics`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_publishDiagnostics
+-[`textDocument/rangeFormatting`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_rangeFormatting
+-[`textDocument/references`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_references
+-[`textDocument/rename`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_rename
+-[`textDocument/selectionRange`]:https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_selectionRange
+-[`textDocument/signatureHelp`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_signatureHelp
+-[`textDocument/typeDefinition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_typeDefinition
+-[`workspace/didChangeWatchedFiles`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#workspace_didChangeWatchedFiles
+diff -urN a/gopls/doc/design/implementation.md b/gopls/doc/design/implementation.md
+--- a/gopls/doc/design/implementation.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/design/implementation.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,172 +0,0 @@
+----
+-title: "Gopls: Implementation"
+----
+-
+-Last major update: Jan 16 2024
+-
+-This doc presents a high-level overview of the structure of gopls to
+-help new contributors find their way. It is not intended to be a
+-complete description of the implementation, nor even of any key
+-components; for that, the package documentation (linked below) and
+-other comments within the code are a better guide.
+-
+-The diagram below shows selected components of the gopls module and
+-their relationship to each other according to the Go import graph.
+-Tests and test infrastructure are not shown, nor are utility packages,
+-nor packages from the [x/tools] module. For brevity, packages are
+-referred to by their last segment, which is usually unambiguous.
+-
+-The height of each blob corresponds loosely to its technical depth.
+-Some blocks are wide and shallow, such as [protocol], which declares
+-Go types for the entire LSP protocol. Others are deep, such as [cache]
+-and [golang], as they contain a lot of dense logic and algorithms.
+-
+-
+-
+-
+-Starting from the bottom, we'll describe the various components.
+-
+-The lowest layer defines the request and response types of the
+-Language Server Protocol:
+-
+-- The [protocol] package defines the standard protocol; it is mostly
+- generated mechanically from the schema definition provided by
+- Microsoft.
+- The most important type is DocumentURI, which represents a `file:`
+- URL that identifies a client editor document. It also provides
+- `Mapper`, which maps between the different coordinate systems used
+- for source positions: UTF-8, UTF-16, and token.Pos.
+-
+-- The [command] package defines Gopls's non-standard commands, which
+- are all invoked through the `workspace/executeCommand` extension
+- mechanism. These commands are typically returned by the server as
+- continuations of Code Actions or Code Lenses; most clients do not
+- construct calls to them directly.
+-
+-The next layer defines a number of important and very widely used data structures:
+-
+-- The [file] package defines the primary abstractions of a client
+- file: its `Identity` (URI and content hash), and its `Handle` (which
+- additionally provides the version and content of a particular
+- snapshot of the file.
+-
+-- The [parsego] package defines `File`, the parsed form of a Go source
+- file, including its content, syntax tree, and coordinary mappings
+- (Mapper and token.File). The package performs various kinds of tree
+- repair to work around error-recovery shortcomings of the Go parser.
+-
+-- The [metadata] package defines `Package`, an abstraction of the
+- metadata of a Go package, similar to the output of `go list -json`.
+- Metadata is produced from [go/packages], which takes
+- care of invoking `go list`. (Users report that it works to some extent
+- with a GOPACKAGESDRIVER for Bazel, though we maintain no tests for this
+- scenario.)
+-
+- The package also provides `Graph`, the complete import graph for a
+- workspace; each graph node is a `Package`.
+-
+-The [settings] layer defines the data structure (effectively a large
+-tree) for gopls configuration options, along with its JSON encoding.
+-
+-The [cache] layer is the largest and most complex component of gopls.
+-It is concerned with state management, dependency analysis, and invalidation:
+-the `Session` of communication with the client;
+-the `Folder`s that the client has opened;
+-the `View` of a particular workspace tree with particular build
+-options;
+-the `Snapshot` of the state of all files in the workspace after a
+-particular edit operation;
+-the contents of all files, whether saved to disk (`DiskFile`) or
+-edited and unsaved (`Overlay`);
+-the `Cache` of in-memory memoized computations,
+-such as parsing go.mod files or build the symbol index;
+-and the `Package`, which holds the results of type checking a package
+-from Go syntax.
+-
+-The cache layer depends on various auxiliary packages, including:
+-
+-- The [filecache] package, which manages gopls' persistent, transactional,
+- file-based key/value store.
+-
+-- The [xrefs], [methodsets], and [typerefs] packages define algorithms
+- for constructing indexes of information derived from type-checking,
+- and for encoding and decoding these serializable indexes in the file
+- cache.
+-
+- Together these packages enable the fast restart, reduced memory
+- consumption, and synergy across processes that were delivered by the
+- v0.12 redesign and described in ["Scaling gopls for the growing Go
+- ecosystem"](https://go.dev/blog/gopls-scalability).
+-
+-The cache also defines gopls's [go/analysis] driver, which runs
+-modular analysis (similar to `go vet`) across the workspace.
+-Gopls also includes a number of analysis passes that are not part of vet.
+-
+-The next layer defines four packages, each for handling files in a
+-particular language:
+-[mod] for go.mod files;
+-[work] for go.work files;
+-[template] for files in `text/template` syntax; and
+-[golang], for files in Go itself.
+-This package, by far the largest, provides the main features of gopls:
+-navigation, analysis, and refactoring of Go code.
+-As most users imagine it, this package _is_ gopls.
+-
+-The [server] package defines the LSP service implementation, with one
+-handler method per LSP request type. Each handler switches on the type
+-of the file and dispatches to one of the four language-specific
+-packages.
+-
+-The [lsprpc] package connects the service interface to our [jsonrpc2](https://www.jsonrpc.org/specification) server.
+-
+-Bear in mind that the diagram is a dependency graph, a "static"
+-viewpoint of the program's structure. A more dynamic viewpoint would
+-order the packages based on the sequence in which they are encountered
+-during processing of a particular request; in such a view, the bottom
+-layer would represent the "wire" (protocol and command), the next
+-layer up would hold the RPC-related packages (lsprpc and server), and
+-features (e.g. golang, mod, work, template) would be at the top.
+-
+-
+-
+-The [cmd] package defines the command-line interface of the `gopls`
+-command, around which gopls's main package is just a trivial wrapper.
+-It is usually run without arguments, causing it to start a server and
+-listen indefinitely.
+-It also provides a number of subcommands that start a server, make a
+-single request to it, and exit, providing traditional batch-command
+-access to server functionality. These subcommands are primarily
+-provided as a debugging aid; but see https://go.dev/issue/63693.
+-
+-[cache]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache
+-[cmd]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cmd
+-[command]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/protocol/command
+-[debug]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/debug
+-[file]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/file
+-[filecache]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/filecache
+-[go/analysis]: https://pkg.go.dev/golang.org/x/tools@master/go/analysis
+-[go/packages]: https://pkg.go.dev/golang.org/x/tools@master/go/packages
+-[gopls]: https://pkg.go.dev/golang.org/x/tools/gopls@master
+-[jsonrpc]: https://pkg.go.dev/golang.org/x/tools@master/internal/jsonrpc
+-[jsonrpc2]: https://pkg.go.dev/golang.org/x/tools@master/internal/jsonrpc2
+-[lsprpc]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/lsprpc
+-[memoize]: https://github.com/golang/tools/tree/master/internal/memoize
+-[metadata]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/metadata
+-[methodsets]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/methodsets
+-[mod]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/mod
+-[parsego]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/parsego
+-[protocol]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/protocol
+-[server]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/server
+-[settings]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/settings
+-[golang]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/golang
+-[template]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/template
+-[typerefs]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/typerefs
+-[work]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/work
+-[x/tools]: https://github.com/golang/tools@master
+-[xrefs]: https://pkg.go.dev/golang.org/x/tools/gopls@master/internal/cache/xrefs
+diff -urN a/gopls/doc/design/integrating.md b/gopls/doc/design/integrating.md
+--- a/gopls/doc/design/integrating.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/design/integrating.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,93 +0,0 @@
+----
+-title: "Documentation for plugin authors"
+----
+-
+-If you are integrating `gopls` into an editor by writing an editor plugin, there are quite a few semantics of the communication between the editor and `gopls` that are not specified by the [LSP specification].
+-
+-We attempt to document those details along with any other information that has been helpful to other plugin authors here.
+-
+-If you are implementing a plugin yourself and have questions this page does not answer, please reach out to us to ask, and then also contribute your findings back to this page.
+-
+-## Supported features
+-
+-For the most part you should look at the [Index of features](../features/)
+-to know whether gopls supports a feature.
+-
+-For a truly authoritative answer you should check the [result][InitializeResult] of the [initialize] request, where gopls enumerates its support in the [ServerCapabilities].
+-
+-
+-## Positions and ranges
+-
+-Many LSP requests pass position or range information. This is described in the [LSP specification][lsp-text-documents]:
+-
+-> A position inside a document (see Position definition below) is expressed as a zero-based line and character offset. The offsets are based on a UTF-16 string representation. So a string of the form a𐐀b the character offset of the character a is 0, the character offset of 𐐀 is 1 and the character offset of b is 3 since 𐐀 is represented using two code units in UTF-16.
+-
+-This means that integrators will need to calculate UTF-16 based column offsets.
+-Use `protocol.Mapper` for all the conversions.
+-
+-## Edits
+-
+-In order to deliver changes from gopls to the editor, the LSP supports arrays of [`TextEdit`][lsp-textedit]s in responses.
+-The spec specifies exactly how these should be applied:
+-
+-> All text edits ranges refer to positions in the original document. Text edits ranges must never overlap, that means no part of the original document must be manipulated by more than one edit. However, it is possible that multiple edits have the same start position: multiple inserts, or any number of inserts followed by a single remove or replace edit. If multiple inserts have the same position, the order in the array defines the order in which the inserted strings appear in the resulting text.
+-
+-All `[]TextEdit` are sorted such that applying the array of deltas received in reverse order achieves the desired result that holds with the spec.
+-
+-## Errors
+-
+-Various error codes are described in the [LSP specification][lsp-response]. We are still determining what it means for a method to return an error; are errors only for low-level LSP/transport issues or can other conditions cause errors to be returned? See some of this discussion on [#31526].
+-
+-The method chosen is currently influenced by the exact treatment in the currently popular editor integrations. It may well change, and ideally would become more coherent across requests.
+-
+-* [`textDocument/codeAction`]: Return error if there was an error computing code actions.
+-* [`textDocument/completion`]: Log errors, return empty result list.
+-* [`textDocument/definition`]: Return error if there was an error computing the definition for the position.
+-* [`textDocument/typeDefinition`]: Return error if there was an error computing the type definition for the position.
+-* [`textDocument/formatting`]: Return error if there was an error formatting the file.
+-* [`textDocument/highlight`]: Log errors, return empty result.
+-* [`textDocument/hover`]: Return empty result.
+-* [`textDocument/documentLink`]: Log errors, return nil result.
+-* [`textDocument/publishDiagnostics`]: Log errors if there were any while computing diagnostics.
+-* [`textDocument/references`]: Log errors, return empty result.
+-* [`textDocument/rename`]: Return error if there was an error computing renames.
+-* [`textDocument/signatureHelp`]: Log errors, return nil result.
+-* [`textDocument/documentSymbols`]: Return error if there was an error computing document symbols.
+-
+-## Watching files
+-
+-It is fairly normal for files that affect `gopls` to be modified outside of the editor it is associated with.
+-
+-For instance, files that are needed to do correct type checking are modified by switching branches in git, or updated by a code generator.
+-
+-Monitoring files inside gopls directly has a lot of awkward problems, but the [LSP specification] has methods that allow gopls to request that the client notify it of file system changes, specifically [`workspace/didChangeWatchedFiles`].
+-This is currently being added to gopls by a community member, and tracked in [#31553]
+-
+-[InitializeResult]: https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol#InitializeResult
+-[ServerCapabilities]: https://pkg.go.dev/golang.org/x/tools/gopls/internal/protocol#ServerCapabilities
+-[`golang.org/x/tools/gopls/internal/protocol`]: https://pkg.go.dev/golang.org/x/tools/internal/protocol#NewPoint
+-
+-[LSP specification]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/
+-[lsp-response]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#response-message
+-[initialize]: https://microsoft.github.io/language-server-protocol/specifications/specification-3-14/#initialize
+-[lsp-text-documents]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#text-documents
+-[lsp-textedit]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textedit
+-
+-[`textDocument/codeAction`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_codeAction
+-[`textDocument/completion`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_completion
+-[`textDocument/definition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_definition
+-[`textDocument/typeDefinition`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_typeDefinition
+-[`textDocument/formatting`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_formatting
+-[`textDocument/highlight`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_highlight
+-[`textDocument/hover`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_hover
+-[`textDocument/documentLink`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentLink
+-[`textDocument/publishDiagnostics`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_publishDiagnostics
+-[`textDocument/references`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_references
+-[`textDocument/rename`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_rename
+-[`textDocument/signatureHelp`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_signatureHelp
+-[`textDocument/documentSymbols`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#textDocument_documentSymbols
+-[`workspace/didChangeWatchedFiles`]: https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-14.md#workspace_didChangeWatchedFiles
+-
+-[#31080]: https://github.com/golang/go/issues/31080
+-[#31553]: https://github.com/golang/go/issues/31553
+-[#31526]: https://github.com/golang/go/issues/31526
+diff -urN a/gopls/doc/editor/emacs.md b/gopls/doc/editor/emacs.md
+--- a/gopls/doc/editor/emacs.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/editor/emacs.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,190 +0,0 @@
+----
+-title: "Gopls: Using Emacs"
+----
+-
+-## Installing `gopls`
+-
+-To use `gopls` with Emacs, you must first
+-[install the `gopls` executable](../index.md#installation) and ensure that the directory
+-containing the resulting binary (either `$(go env GOBIN)` or `$(go env
+-GOPATH)/bin`) is in your `PATH`.
+-
+-## Choosing an Emacs LSP client
+-
+-To use `gopls` with Emacs, you will need to choose and install an Emacs LSP
+-client package. Two popular client packages are [LSP Mode] and [Eglot].
+-
+-LSP Mode takes a batteries-included approach, with many integrations enabled
+-“out of the box” and several additional behaviors provided by `lsp-mode` itself.
+-
+-Eglot takes a minimally-intrusive approach, focusing on smooth integration with
+-other established packages. It provides a few of its own `eglot-` commands but
+-no additional keybindings by default.
+-
+-Once you have selected which client you want to use, install it per the packages
+-instructions: see [Eglot 1-2-3](https://github.com/joaotavora/eglot#1-2-3) or
+-[LSP Mode Installation](https://emacs-lsp.github.io/lsp-mode/page/installation/).
+-
+-## Common configuration
+-
+-Both Eglot and LSP Mode can integrate with popular packages in the Emacs
+-ecosystem:
+-
+-* The built-in [`xref`] package provides cross-references.
+-* The built-in [Flymake] package provides an on-the-fly diagnostic overlay.
+-* [Company] mode displays code completion candidates (with a richer UI than
+- the built-in [`completion-at-point`]).
+-
+-Eglot provides documentation using the built-in [ElDoc] minor mode, while LSP
+-Mode by default provides documentation using its own [`lsp-ui`] mode.
+-
+-Eglot by default locates the project root using the [`project`] package. In LSP
+-Mode, this behavior can be configured using the `lsp-auto-guess-root` setting.
+-
+-## Configuring LSP Mode
+-
+-### Loading LSP Mode in `.emacs`
+-
+-```elisp
+-(require 'lsp-mode)
+-(add-hook 'go-mode-hook #'lsp-deferred)
+-
+-;; Set up before-save hooks to format buffer and add/delete imports.
+-;; Make sure you don't have other gofmt/goimports hooks enabled.
+-(defun lsp-go-install-save-hooks ()
+- (add-hook 'before-save-hook #'lsp-format-buffer t t)
+- (add-hook 'before-save-hook #'lsp-organize-imports t t))
+-(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)
+-```
+-
+-### Configuring `gopls` via LSP Mode
+-
+-See [settings](../settings) for information about available gopls settings.
+-
+-Stable gopls settings have corresponding configuration variables in `lsp-mode`.
+-For example, `(setq lsp-gopls-use-placeholders nil)` will disable placeholders
+-in completion snippets. See [`lsp-go`] for a list of available variables.
+-
+-Experimental settings can be configured via `lsp-register-custom-settings`:
+-
+-```lisp
+-(lsp-register-custom-settings
+- '(("gopls.completeUnimported" t t)
+- ("gopls.staticcheck" t t)))
+-```
+-
+-Note that after changing settings you must restart gopls using e.g. `M-x
+-lsp-restart-workspace`.
+-
+-## Configuring Eglot
+-
+-### Configuring `project` for Go modules in `.emacs`
+-
+-Eglot uses the built-in `project` package to identify the LSP workspace for a
+-newly-opened buffer. The `project` package does not natively know about `GOPATH`
+-or Go modules. Fortunately, you can give it a custom hook to tell it to look for
+-the nearest parent `go.mod` file (that is, the root of the Go module) as the
+-project root.
+-
+-```elisp
+-(require 'project)
+-
+-(defun project-find-go-module (dir)
+- (when-let ((root (locate-dominating-file dir "go.mod")))
+- (cons 'go-module root)))
+-
+-(cl-defmethod project-root ((project (head go-module)))
+- (cdr project))
+-
+-(add-hook 'project-find-functions #'project-find-go-module)
+-```
+-
+-### Loading Eglot in `.emacs`
+-
+-```elisp
+-;; Optional: load other packages before eglot to enable eglot integrations.
+-(require 'company)
+-(require 'yasnippet)
+-
+-(require 'go-mode)
+-(require 'eglot)
+-(add-hook 'go-mode-hook 'eglot-ensure)
+-
+-;; Optional: install eglot-format-buffer as a save hook.
+-;; The depth of -10 places this before eglot's willSave notification,
+-;; so that notification reports the actual contents that will be saved.
+-(defun eglot-format-buffer-before-save ()
+- (add-hook 'before-save-hook #'eglot-format-buffer -10 t))
+-(add-hook 'go-mode-hook #'eglot-format-buffer-before-save)
+-```
+-
+-Use `M-x eglot-upgrade-eglot` to upgrade to the latest version of
+-Eglot.
+-
+-### Configuring `gopls` via Eglot
+-
+-See [settings](../settings) for information about available gopls settings.
+-
+-LSP server settings are controlled by the `eglot-workspace-configuration`
+-variable, which can be set either globally in `.emacs` or in a `.dir-locals.el` file in the project root.
+-
+-`.emacs`:
+-```elisp
+-(setq-default eglot-workspace-configuration
+- '((:gopls .
+- ((staticcheck . t)
+- (matcher . "CaseSensitive")))))
+-```
+-
+-`.dir-locals.el`:
+-```elisp
+-((nil (eglot-workspace-configuration . ((gopls . ((staticcheck . t)
+- (matcher . "CaseSensitive")))))))
+-```
+-
+-### Organizing imports with Eglot
+-
+-`gopls` provides the import-organizing functionality of `goimports` as an LSP
+-code action, which you can invoke as needed by running `M-x eglot-code-actions`
+-(or a key of your choice bound to the `eglot-code-actions` function) and
+-selecting `Organize Imports` at the prompt.
+-
+-To automatically organize imports before saving, add a hook:
+-
+-```elisp
+-(add-hook 'before-save-hook
+- (lambda ()
+- (call-interactively 'eglot-code-action-organize-imports))
+- nil t)
+-```
+-
+-## Troubleshooting
+-
+-Common errors:
+-
+-* When prompted by Emacs for your project folder, if you are using modules you
+- must select the module's root folder (i.e. the directory with the "go.mod").
+- If you are using GOPATH, select your $GOPATH as your folder.
+-* Emacs must have your environment set properly (PATH, GOPATH, etc). You can
+- run `M-x getenv PATH ` to see if your PATH is set in Emacs. If
+- not, you can try starting Emacs from your terminal, using [this
+- package][exec-path-from-shell], or moving your shell config from `.bashrc`
+- into `.profile` and logging out and back in.
+-* Make sure only one LSP client mode is installed. (For example, if using
+- `lsp-mode`, ensure that you are not _also_ enabling `eglot`.)
+-* Look for errors in the `*lsp-log*` buffer or run `M-x eglot-events-buffer`.
+-* Ask for help in the `#emacs` channel on the [Gophers slack].
+-
+-[LSP Mode]: https://emacs-lsp.github.io/lsp-mode/
+-[Eglot]: https://github.com/joaotavora/eglot/blob/master/README.md
+-[`xref`]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html
+-[Flymake]: https://www.gnu.org/software/emacs/manual/html_node/flymake/Using-Flymake.html#Using-Flymake
+-[Company]: https://company-mode.github.io/
+-[`completion-at-point`]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion-in-Buffers.html
+-[ElDoc]: https://elpa.gnu.org/packages/eldoc.html
+-[`lsp-ui`]: https://emacs-lsp.github.io/lsp-ui/
+-[`lsp-go`]: https://github.com/emacs-lsp/lsp-mode/blob/master/clients/lsp-go.el
+-[`use-package`]: https://github.com/jwiegley/use-package
+-[`exec-path-from-shell`]: https://github.com/purcell/exec-path-from-shell
+-[settings]: settings.md
+-[Gophers slack]: https://invite.slack.golangbridge.org/
+diff -urN a/gopls/doc/editor/helix.md b/gopls/doc/editor/helix.md
+--- a/gopls/doc/editor/helix.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/editor/helix.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,53 +0,0 @@
+----
+-title: "Gopls: Using Helix"
+----
+-
+-Configuring `gopls` to work with Helix is rather straightforward. Install `gopls`, and then add it to the `PATH` variable. If it is in the `PATH` variable, Helix will be able to detect it automatically.
+-
+-The documentation explaining how to install the default language servers for Helix can be found [here](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers)
+-
+-## Installing `gopls`
+-
+-The first step is to install `gopls` on your machine.
+-You can follow installation instructions [here](https://github.com/golang/tools/tree/master/gopls#installation).
+-
+-## Setting your path to include `gopls`
+-
+-Set your `PATH` environment variable to point to `gopls`.
+-If you used `go install` to download `gopls`, it should be in `$GOPATH/bin`.
+-If you don't have `GOPATH` set, you can use `go env GOPATH` to find it.
+-
+-## Additional information
+-
+-You can find more information about how to set up the LSP formatter [here](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers#autoformatting).
+-
+-It is possible to use `hx --health go` to see that the language server is properly set up.
+-
+-### Configuration
+-
+-The settings for `gopls` can be configured in the `languages.toml` file.
+-The official Helix documentation for this can be found [here](https://docs.helix-editor.com/languages.html)
+-
+-Configuration pertaining to `gopls` should be in the table `language-server.gopls`.
+-
+-#### How to set flags
+-
+-To set flags, add them to the `args` array in the `language-server.gopls` section of the `languages.toml` file.
+-
+-#### How to set LSP configuration
+-
+-Configuration options can be set in the `language-server.gopls.config` section of the `languages.toml` file, or in the `config` key of the `language-server.gopls` section of the `languages.toml` file.
+-
+-#### A minimal config example
+-
+-In the `~/.config/helix/languages.toml` file, the following snippet would set up `gopls` with a logfile located at `/tmp/gopls.log` and enable staticcheck.
+-
+-```toml
+-[language-server.gopls]
+-command = "gopls"
+-args = ["-logfile=/tmp/gopls.log", "serve"]
+-[language-server.gopls.config]
+-"ui.diagnostic.staticcheck" = true
+-```
+-
+-
+diff -urN a/gopls/doc/editor/sublime.md b/gopls/doc/editor/sublime.md
+--- a/gopls/doc/editor/sublime.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/editor/sublime.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,83 +0,0 @@
+----
+-title: "Gopls: Using Sublime Text"
+----
+-
+-Use the [LSP] package. After installing it using Package Control, do the following:
+-
+-* Open the **Command Palette**
+-* Find and run the command **LSP: Enable Language Server Globally**
+-* Select the **gopls** item. Be careful not to select the similarly named *golsp* by mistake.
+-
+-Finally, you should familiarise yourself with the LSP package's *Settings* and *Key Bindings*. Find them under the menu item **Preferences > Package Settings > LSP**.
+-
+-## Examples
+-Minimal global LSP settings, that assume **gopls** and **go** appear on the PATH seen by Sublime Text:
+-```
+-{
+- "clients": {
+- "gopls": {
+- "enabled": true,
+- }
+- }
+-}
+-```
+-
+-Global LSP settings that supply a specific PATH for finding **gopls** and **go**, as well as some settings for Sublime LSP itself:
+-```
+-{
+- "clients": {
+- "gopls": {
+- "enabled": true,
+- "env": {
+- "PATH": "/path/to/your/go/bin",
+- }
+- }
+- },
+- // Recommended by https://agniva.me/gopls/2021/01/02/setting-up-gopls-sublime.html
+- // except log_stderr mentioned there is no longer recognized.
+- "show_references_in_quick_panel": true,
+- "log_debug": true,
+- // These two are recommended by LSP-json as replacement for deprecated only_show_lsp_completions
+- "inhibit_snippet_completions": true,
+- "inhibit_word_completions": true,
+- }
+- ```
+-
+-LSP and gopls settings can also be adjusted on a per-project basis to override global settings.
+-```
+-{
+- "folders": [
+- {
+- "path": "/path/to/a/folder/one"
+- },
+- {
+- // If you happen to be working on Go itself, this can be helpful; go-dev/bin should be on PATH.
+- "path": "/path/to/your/go-dev/src/cmd"
+- }
+- ],
+- "settings": {
+- "LSP": {
+- "gopls": {
+- // To use a specific version of gopls with Sublime Text LSP (e.g., to try new features in development)
+- "command": [
+- "/path/to/your/go/bin/gopls"
+- ],
+- "env": {
+- "PATH": "/path/to/your/go-dev/bin:/path/to/your/go/bin",
+- "GOPATH": "",
+- },
+- "settings": {
+- "experimentalWorkspaceModule": true
+- }
+- }
+- },
+- // This will apply for all languages in this project that have
+- // LSP servers, not just Go, however cannot enable just for Go.
+- "lsp_format_on_save": true,
+- }
+-}
+-```
+-
+-Usually changes to these settings are recognized after saving the project file, but it may sometimes be necessary to either restart the server(s) (**Tools > LSP > Restart Servers**) or quit and restart Sublime Text itself.
+-
+-[LSP]: https://packagecontrol.io/packages/LSP
+diff -urN a/gopls/doc/editor/vim.md b/gopls/doc/editor/vim.md
+--- a/gopls/doc/editor/vim.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/editor/vim.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,236 +0,0 @@
+----
+-title: "Gopls: Using Vim or Neovim"
+----
+-
+-* [vim-go](#vimgo)
+-* [LanguageClient-neovim](#lcneovim)
+-* [Ale](#ale)
+-* [vim-lsp](#vimlsp)
+-* [vim-lsc](#vimlsc)
+-* [coc.nvim](#cocnvim)
+-* [govim](#govim)
+-* [Neovim v0.5.0+](#neovim)
+- * [Installation](#neovim-install)
+- * [Custom Configuration](#neovim-config)
+- * [Imports](#neovim-imports)
+- * [Omnifunc](#neovim-omnifunc)
+- * [Additional Links](#neovim-links)
+-
+-## vim-go
+-
+-Use [vim-go] ver 1.20+, with the following configuration:
+-
+-```vim
+-let g:go_def_mode='gopls'
+-let g:go_info_mode='gopls'
+-```
+-
+-## LanguageClient-neovim
+-
+-Use [LanguageClient-neovim], with the following configuration:
+-
+-```vim
+-" Launch gopls when Go files are in use
+-let g:LanguageClient_serverCommands = {
+- \ 'go': ['gopls']
+- \ }
+-" Run gofmt on save
+-autocmd BufWritePre *.go :call LanguageClient#textDocument_formatting_sync()
+-```
+-
+-## Ale
+-
+-Use [ale]:
+-
+-```vim
+-let g:ale_linters = {
+- \ 'go': ['gopls'],
+- \}
+-```
+-
+-see [this issue][ale-issue-2179]
+-
+-## vim-lsp
+-
+-Use [prabirshrestha/vim-lsp], with the following configuration:
+-
+-```vim
+-augroup LspGo
+- au!
+- autocmd User lsp_setup call lsp#register_server({
+- \ 'name': 'gopls',
+- \ 'cmd': {server_info->['gopls']},
+- \ 'whitelist': ['go'],
+- \ })
+- autocmd FileType go setlocal omnifunc=lsp#complete
+- "autocmd FileType go nmap gd (lsp-definition)
+- "autocmd FileType go nmap ,n (lsp-next-error)
+- "autocmd FileType go nmap ,p (lsp-previous-error)
+-augroup END
+-```
+-
+-## vim-lsc
+-
+-Use [natebosch/vim-lsc], with the following configuration:
+-
+-```vim
+-let g:lsc_server_commands = {
+-\ "go": {
+-\ "command": "gopls serve",
+-\ "log_level": -1,
+-\ "suppress_stderr": v:true,
+-\ },
+-\}
+-```
+-
+-The `log_level` and `suppress_stderr` parts are needed to prevent breakage from logging. See
+-issues [#180](https://github.com/natebosch/vim-lsc/issues/180) and
+-[#213](https://github.com/natebosch/vim-lsc/issues/213).
+-
+-## coc.nvim
+-
+-Use [coc.nvim], with the following `coc-settings.json` configuration:
+-
+-```json
+- "languageserver": {
+- "go": {
+- "command": "gopls",
+- "rootPatterns": ["go.work", "go.mod", ".vim/", ".git/", ".hg/"],
+- "filetypes": ["go"],
+- "initializationOptions": {
+- "usePlaceholders": true
+- }
+- }
+- }
+-```
+-
+-If you use `go.work` files, you may want to set the
+-`workspace.workspaceFolderCheckCwd` option. This will force coc.nvim to search
+-parent directories for `go.work` files, even if the current open directory has
+-a `go.mod` file. See the
+-[coc.nvim documentation](https://github.com/neoclide/coc.nvim/wiki/Using-workspaceFolders)
+-for more details.
+-
+-Other [settings](../settings) can be added in `initializationOptions` too.
+-
+-The `editor.action.organizeImport` code action will auto-format code and add missing imports. To run this automatically on save, add the following line to your `init.vim`:
+-
+-```vim
+-autocmd BufWritePre *.go :call CocAction('runCommand', 'editor.action.organizeImport')
+-```
+-
+-## govim
+-
+-In vim classic only, use the experimental [`govim`], simply follow the [install steps][govim-install].
+-
+-## Neovim v0.5.0+
+-
+-To use the new native LSP client in Neovim, make sure you
+-[install][nvim-install] Neovim v.0.5.0+,
+-the `nvim-lspconfig` configuration helper plugin, and check the
+-[`gopls` configuration section][nvim-lspconfig] there.
+-
+-### Installation
+-
+-You can use Neovim's native plugin system. On a Unix system, you can do that by
+-cloning the `nvim-lspconfig` repository into the correct directory:
+-
+-```sh
+-dir="${HOME}/.local/share/nvim/site/pack/nvim-lspconfig/opt/nvim-lspconfig/"
+-mkdir -p "$dir"
+-cd "$dir"
+-git clone 'https://github.com/neovim/nvim-lspconfig.git' .
+-```
+-
+-### Configuration
+-
+-nvim-lspconfig aims to provide reasonable defaults, so your setup can be very
+-brief.
+-
+-```lua
+-local lspconfig = require("lspconfig")
+-lspconfig.gopls.setup({})
+-```
+-
+-However, you can also configure `gopls` for your preferences. Here's an
+-example that enables `unusedparams`, `staticcheck`, and `gofumpt`.
+-
+-```lua
+-local lspconfig = require("lspconfig")
+-lspconfig.gopls.setup({
+- settings = {
+- gopls = {
+- analyses = {
+- unusedparams = true,
+- },
+- staticcheck = true,
+- gofumpt = true,
+- },
+- },
+-})
+-```
+-
+-### Imports and Formatting
+-
+-Use the following configuration to have your imports organized on save using
+-the logic of `goimports` and your code formatted.
+-
+-```lua
+-autocmd("BufWritePre", {
+- pattern = "*.go",
+- callback = function()
+- local params = vim.lsp.util.make_range_params()
+- params.context = {only = {"source.organizeImports"}}
+- -- buf_request_sync defaults to a 1000ms timeout. Depending on your
+- -- machine and codebase, you may want longer. Add an additional
+- -- argument after params if you find that you have to write the file
+- -- twice for changes to be saved.
+- -- E.g., vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, 3000)
+- local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params)
+- for cid, res in pairs(result or {}) do
+- for _, r in pairs(res.result or {}) do
+- if r.edit then
+- local enc = (vim.lsp.get_client_by_id(cid) or {}).offset_encoding or "utf-16"
+- vim.lsp.util.apply_workspace_edit(r.edit, enc)
+- end
+- end
+- end
+- vim.lsp.buf.format({async = false})
+- end
+-})
+-```
+-
+-### Omnifunc
+-
+-In Neovim v0.8.1 and later if you don't set the option `omnifunc`, it will auto
+-set to `v:lua.vim.lsp.omnifunc`. If you are using an earlier version, you can
+-configure it manually:
+-
+-```lua
+-local on_attach = function(client, bufnr)
+- -- Enable completion triggered by
+- vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
+-end
+-require('lspconfig').gopls.setup({
+- on_attach = on_attach
+-})
+-```
+-
+-### Additional Links
+-
+-* [Neovim's official LSP documentation][nvim-docs].
+-
+-[vim-go]: https://github.com/fatih/vim-go
+-[LanguageClient-neovim]: https://github.com/autozimu/LanguageClient-neovim
+-[ale]: https://github.com/w0rp/ale
+-[ale-issue-2179]: https://github.com/w0rp/ale/issues/2179
+-[prabirshrestha/vim-lsp]: https://github.com/prabirshrestha/vim-lsp/
+-[natebosch/vim-lsc]: https://github.com/natebosch/vim-lsc/
+-[natebosch/vim-lsc#180]: https://github.com/natebosch/vim-lsc/issues/180
+-[coc.nvim]: https://github.com/neoclide/coc.nvim/
+-[`govim`]: https://github.com/myitcv/govim
+-[govim-install]: https://github.com/myitcv/govim/blob/master/README.md#govim---go-development-plugin-for-vim8
+-[nvim-docs]: https://neovim.io/doc/user/lsp.html
+-[nvim-install]: https://github.com/neovim/neovim/wiki/Installing-Neovim
+-[nvim-lspconfig]: https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#gopls
+-[nvim-lspconfig-imports]: https://github.com/neovim/nvim-lspconfig/issues/115
+diff -urN a/gopls/doc/editor/zed.md b/gopls/doc/editor/zed.md
+--- a/gopls/doc/editor/zed.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/editor/zed.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,19 +0,0 @@
+----
+-title: "Gopls: Using Zed"
+----
+-
+-## Install `gopls`
+-
+-To use `gopls` with [Zed](https://zed.dev/), first
+-[install the `gopls` executable](../index.md#installation) and ensure that the directory
+-containing the resulting binary (either `$(go env GOBIN)` or `$(go env
+-GOPATH)/bin`) is in your `PATH`.
+-
+-## That's it
+-
+-Zed has a built-in LSP client and knows to run `gopls` when visiting a
+-Go source file, so most features work right out of the box.
+-
+-Zed does not yet support external `window/showDocument` requests,
+-so web-based features will not work;
+-see [Zed issue 24852](https://github.com/zed-industries/zed/discussions/24852).
+diff -urN a/gopls/doc/features/assembly.md b/gopls/doc/features/assembly.md
+--- a/gopls/doc/features/assembly.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/assembly.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,31 +0,0 @@
+----
+-title: "Gopls: Support for Go *.s assembly files"
+----
+-
+-Gopls has rudimentary support for LSP operations in Go assembly files.
+-
+-Go assembly files have a `.s` file name extension. LSP clients need to
+-be configured to recognize `.s` files as Go assembly files, since this
+-file name extension is also used for assembly files in other
+-languages. A good heuristic is that if a file named `*.s` belongs to a
+-directory containing at least one `*.go` file, then the `.s` file is
+-Go assembly, and its appropriate language server is gopls.
+-
+-Only Definition (`textDocument/definition`) requests are currently
+-supported. For example, a Definition request on the `sigpanic`
+-symbol in this file in GOROOT/src/runtime/asm.s:
+-
+-```asm
+- JMP ·sigpanic(SB)
+-```
+-
+-returns the location of the function declaration in
+-GOROOT/src/runtime/signal_unix.go:
+-
+-```go
+-//go:linkname sigpanic
+-func sigpanic() {
+-```
+-
+-See also issue https://go.dev/issue/71754, which tracks the development of LSP
+-features in Go assembly files.
+\ No newline at end of file
+diff -urN a/gopls/doc/features/completion.md b/gopls/doc/features/completion.md
+--- a/gopls/doc/features/completion.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/completion.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,5 +0,0 @@
+----
+-title: "Gopls: Completion"
+----
+-
+-TODO(https://go.dev/issue/62022): document
+diff -urN a/gopls/doc/features/diagnostics.md b/gopls/doc/features/diagnostics.md
+--- a/gopls/doc/features/diagnostics.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/diagnostics.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,328 +0,0 @@
+----
+-title: "Gopls: Diagnostics"
+----
+-
+-Gopls continuously annotates all your open files of source code with a
+-variety of diagnostics. Every time you edit a file or make a
+-configuration change, gopls asynchronously recomputes these
+-diagnostics and sends them to the client using the LSP
+-[`publishDiagnostics`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_publishDiagnostics)
+-notification, giving you real-time feedback that reduces the cost of
+-common mistakes.
+-
+-Diagnostics come from two main sources: compilation errors and analysis findings.
+-
+-- **Compilation errors** are those that you would obtain from running `go
+-build`. Gopls doesn't actually run the compiler; that would be too
+- slow. Instead it runs `go list` (when needed) to compute the
+- metadata of the compilation, then processes those packages in a similar
+- manner to the compiler front-end: reading, scanning, and parsing the
+- source files, then type-checking them. Each of these steps can
+- produce errors that gopls will surface as a diagnostic.
+-
+- The `source` field of the LSP `Diagnostic` record indicates where
+- the diagnostic came from: those with source `"go list"` come from
+- the `go list` command, and those with source `"compiler"` come from
+- gopls' parsing or type checking phases, which are similar to those
+- used in the Go compiler.
+-
+- 
+-
+- The example above shows a `string + int` addition, causes the type
+- checker to report a `MismatchedTypes` error. The diagnostic contains
+- a link to the documentation about this class of type error.
+-
+-- **Analysis findings** come from the [**Go analysis
+- framework**](https://golang.org/x/tools/go/analysis), the system
+- used by `go vet` to apply a variety of additional static checks to
+- your Go code. The best-known example is the [`printf`
+- analyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf),
+- which reports calls to [`fmt.Printf`](https://pkg.go.dev/fmt#Printf)
+- where the format "verb" doesn't match the argument, such as
+- `fmt.Printf("%d", "three")`.
+-
+- Gopls provides dozens of analyzers aggregated from a variety of
+- suites; see [Analyzers](../analyzers.md) for the complete list. The
+- `source` field of each diagnostic produced by an analyzer records
+- the name of the analyzer that produced it.
+-
+- 
+-
+- The example above shows a `printf` formatting mistake. The diagnostic contains
+- a link to the documentation for the `printf` analyzer.
+-
+-There is an optional third source of diagnostics:
+-
+-
+-
+-- **Compiler optimization details** are diagnostics that report
+- details relevant to optimization decisions made by the Go
+- compiler, such as whether a variable escapes or a slice index
+- requires a bounds check.
+-
+- Optimization decisions include:
+- whether a variable escapes, and how escape is inferred;
+- whether a nil-pointer check is implied or eliminated; and
+- whether a function can be inlined.
+-
+- This source is disabled by default but can be enabled on a
+- package-by-package basis by invoking the
+- `source.toggleCompilerOptDetails` ("{Show,Hide} compiler optimization
+- details") code action.
+-
+- Remember that the compiler's optimizer runs only on packages that
+- are transitively free from errors, so optimization diagnostics
+- will not be shown on packages that do not build.
+-
+-
+-## Recomputation of diagnostics
+-
+-By default, diagnostics are automatically recomputed each time the source files
+-are edited.
+-
+-Compilation errors in open files are updated after a very short delay
+-(tens of milliseconds) after each file change, potentially after every keystroke.
+-This ensures rapid feedback of syntax and type errors while editing.
+-
+-Compilation and analysis diagnostics for the whole workspace are much
+-more expensive to compute, so they are usually recomputed after a
+-short idle period (around 1s) following an edit.
+-
+-The [`diagnosticsDelay`](../settings.md#diagnosticsDelay) setting determines
+-this period.
+-Alternatively, diagnostics may be triggered only after an edited file
+-is saved, using the
+-[`diagnosticsTrigger`](../settings.md#diagnosticsTrigger) setting.
+-
+-When initialized with `"pullDiagnostics": true`, gopls also supports
+-["pull diagnostics"](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics),
+-an alternative mechanism for recomputing diagnostics in which the client
+-requests diagnostics from gopls explicitly using the `textDocument/diagnostic`
+-request. This feature is off by default until the performance of pull
+-diagnostics is comparable to push diagnostics.
+-
+-## Quick fixes
+-
+-Each analyzer diagnostic may suggest one or more alternative
+-ways to fix the problem by editing the code.
+-For example, when a `return` statement has too few operands,
+-the [`fillreturns`](../analyzers.md#fillreturns) analyzer
+-suggests a fix that heuristically fills in the missing ones
+-with suitable values. Applying the fix eliminates the compilation error.
+-
+-
+-
+-The screenshot above shows VS Code's Quick Fix menu for an "unused
+-parameter" analysis diagnostic with two alternative fixes.
+-(See [Remove unused parameter](transformation.md#remove-unused-parameter) for more detail.)
+-
+-Suggested fixes that are indisputably safe are [code
+-actions](transformation.md#code-actions) whose kind is
+-`"source.fixAll"`.
+-Many client editors have a shortcut to apply all such fixes.
+-
+-
+-
+-TODO(adonovan): audit all the analyzers to ensure that their
+-documentation is up-to-date w.r.t. any fixes they suggest.
+-
+-Settings:
+-
+-- The [`diagnosticsDelay`](../settings.md#diagnosticsDelay) setting determines
+- the idle period after an edit before diagnostics are recomputed.
+-- The [`diagnosticsTriggerr`](../settings.md#diagnosticsTrigger) setting determines
+- what events cause recomputation of diagnostics.
+-- The [`linkTarget`](../settings.md#linkTarget) setting specifies
+- the base URI for Go package links in the Diagnostic.CodeDescription field.
+-
+-Client support:
+-
+-- **VS Code**: Each diagnostic appears as a squiggly underline.
+- Hovering reveals the details, along with any suggested fixes.
+-- **Emacs + eglot**: Each diagnostic appears as a squiggly underline.
+- Hovering reveals the details. Use `M-x eglot-code-action-quickfix`
+- to apply available fixes; it will prompt if there are more than one.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls check file.go`
+-
+-
+-
+-### `stubMissingInterfaceMethods`: Declare missing methods of I
+-
+-When a value of a concrete type is assigned to a variable of an
+-interface type, but the concrete type does not possess all the
+-necessary methods, the type checker will report a "missing method"
+-error.
+-
+-In this situation, gopls offers a quick fix to add stub declarations
+-of all the missing methods to the concrete type so that it implements
+-the interface.
+-
+-For example, this function will not compile because the value
+-`NegativeErr{}` does not implement the "error" interface:
+-
+-```go
+-func sqrt(x float64) (float64, error) {
+- if x < 0 {
+- return 0, NegativeErr{} // error: missing method
+- }
+- ...
+-}
+-
+-type NegativeErr struct{}
+-```
+-
+-Gopls will offer a quick fix to declare this method:
+-
+-```go
+-
+-// Error implements [error.Error].
+-func (NegativeErr) Error() string {
+- panic("unimplemented")
+-}
+-```
+-
+-Beware that the new declarations appear alongside the concrete type,
+-which may be in a different file or even package from the cursor
+-position.
+-(Perhaps gopls should send a `showDocument` request to navigate the
+-client there, or a progress notification indicating that something
+-happened.)
+-
+-### `StubMissingCalledFunction`: Declare missing method T.f
+-
+-When you attempt to call a method on a type that does not have that method,
+-the compiler will report an error such as "type X has no field or method Y".
+-In this scenario, gopls now offers a quick fix to generate a stub declaration of
+-the missing method, inferring its type from the call.
+-
+-Consider the following code where `Foo` does not have a method `bar`:
+-
+-```go
+-type Foo struct{}
+-
+-func main() {
+- var s string
+- f := Foo{}
+- s = f.bar("str", 42) // error: f.bar undefined (type Foo has no field or method bar)
+-}
+-```
+-
+-Gopls will offer a quick fix, "Declare missing method Foo.bar".
+-When invoked, it creates the following declaration:
+-
+-```go
+-func (f Foo) bar(s string, i int) string {
+- panic("unimplemented")
+-}
+-```
+-
+-### `CreateUndeclared`: Create missing declaration for "undeclared name: X"
+-
+-A Go compiler error "undeclared name: X" indicates that a variable or function is being used before
+-it has been declared in the current scope. In this scenario, gopls offers a quick fix to create the declaration.
+-
+-#### Declare a new variable
+-
+-When you reference a variable that hasn't been declared:
+-
+-```go
+-func main() {
+- x := 42
+- min(x, y) // error: undefined: y
+-}
+-```
+-
+-The quick fix would insert a declaration with a default
+-value inferring its type from the context:
+-
+-```go
+-func main() {
+- x := 42
+- y := 0
+- min(x, y)
+-}
+-```
+-
+-#### Declare a new function
+-
+-Similarly, if you call a function that hasn't been declared:
+-
+-```go
+-func main() {
+- var s string
+- s = doSomething(42) // error: undefined: doSomething
+-}
+-```
+-
+-Gopls will insert a new function declaration below,
+-inferring its type from the call:
+-
+-```go
+-func main() {
+- var s string
+- s = doSomething(42)
+-}
+-
+-func doSomething(i int) string {
+- panic("unimplemented")
+-}
+-```
+-
+diff -urN a/gopls/doc/features/index.md b/gopls/doc/features/index.md
+--- a/gopls/doc/features/index.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/index.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,72 +0,0 @@
+----
+-title: "Gopls: Index of features"
+----
+-
+-This page provides an index of all supported features of gopls that
+-are accessible through the [language server protocol](https://microsoft.github.io/language-server-protocol/) (LSP).
+-It is intended for:
+-- **users of gopls** learning its capabilities so that they get the most out of their editor;
+-- **editor maintainers** adding or improving Go support in an LSP-capable editor; and
+-- **contributors to gopls** trying to understand how it works.
+-
+-In an ideal world, Go users would not need to know that gopls or even
+-LSP exists, as their LSP-enabled editors would implement every facet
+-of the protocol and expose each feature in a natural and discoverable
+-way. In reality, editors vary widely in their support for LSP, so
+-unfortunately these documents necessarily involve many details of the
+-protocol.
+-
+-We also list [settings](../settings.md) that affect each feature.
+-
+-Most features are illustrated with reference to VS Code, but we will
+-briefly mention whether each feature is supported in other popular
+-clients, and if so, how to find it. We welcome contributions, edits,
+-and updates from users of any editor.
+-
+-Contributors should [update this documentation](../contributing.md#documentation)
+-when making significant changes to existing features or when adding new ones.
+-
+-- [Passive](passive.md): features that are always on and require no special action
+- - [Hover](passive.md#hover): information about the symbol under the cursor
+- - [Signature Help](passive.md#signature-help): type information about the enclosing function call
+- - [Document Highlight](passive.md#document-highlight): highlight identifiers referring to the same symbol
+- - [Inlay Hint](passive.md#inlay-hint): show implicit names of struct fields and parameter names
+- - [Semantic Tokens](passive.md#semantic-tokens): report syntax information used by editors to color the text
+- - [Folding Range](passive.md#folding-range): report text regions that can be "folded" (expanded/collapsed) in an editor
+- - [Document Link](passive.md#document-link): extracts URLs from doc comments, strings in current file so client can linkify
+-- [Diagnostics](diagnostics.md): compile errors and static analysis findings
+-- [Navigation](navigation.md): navigation of cross-references, types, and symbols
+- - [Definition](navigation.md#definition): go to definition of selected symbol
+- - [Type Definition](navigation.md#type-definition): go to definition of type of selected symbol
+- - [References](navigation.md#references): list references to selected symbol
+- - [Implementation](navigation.md#implementation): show "implements" relationships of selected type
+- - [Document Symbol](navigation.md#document-symbol): outline of symbols defined in current file
+- - [Symbol](navigation.md#symbol): fuzzy search for symbol by name
+- - [Selection Range](navigation.md#selection-range): select enclosing unit of syntax
+- - [Call Hierarchy](navigation.md#call-hierarchy): show outgoing/incoming calls to the current function
+- - [Type Hierarchy](navigation.md#type-hierarchy): show interfaces/implementations of the current type
+-- [Completion](completion.md): context-aware completion of identifiers, statements
+-- [Code transformation](transformation.md): fixes and refactorings
+- - [Formatting](transformation.md#formatting): format the source code
+- - [Rename](transformation.md#rename): rename a symbol or package
+- - [Organize imports](transformation.md#source.organizeImports): organize the import declaration
+- - [Extract](transformation.md#refactor.extract): extract selection to a new file/function/variable
+- - [Inline](transformation.md#refactor.inline.call): inline a call to a function or method
+- - [Miscellaneous rewrites](transformation.md#refactor.rewrite): various Go-specific refactorings
+- - [Add test for func](transformation.md#source.addTest): create a test for the selected function
+-- [Web-based queries](web.md): commands that open a browser page
+- - [Package documentation](web.md#doc): browse documentation for current Go package
+- - [Free symbols](web.md#freesymbols): show symbols used by a selected block of code
+- - [Assembly](web.md#assembly): show listing of assembly code for selected function
+- - [Split package](web.md#splitpkg): split a package into two or more components
+-- Support for non-Go files:
+- - [Template files](templates.md): files parsed by `text/template` and `html/template`
+- - [go.mod and go.work files](modfiles.md): Go module and workspace manifests
+- - [Go *.s assembly files](assembly.md): Go assembly files
+-- [Command-line interface](../command-line.md): CLI for debugging and scripting (unstable)
+-- [Model Context Protocol (MCP)](mcp.md): use some features in AI-assisted environments
+-
+-You can find this page from within your editor by executing the
+-`gopls.doc.features` [code action](transformation.md#code-actions),
+-which opens it in a web browser.
+-In VS Code, you can find it on the "Quick fix" menu.
+diff -urN a/gopls/doc/features/mcp.md b/gopls/doc/features/mcp.md
+--- a/gopls/doc/features/mcp.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/mcp.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,74 +0,0 @@
+----
+-title: "Gopls: Model Context Protocol support"
+----
+-
+-Gopls includes an experimental built-in server for the [Model Context
+-Protocol](https://modelcontextprotocol.io/introduction) (MCP), allowing it to
+-expose a subset of its functionality to AI assistants in the form of MCP tools.
+-
+-## Running the MCP server
+-
+-There are two modes for running this server: 'attached' and 'detached'. In
+-attached mode, the MCP server operates in the context of an active gopls LSP
+-session, and so is able to share memory with your LSP session and observe the
+-current unsaved buffer state. In detached mode, gopls interacts with a headless
+-LSP session, and therefore only sees saved files on disk.
+-
+-### Attached mode
+-
+-To use the 'attached' mode, run gopls with the `-mcp.listen` flag. For
+-example:
+-
+-```
+-gopls serve -mcp.listen=localhost:8092
+-```
+-
+-This exposes an HTTP based MCP server using the server-sent event transport
+-(SSE), available at `http://localhost:8092/sessions/1` (assuming you have only
+-one [session](../daemon.md) on your gopls instance).
+-
+-### Detached mode
+-
+-To use the 'detached' mode, run the `mcp` subcommand:
+-
+-```
+-gopls mcp
+-```
+-
+-This runs a standalone gopls instance that speaks MCP over stdin/stdout.
+-
+-## Instructions to the model
+-
+-This gopls MCP server includes model instructions for its usage, describing
+-workflows for interacting with Go code using its available tools. These
+-instructions are automatically published during the MCP server initialization,
+-but you may want to also load them as additional context in your AI-assisted
+-session, to emphasize their importance. The `-instructions` flag causes them to
+-be printed, so that you can do, for example:
+-
+-```
+-gopls mcp -instructions > /path/to/contextFile.md
+-```
+-
+-## Security considerations
+-
+-The gopls MCP server is a wrapper around the functionality ordinarily exposed
+-by gopls through the Language Server Protocol (LSP). As such, gopls' tools
+-may perform any of the operations gopls normally performs, including:
+-
+-- reading files from the file system, and returning their contents in tool
+- results (such as when providing context);
+-- executing the `go` command to load package information, which may result in
+- calls to https://proxy.golang.org to download Go modules, and writes to go
+- caches;
+-- writing to gopls' cache or persistant configuration files; and
+-- uploading weekly telemetry data **if you have opted in** to [Go telemetry](https://go.dev/doc/telemetry).
+-
+-The gopls MCP server does not perform any operations not already performed by
+-gopls in an ordinary IDE session. Like most LSP servers, gopls does not
+-generally write directly to your source tree, though it may instruct the client
+-to apply edits. Nor does it make arbitrary requests over the network, though it
+-may make narrowly scoped requests to certain services such as the Go module
+-mirror or the Go vulnerability database, which can't readily be exploited as a
+-vehicle for exfiltration by a confused agent. Nevertheless, these capabilities
+-may require additional consideration when used as part of an AI-enabled system.
+diff -urN a/gopls/doc/features/modfiles.md b/gopls/doc/features/modfiles.md
+--- a/gopls/doc/features/modfiles.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/modfiles.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,11 +0,0 @@
+----
+-title: "Gopls: Support for go.mod and go.work files"
+----
+-
+-TODO: document these features for go.{mod,work} files:
+-- hover
+-- vulncheck
+-- add dependency
+-- update dependency
+-- diagnostics
+-
+diff -urN a/gopls/doc/features/navigation.md b/gopls/doc/features/navigation.md
+--- a/gopls/doc/features/navigation.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/navigation.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,337 +0,0 @@
+----
+-title: "Gopls: Navigation features"
+----
+-
+-This page documents gopls features for navigating your source code.
+-
+-
+-
+-## Definition
+-
+-The LSP [`textDocument/definition`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition)
+-request returns the location of the declaration of the symbol under the cursor.
+-Most editors provide a command to navigate directly to that location.
+-
+-A definition query also works in these unexpected places:
+-
+-- On an **import path**, it returns the list of locations, of
+- each package declaration in the files of the imported package.
+-- On a **package declaration**, it returns the location of
+- the package declaration that provides the documentation of that package.
+-- On a symbol in a **[`go:linkname` directive](https://pkg.go.dev/cmd/compile)**,
+- it returns the location of that symbol's declaration.
+-- On a **[doc link](https://tip.golang.org/doc/comment#doclinks)**, it returns
+- (like [`hover`](passive.md#hover)) the location of the linked symbol.
+-- On a file name in a **[`go:embed` directive](https://pkg.go.dev/embed)**,
+- it returns the location of the embedded file.
+-- On the declaration of a non-Go function (a `func` with no body),
+- it returns the location of the assembly implementation, if any,
+-- On a **return statement**, it returns the location of the function's result variables.
+-- On a **goto**, **break**, or **continue** statement, it returns the
+- location of the label, the closing brace of the relevant block statement, or the
+- start of the relevant loop, respectively.
+-
+-
+-
+-Client support:
+-- **VS Code**: Use [Go to Definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-definition) (`F12` or `⌘`-click).
+- If the cursor is already at the declaration, the request is instead interpreted as "Go to References".
+-- **Emacs + eglot**: use [`M-x xref-find-definitions`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html).
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls definition file.go:#offset`
+-
+-## References
+-
+-The LSP [`textDocument/references`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references)
+-request returns the locations of all identifiers that refer to the symbol under the cursor.
+-
+-The references algorithm handles various parts of syntax as follows:
+-
+-- The references to a **symbol** report all uses of that symbol.
+- In the case of exported symbols this may include locations in other packages.
+-- The references to a **package declaration** are all the
+- direct imports of the package, along with all the other package
+- declarations in the same package.
+-- It is an error to request the references to a **built-in symbol**
+- such as `int` or `append`,
+- as they are presumed too numerous to be of interest.
+-- The references to an **interface method** include references to
+- concrete types that implement the interface. Similarly, the
+- references to a **method of a concrete type** include references to
+- corresponding interface methods.
+-- An **embedded field** `T` in a struct type such as `struct{T}` is
+- unique in Go in that it is both a reference (to a type) and a
+- definition (of a field).
+- The `references` operation reports only the references to it [as a field](https://go.dev/issue/63521).
+- To find references to the type, jump to the type declararation first.
+-
+-Be aware that a references query returns information only about the
+-build configuration used to analyze the selected file, so if you ask
+-for the references to a symbol defined in `foo_windows.go`, the result
+-will never include the file `bar_linux.go`, even if that file refers
+-to a symbol of the same name; see https://go.dev/issue/65755.
+-
+-Clients can request that the declaration be included among the
+-references; most do.
+-
+-Client support:
+-- **VS Code**: Use [`Go to References`](https://code.visualstudio.com/docs/editor/editingevolved#_peek) to quickly "peek" at the references,
+- or `Find all References` to open the references panel.
+-- **Emacs + eglot**: Via [`xref` package](https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html): use `M-x xref-find-references`.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls references file.go:#offset`
+-
+-## Implementation
+-
+-The LSP
+-[`textDocument/implementation`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_implementation)
+-request queries the relation between abstract and concrete types and
+-their methods.
+-
+-Interfaces and concrete types are matched using method sets:
+-
+-- When invoked on a reference to an **interface type**, it returns the
+- location of the declaration of each type that implements
+- the interface.
+-- When invoked on a **concrete type**,
+- it returns the locations of the matching interface types.
+-- When invoked on an **interface method**, it returns the corresponding
+- methods of the types that satisfy the interface.
+-- When invoked on a **concrete method**,
+- it returns the locations of the matching interface methods.
+-
+-For example:
+-- `implementation(io.Reader)` includes subinterfaces such as `io.ReadCloser`,
+- and concrete implementations such as `*os.File`. It also includes
+- other declarations equivalent to `io.Reader`.
+-- `implementation(os.File)` includes only interfaces, such as
+- `io.Reader` and `io.ReadCloser`.
+-
+-The LSP's Implementation feature has a built-in bias towards subtypes,
+-possibly because in languages such as Java and C++ the relationship
+-between a type and its supertypes is explicit in the syntax, so the
+-corresponding "Go to interfaces" operation can be achieved as sequence
+-of two or more "Go to definition" steps: the first to visit the type
+-declaration, and the rest to sequentially visit ancestors.
+-(See https://github.com/microsoft/language-server-protocol/issues/2037.)
+-
+-In Go, where there is no syntactic relationship between two types, a
+-search is required when navigating in either direction between
+-subtypes and supertypes. The heuristic above works well in many cases,
+-but it is not possible to ask for the superinterfaces of
+-`io.ReadCloser`. For more explicit navigation between subtypes and
+-supertypes, use the [Type Hierarchy](#Type Hierarchy) feature.
+-
+-Only non-trivial interfaces are considered; no implementations are
+-reported for type `any`.
+-
+-Within the same package, all matching types/methods are reported.
+-However, across packages, only exported package-level types and their
+-methods are reported, so local types (whether interfaces, or struct
+-types with methods due to embedding) may be missing from the results.
+-
+-
+-Functions, `func` types, and dynamic function calls are matched using signatures:
+-
+-- When invoked on the `func` token of a **function definition**,
+- it returns the locations of the matching signature types
+- and dynamic call expressions.
+-- When invoked on the `func` token of a **signature type**,
+- it returns the locations of the matching concrete function definitions.
+-- When invoked on the `(` token of a **dynamic function call**,
+- it returns the locations of the matching concrete function
+- definitions.
+-
+-If either the target type or the candidate type are generic, the
+-results will include the candidate type if there is any instantiation
+-of the two types that would allow one to implement the other.
+-(Note: the matcher doesn't current implement full unification, so type
+-parameters are treated like wildcards that may match arbitrary
+-types, without regard to consistency of substitutions across the
+-method set or even within a single method.
+-This may lead to occasional spurious matches.)
+-
+-Since a type may be both a function type and a named type with methods
+-(for example, `http.HandlerFunc`), it may participate in both kinds of
+-implementation queries (by method-sets and function signatures).
+-Queries using method-sets should be invoked on the type or method name,
+-and queries using signatures should be invoked on a `func` or `(` token.
+-
+-Client support:
+-- **VS Code**: Use [Go to Implementations](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-implementation) (`⌘F12`).
+-- **Emacs + eglot**: Use `M-x eglot-find-implementation`.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls implementation file.go:#offset`
+-
+-
+-## Type Definition
+-
+-The LSP
+-[`textDocument/typeDefinition`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_typeDefinition)
+-request returns the location of the type of the selected symbol.
+-
+-For example, if the selection is the name `buf` of a local variable of
+-type `*bytes.Buffer`, a `typeDefinition` query will return the
+-location of the type `bytes.Buffer`.
+-Clients typically navigate to that location.
+-
+-Type constructors such as pointer, array, slice, channel, and map are
+-stripped off the selected type in the search for a named type. For
+-example, if x is of type `chan []*T`, the reported type definition
+-will be that of `T`.
+-Similarly, if the symbol's type is a function with one "interesting"
+-(named, non-`error`) result type, the function's result type is used.
+-
+-Gopls currently requires that a `typeDefinition` query be applied to a
+-symbol, not to an arbitrary expression; see https://go.dev/issue/67890 for
+-potential extensions of this functionality.
+-
+-
+-Client support:
+-- **VS Code**: Use [Go to Type Definition](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-implementation).
+-- **Emacs + eglot**: Use `M-x eglot-find-typeDefinition`.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: not supported
+-
+-## Document Symbol
+-
+-The `textDocument/documentSymbol` LSP query reports the list of
+-top-level declarations in this file. Clients may use this information
+-to present an overview of the file, and an index for faster navigation.
+-
+-Gopls responds with the
+-[`DocumentSymbol`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbol)
+-type if the client indicates
+-[`hierarchicalDocumentSymbolSupport`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#documentSymbolClientCapabilities);
+-otherwise it returns a
+-[`SymbolInformation`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#symbolInformation).
+-
+-Client support:
+-- **VS Code**: Use the [Outline view](https://code.visualstudio.com/docs/getstarted/userinterface#_outline-view) for navigation.
+-- **Emacs + eglot**: Use [`M-x imenu`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html#Imenu) to jump to a symbol.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls links file.go`
+-
+-
+-## Symbol
+-
+-The
+-[`workspace/symbol`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#workspace_symbol)
+-LSP query searches an index of all the symbols in the workspace.
+-
+-The default symbol matching algorithm (`fastFuzzy`), inspired by the
+-popular fuzzy matcher [FZF](https://github.com/junegunn/fzf), attempts
+-a variety of inexact matches to correct for misspellings or abbreviations in your
+-query. For example, it considers `DocSym` a match for `DocumentSymbol`.
+-
+-
+-
+-Settings:
+-- The [`symbolMatcher`](../settings.md#symbolMatcher) setting controls the algorithm used for symbol matching.
+-- The [`symbolStyle`](../settings.md#symbolStyle) setting controls how symbols are qualified in symbol responses.
+-- The [`symbolScope`](../settings.md#symbolScope) setting determines the scope of the query.
+-- The [`directoryFilters`](../settings.md#directoryFilters) setting specifies directories to be excluded from the search.
+-
+-Client support:
+-- **VS Code**: Use ⌘T to open [Go to Symbol](https://code.visualstudio.com/docs/editor/editingevolved#_go-to-symbol) with workspace scope. (Alternatively, use Ctrl-Shift-O, and add a `@` prefix to search within the file or a `#` prefix to search throughout the workspace.)
+-- **Emacs + eglot**: Use [`M-x xref-find-apropos`](https://www.gnu.org/software/emacs/manual/html_node/emacs/Looking-Up-Identifiers.html) to show symbols that match a search term.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls links file.go`
+-
+-
+-## Selection Range
+-
+-The
+-[`textDocument/selectionRange`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_selectionRange)
+-LSP query returns information about the lexical extent of each piece
+-of syntax enclosing the current selection.
+-Clients may use it to provide an operation to expand the selection
+-to successively larger expressions.
+-
+-Client support:
+-- **VSCode**: Use `⌘⇧^→` to expand the selection or `⌘⇧^←` to contract it again; watch this [video](https://www.youtube.com/watch?v=dO4SGAMl7uQ).
+-- **Emacs + eglot**: Not standard. Use `M-x eglot-expand-selection` defined in [this configuration snippet](https://github.com/joaotavora/eglot/discussions/1220#discussioncomment-9321061).
+-- **Vim + coc.nvim**: ??
+-- **CLI**: not supported
+-
+-## Call Hierarchy
+-
+-The LSP CallHierarchy mechanism consists of three queries that
+-together enable clients to present a hierarchical view of a portion of
+-the static call graph:
+-
+-- [`textDocument/prepareCallHierarchy`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareCallHierarchy) returns a list of [items](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchyItem) for a given position, each representing a named function or method enclosing the position;
+-- [`callHierarchyItem/incomingCalls`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchy_incomingCalls) returns the set of call sites that call the selected item; and
+-- [`callHierarchy/outgoingCalls`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#callHierarchy_incomingCalls) returns the set of functions called by the selected item.
+-
+-Invoke the command while selecting the name in a function declaration.
+-
+-Dynamic calls are not included, because it is not analytically
+-practical to detect them. So, beware that the results may not be
+-exhaustive, and perform a [References](#references) query if necessary.
+-
+-The hierarchy does not consider a nested function distinct from its
+-enclosing named function. (Without the ability to detect dynamic
+-calls, it would make little sense do so.)
+-
+-The screenshot below shows the outgoing call tree rooted at `f`. The
+-tree has been expanded to show a path from `f` to the `String` method
+-of `fmt.Stringer` through the guts of `fmt.Sprint:`
+-
+-
+-
+-Client support:
+-- **VS Code**: `Show Call Hierarchy` menu item (`⌥⇧H`) opens [Call hierarchy view](https://code.visualstudio.com/docs/cpp/cpp-ide#_call-hierarchy) (note: docs refer to C++ but the idea is the same for Go).
+-- **Emacs + eglot**: Not standard; install with `(package-vc-install "https://github.com/dolmens/eglot-hierarchy")`. Use `M-x eglot-hierarchy-call-hierarchy` to show the direct incoming calls to the selected function; use a prefix argument (`C-u`) to show the direct outgoing calls. There is no way to expand the tree.
+-- **CLI**: `gopls call_hierarchy file.go:#offset` shows outgoing and incoming calls.
+-
+-
+-## Type Hierarchy
+-
+-The LSP TypeHierarchy mechanism consists of three queries that
+-together enable clients to present a hierarchical view of a portion of
+-the subtyping relation over named types.
+-
+-- [`textDocument/prepareTypeHierarchy`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#textDocument_prepareTypeHierarchy) returns an [item](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchyItem) describing the named type at the current position;
+-- [`typeHierarchyItem/subtypes`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchy_subtypes) returns the set of subtypes of the selected (interface) type; and
+-- [`typeHierarchy/supertypes`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification#typeHierarchy_supertypes) returns the set of supertypes (interface types) of the selected type.
+-
+-Invoke the command while selecting the name of a type.
+-
+-As with an Implementation query, a type hierarchy query reports
+-function-local types only within the same package as the query type.
+-Also the result does not include alias types, only defined types.
+-
+-
+-
+-
+-
+-Caveats:
+-
+-- The type hierarchy supports only named types and their assignability
+- relation. By contrast, the Implementations request also reports the
+- relation between unnamed `func` types and function declarations,
+- function literals, and dynamic calls of values of those types.
+-
+-Client support:
+-- **VS Code**: `Show Type Hierarchy` menu item opens [Type hierarchy view](https://code.visualstudio.com/docs/java/java-editing#_type-hierarchy) (note: docs refer to Java but the idea is the same for Go).
+-- **Emacs + eglot**: Support added in March 2025. Use `M-x eglot-show-call-hierarchy`.
+-- **CLI**: not yet supported.
+diff -urN a/gopls/doc/features/passive.md b/gopls/doc/features/passive.md
+--- a/gopls/doc/features/passive.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/passive.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,321 +0,0 @@
+----
+-title: "Gopls: Passive features"
+----
+-
+-This page documents the fundamental LSP features of gopls that may be
+-described as "passive", since many editors use them to continuously
+-provide information about your source files without requiring any
+-special action.
+-
+-See also [Code Lenses](../codelenses.md), some of which annotate your
+-source code with additional information and may thus also be
+-considered passive features.
+-
+-
+-## Hover
+-
+-The LSP [`textDocument/hover`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_hover)
+-query returns a description of the code currently under the cursor, such
+-as its name, kind, type, value (for a constant), abbreviated
+-declaration (for a type), doc comment (if any), and a link to the
+-symbol's documentation on `pkg.go.dev`. The client may request either
+-plain text or Markdown.
+-
+-
+-
+-Depending on the selection, the response may include additional information.
+-For example, hovering over a type shows its declared methods,
+-plus any methods promoted from embedded fields.
+-
+-**Doc links**: A doc comment may refer to another symbol using square
+-brackets, for example `[fmt.Printf]`. Hovering over one of these
+-[doc links](https://go.dev/doc/comment#doclinks) reveals
+-information about the referenced symbol.
+-
+-
+-
+-**Struct size/offset info**: for declarations of struct types,
+-hovering over the name reveals the struct's size in bytes:
+-
+-
+-
+-And hovering over each field name shows the size and offset of that field:
+-
+-
+-
+-This information may be useful when optimizing the layout of your data
+-structures, or when reading assembly files or stack traces that refer
+-to each field by its cryptic byte offset.
+-
+-In addition, Hover reports:
+-- the struct's size class, which is the number of bytes actually
+- allocated by the Go runtime for a single object of this type; and
+-- the percentage of wasted space due to suboptimal ordering of struct
+- fields, if this figure is 20% or higher:
+-
+-
+-
+-In the struct above, alignment rules require each of the two boolean
+-fields (1 byte) to occupy a complete word (8 bytes), leading to (7 +
+-7) / (3 * 8) = 58% waste.
+-Placing the two booleans together would save a word.
+-(In most structures clarity is more important than compactness, so you
+-should reorder fields to save space only in data structures that have
+-been shown by profiling to be very frequently allocated.)
+-
+-**Embed directives**: hovering over the file name pattern in
+-[`//go:embed` directive](https://pkg.go.dev/embed), for example
+-`*.html`, reveals the list of file names to which the wildcard
+-expands.
+-
+-
+-
+-
+-**Linkname directives**: a [`//go:linkname` directive](https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives) creates a linker-level alias for another symbol.
+-Hovering over the directive shows information about the other symbol.
+-
+-
+-
+-The hover information for symbols from the standard library added
+-after Go 1.0 states the Go release that added the symbol.
+-
+-Settings:
+-- The [`hoverKind`](../settings.md#hoverKind) setting controls the verbosity of documentation.
+-- The [`linkTarget`](../settings.md#linkTarget) setting specifies
+- the base URI for Go package links
+-
+-Caveats:
+-- It is an unfortunate limitation of the LSP that a `Hover` request
+- currently includes only a position but not a selection, as this
+- means it is impossible to request information about the type and
+- methods of, say, the `f(x)` portion of the larger expression
+- `f(x).y`. Please upvote microsoft/language-server-protocol#1466 if
+- you would like to see this addressed.
+-
+-Client support:
+-- **VS Code**: enabled by default. Displays rendered Markdown in a panel near the cursor.
+-- **Emacs + eglot**: enabled by default. Displays a one-line summary in the echo area.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls definition file.go:#start-#end` includes information from a Hover query.
+-
+-
+-## Signature Help
+-
+-The LSP [`textDocument/signatureHelp`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_signatureHelp)
+-query returns information about the innermost function call enclosing
+-the cursor or selection, including the signature of the function and
+-the names, types, and documentation of each parameter.
+-
+-Clients may provide this information to help remind the user of the
+-purpose of each parameter and their order, while reading or editing a
+-function call.
+-
+-
+-
+-Call parens are not necessary if the cursor is within an identifier
+-that denotes a function or method. For example, Signature Help at
+-`once.Do(initialize‸)` will describe `initialize`, not `once.Do`.
+-
+-Client support:
+-- **VS Code**: enabled by default.
+- Also known as "[parameter hints](https://code.visualstudio.com/api/references/vscode-api#SignatureHelpProvider)" in the [IntelliSense settings](https://code.visualstudio.com/docs/editor/intellisense#_settings).
+- Displays signature and doc comment alongside Hover information.
+-- **Emacs + eglot**: enabled by default. Displays signature in the echo area.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls signature file.go:#start-#end`
+-
+-
+-## Document Highlight
+-
+-The LSP [`textDocument/documentHighlight`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentHighlight)
+-query reports a set of source ranges that should be highlighted based
+-on the current cursor position or selection, to emphasize the
+-relationship between them.
+-
+-Each of the following parts of syntax forms a set so that if you
+-select any one member, gopls will highlight the complete set:
+-
+-- each identifier that refers to the same symbol (as in the screenshot below);
+-- a named result variable and all its corresponding operands of `return` statements;
+-- the `for`, `break`, and `continue` tokens of the same loop;
+-- the `switch` and `break` tokens of the same switch statement;
+-- the `func` keyword of a function and all of its `return` statements.
+-
+-More than one of these rules may be activated by a single selection,
+-for example, by an identifier that is also a return operand.
+-
+-Different occurrences of the same identifier may be color-coded to distinguish
+-"read" from "write" references to a given variable symbol.
+-
+-
+-
+-Client support:
+-- **VS Code**: enabled by default. Triggered by cursor motion, or single click.
+- (Note: double clicking activates a simple syntax-oblivious textual match.)
+-- **Emacs + eglot**: enabled by default. Triggered by cursor motion or selection.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls signature file.go:#start-#end`
+-
+-
+-## Inlay Hint
+-
+-The LSP [`textDocument/inlayHint`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint)
+-query returns a set of annotations to be spliced into the current file
+-that reveal implicit information.
+-
+-
+-
+-Examples:
+-
+-- In a function call `f(1, 2)`, hints provide the
+- names of the parameters (`parameterNames`), as in the screenshot above.
+-- In a call to a generic function, hints provide the type arguments
+- (`functionTypeParameters`).
+-- In an assignment `x, y = 1, 2`, hints provide the types of the
+- variables (`assignVariableTypes`).
+-- In a struct literal such as `Point2D{1, 2}`, hints provide the field
+- names (`compositeLiteralFields`).
+-- In a nested composite literal `T{{...}}`, a hint provides the type of
+- the inner literal, `{...}` (`compositeLiteralTypes`).
+-- In a `for k, v := range x {}` loop, hints provide the types of the
+- variables k and v (`rangeVariableTypes`).
+-- For a constant expression (perhaps using `iota`), a hint provides
+- its computed value (`constantValues`).
+-
+-See [Inlay hints](../inlayHints.md) for a complete list with examples.
+-
+-
+-
+-Settings:
+-- The [`hints`](../settings.md#hints) setting indicates the desired set of hints.
+- To reduce distractions, its default value is empty.
+- To enable hints, add one or more of the identifiers above to the hints
+- map. For example:
+- ```json5
+- "hints": {"parameterNames": true}
+- ```
+-
+-Client support:
+-- **VS Code**: in addition to the `hints` configuration value, VS Code provides a graphical
+- configuration menu ("Preferences: Open Settings (UI)" the search for "Go Inlay Hints")
+- for each supported kind of inlay hint.
+-- **Emacs + eglot**: disabled by default. Needs `M-x eglot-inlay-hints-mode` plus the configuration [described here](https://www.reddit.com/r/emacs/comments/11bqzvk/emacs29_and_eglot_inlay_hints/)
+-- **Vim + coc.nvim**: ??
+-- **CLI**: not supported
+-
+-## Semantic Tokens
+-
+-The LSP [`textDocument/semanticTokens`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens)
+-query reports information about all the tokens in the current file, or
+-a portion of it.
+-The client may use this information to provide syntax highlighting
+-that conveys semantic distinctions between, for example, functions and
+-types, constants and variables, or library functions and built-ins.
+-
+-The client must specify the sets of types and modifiers it is interested in.
+-
+-Gopls reports the following token types:
+-
+-- `"comment"`: a comment
+-- `"function"`: a function
+-- `"keyword"`: a keyword
+-- `"label"`: a control label (not an LSP standard type)
+-- `"macro"`: text/template tokens
+-- `"method"`: a method
+-- `"namespace"`: an imported package name
+-- `"number"`: a numeric literal
+-- `"operator"`: an operator
+-- `"parameter"`: a parameter variable
+-- `"string"`: a string literal
+-- `"type"`: a type name (plus other uses)
+-- `"typeParameter"`: a type parameter
+-- `"variable"`: a var or const (see `readonly` modifier)
+-
+-Gopls also reports the following standard modifiers:
+-
+-- `"defaultLibrary"`: predeclared symbols
+-- `"definition"`: the declaring identifier of a symbol
+-- `"readonly"`: for constants
+-
+-plus these non-standard modifiers each representing the top-level
+-constructor of each symbols's type:
+-
+-- `"array"`
+-- `"bool"`
+-- `"chan"`
+-- `"interface"`
+-- `"map"`
+-- `"number"`
+-- `"pointer"`
+-- `"signature"`
+-- `"slice"`
+-- `"string"`
+-- `"struct"`
+-
+-Settings:
+-- The [`semanticTokens`](../settings.md#semanticTokens) setting determines whether
+- gopls responds to semantic token requests. This option allows users to disable
+- semantic tokens even when their client provides no client-side control over the
+- feature. Because gopls' semantic-tokens algorithm depends on type checking,
+- which adds a tangible latency, this feature is currently disabled by default
+- to avoid any delay in syntax highlighting; see https://go.dev/issue/#45313, https://go.dev/issue/#47465.
+-- The experimental [`noSemanticString`](../settings.md#noSemanticString) and
+- [`noSemanticNumber`](../settings.md#noSemanticNumber) settings cause the server
+- to exclude the `string` and `number` kinds from the response, as some clients
+- may do a more colorful job highlighting these tokens; see https://go.dev/issue/45753.
+-
+-Client Support:
+-- **VS Code**: See [Semantic Highlighting Guide](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide).
+-- **Emacs + eglot**: Not supported; see joaotavora/eglot#615.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls semtok file.go`
+-
+-## Folding Range
+-
+-The LSP [`textDocument/foldingRange`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_foldingRange)
+-query reports the list of regions in the current file that may be
+-independently collapsed or expanded. For example, it may be convenient
+-to collapse large comments or functions when studying some code so
+-that more of it fits in a single screen.
+-
+-
+-
+-The protocol [allows](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#foldingRangeClientCapabilities) clients to indicate whether they prefer
+-fine-grained ranges such as matched pairs of brackets, or only ranges
+-consisting of complete lines.
+-
+-Client support:
+-- **VS Code**: displayed in left margin. Toggle the chevrons (`∨` and `>`) to collapse or expand.
+-- **Emacs + eglot**: not supported.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls folding_ranges file.go`
+-
+-## Document Link
+-
+-The LSP [`textDocument/documentLink`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink)
+-query uses heuristics to extracts URLs from doc comments and string
+-literals in the current file so that the client can present them as
+-clickable links.
+-
+-
+-
+-In addition to explicit URLs, gopls also turns string literals in
+-import declarations into links to the pkg.go.dev documentation for the
+-imported package.
+-
+-Settings:
+-- The [`importShortcut`](../settings.md#importShortcut) setting determines
+- what kind of link is returned for an `import` declaration.
+-- The [`linkTarget`](../settings.md#linkTarget) setting specifies
+- the base URI for Go package links.
+-
+-Client support:
+-- **VS Code**: Hovering over a link displays a "Follow link (cmd+click)" popup.
+-- **Emacs + eglot**: not currently used.
+-- **Vim + coc.nvim**: ??
+-- **CLI**: `gopls links file.go`
+diff -urN a/gopls/doc/features/README.md b/gopls/doc/features/README.md
+--- a/gopls/doc/features/README.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/README.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1 +0,0 @@
+-See [index.md](index.md).
+diff -urN a/gopls/doc/features/templates.md b/gopls/doc/features/templates.md
+--- a/gopls/doc/features/templates.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/templates.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,51 +0,0 @@
+----
+-title: "Gopls: Support for template files"
+----
+-
+-Gopls provides some support for Go template files, that is, files that
+-are parsed by [`text/template`](https://pkg.go.dev/text/template) or
+-[`html/template`](https://pkg.go.dev/html/template).
+-
+-## Enabling template support
+-
+-Gopls recognizes template files based on their file extension, which
+-may be configured by the
+-[`templateExtensions`](../settings.md#templateExtensions) setting. If
+-this list is empty, template support is disabled. (This is the default
+-value, since Go templates don't have a canonical file extension.)
+-
+-Additional configuration may be necessary to ensure that your client
+-chooses the correct language kind when opening template files.
+-Gopls recognizes both `"tmpl"` and `"gotmpl"` for template files.
+-For example, in `VS Code` you will also need to add an
+-entry to the
+-[`files.associations`](https://code.visualstudio.com/docs/languages/identifiers)
+-mapping:
+-```json
+-"files.associations": {
+- ".mytemplate": "gotmpl"
+-},
+-```
+-
+-
+-## Features
+-In template files, template support works inside
+-the default `{{` delimiters. (Go template parsing
+-allows the user to specify other delimiters, but
+-gopls does not know how to do that.)
+-
+-Gopls template support includes the following features:
+-+ **Diagnostics**: if template parsing returns an error,
+-it is presented as a diagnostic. (Missing functions do not produce errors.)
+-+ **Syntax Highlighting**: syntax highlighting is provided for template files.
+-+ **Definitions**: gopls provides jump-to-definition inside templates, though it does not understand scoping (all templates are considered to be in one global scope).
+-+ **References**: gopls provides find-references, with the same scoping limitation as definitions.
+-+ **Completions**: gopls will attempt to suggest completions inside templates.
+-
+-TODO: also
+-+ Hover
+-+ SemanticTokens
+-+ Symbol search
+-+ DocumentHighlight
+-
+-
+diff -urN a/gopls/doc/features/transformation.md b/gopls/doc/features/transformation.md
+--- a/gopls/doc/features/transformation.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/transformation.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,913 +0,0 @@
+----
+-title: "Gopls: Code transformation features"
+----
+-
+-This document describes gopls' features for code transformation, which
+-include a range of behavior-preserving changes (refactorings,
+-formatting, simplifications), code repair (fixes), and editing support
+-(filling in struct literals and switch statements).
+-
+-Code transformations are not a single category in the LSP:
+-
+-- A few, such as Formatting and Rename, are primary operations in the
+- protocol.
+-- Some transformations are exposed through [Code Lenses](../codelenses.md),
+- which return _commands_, arbitrary server
+- operations invoked for their side effects through a
+- [`workspace/executeCommand`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_executeCommand) request;
+- however, no current code lenses are transformations of Go syntax.
+-
+-- Most transformations are defined as *code actions*.
+-
+-## Code Actions
+-
+-A **code action** is an action associated with a portion of the file.
+-Each time the selection changes, a typical client makes a
+-[`textDocument/codeAction`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction)
+-request for the set of available actions, then updates its UI
+-elements (menus, icons, tooltips) to reflect them.
+-The VS Code manual describes code actions as
+-"[Quick fixes + Refactorings](https://code.visualstudio.com/docs/editor/refactoring#_code-actions-quick-fixes-and-refactorings)".
+-
+-A `codeAction` request delivers the menu, so to speak, but it does
+-not order the meal. Once the user chooses an action, one of two things happens.
+-In trivial cases, the action itself contains an edit that the
+-client can directly apply to the file.
+-But in most cases the action contains a command,
+-similar to the command associated with a code lens.
+-This allows the work of computing the patch to be done lazily, only
+-when actually needed. (Most aren't.)
+-The server may then compute the edit and send the client a
+-[`workspace/applyEdit`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_applyEdit)
+-request to patch the files.
+-Not all code actions' commands have an `applyEdit` side effect: some
+-may change the state of the server, for example to toggle a variable
+-or to cause the server to send other requests to the client,
+-such as a `showDocument` request to open a report in a web browser.
+-
+-The main difference between code lenses and code actions is this:
+-
+-- a `codeLens` request obtains commands for the entire file.
+- Each command specifies its applicable source range,
+- and typically appears as an annotation on that source range.
+-- a `codeAction` request obtains commands only for a particular range: the current selection.
+- All the commands are presented together in a menu at that location.
+-
+-Each action has a _kind_,
+-which is a hierarchical identifier such as `refactor.inline.call`.
+-Clients may filter actions based on their kind.
+-For example, VS Code has:
+-two menus, "Refactor..." and "Source action...", each populated by
+-different kinds of code actions (`refactor` and `source`);
+-a lightbulb icon that triggers a menu of "quick fixes" (of kind `quickfix`);
+-and a "Fix All" command that executes all code actions of
+-kind `source.fixAll`, which are those deemed unambiguously safe to apply.
+-
+-Gopls supports the following code actions:
+-
+-- `quickfix`, which applies unambiguously safe fixes
+-- [`source.organizeImports`](#source.organizeImports)
+-- [`source.assembly`](web.md#assembly)
+-- [`source.doc`](web.md#doc)
+-- [`source.freesymbols`](web.md#freesymbols)
+-- `source.test` (undocumented)
+-- [`source.addTest`](#source.addTest)
+-- [`source.toggleCompilerOptDetails`](diagnostics.md#toggleCompilerOptDetails)
+-- [`gopls.doc.features`](README.md), which opens gopls' index of features in a browser
+-- [`refactor.extract.constant`](#extract)
+-- [`refactor.extract.function`](#extract)
+-- [`refactor.extract.method`](#extract)
+-- [`refactor.extract.toNewFile`](#extract.toNewFile)
+-- [`refactor.extract.variable`](#extract)
+-- [`refactor.extract.variable-all`](#extract)
+-- [`refactor.inline.call`](#refactor.inline.call)
+-- [`refactor.inline.variable`](#refactor.inline.variable)
+-- [`refactor.rewrite.addTags`](#refactor.rewrite.addTags)
+-- [`refactor.rewrite.changeQuote`](#refactor.rewrite.changeQuote)
+-- [`refactor.rewrite.fillStruct`](#refactor.rewrite.fillStruct)
+-- [`refactor.rewrite.fillSwitch`](#refactor.rewrite.fillSwitch)
+-- [`refactor.rewrite.invertIf`](#refactor.rewrite.invertIf)
+-- [`refactor.rewrite.joinLines`](#refactor.rewrite.joinLines)
+-- [`refactor.rewrite.moveParamLeft`](#refactor.rewrite.moveParamLeft)
+-- [`refactor.rewrite.moveParamRight`](#refactor.rewrite.moveParamRight)
+-- [`refactor.rewrite.removeTags`](#refactor.rewrite.removeTags)
+-- [`refactor.rewrite.removeUnusedParam`](#refactor.rewrite.removeUnusedParam)
+-- [`refactor.rewrite.splitLines`](#refactor.rewrite.splitLines)
+-
+-Gopls reports some code actions twice, with two different kinds, so
+-that they appear in multiple UI elements: simplifications,
+-for example from `for _ = range m` to `for range m`,
+-have kinds `quickfix` and `source.fixAll`,
+-so they appear in the "Quick Fix" menu and
+-are activated by the "Fix All" command.
+-
+-
+-
+-Many transformations are computed by [analyzers](../analyzers.md)
+-that, in the course of reporting a diagnostic about a problem,
+-also suggest a fix.
+-A `codeActions` request will return any fixes accompanying diagnostics
+-for the current selection.
+-
+-
+-
+-
+-
+-Caveats:
+-
+-- Many of gopls code transformations are limited by Go's syntax tree
+- representation, which currently records comments not in the tree
+- but in a side table; consequently, transformations such as Extract
+- and Inline are prone to losing comments. This is issue
+- https://go.dev/issue/20744, and it is a priority for us to fix in 2024.
+-
+-- Generated files, as identified by the conventional
+- [DO NOT EDIT](https://go.dev/s/generatedcode) comment,
+- are not offered code actions for transformations.
+-
+-
+-Client support for code actions:
+-
+-- **VS Code**: Depending on their kind, code actions are found in
+- the "Refactor..." menu (`^⇧R`),
+- the "Source action..." menu,
+- the 💡 (light bulb) icon's menu, or
+- the "Quick fix" (`⌘.`) menu.
+- The "Fix All" command applies all actions of kind `source.fixAll`.
+-- **Emacs + eglot**: Code actions are invisible.
+- Use `M-x eglot-code-actions` to select one from those that are
+- available (if there are multiple) and execute it.
+- Some action kinds have filtering shortcuts,
+- e.g. [`M-x eglot-code-action-{inline,extract,rewrite}`](https://joaotavora.github.io/eglot/#index-M_002dx-eglot_002dcode_002daction_002dinline).
+-- **CLI**: `gopls codeaction -exec -kind k,... -diff file.go:#123-#456` executes code actions of the specified
+- kinds (e.g. `refactor.inline`) on the selected range, specified using zero-based byte offsets, and displays the diff.
+-
+-
+-## Formatting
+-
+-The LSP
+-[`textDocument/formatting`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting)
+-request returns edits that format a file.
+-Gopls applies Go's canonical formatting algorithm,
+-[`go fmt`](https://pkg.go.dev/cmd/gofmt).
+-LSP formatting options are ignored.
+-
+-Most clients are configured to format files and organize imports
+-whenever a file is saved.
+-
+-Settings:
+-
+-- The [`gofumpt`](../settings.md#gofumpt) setting causes gopls to use an
+- alternative formatter, [`github.com/mvdan/gofumpt`](https://pkg.go.dev/mvdan.cc/gofumpt).
+-
+-Client support:
+-
+-- **VS Code**: Formats on save by default. Use `Format document` menu item (`⌥⇧F`) to invoke manually.
+-- **Emacs + eglot**: Use `M-x eglot-format-buffer` to format. Attach it to `before-save-hook` to format on save. For formatting combined with organize-imports, many users take the legacy approach of setting `"goimports"` as their `gofmt-command` using [go-mode](https://github.com/dominikh/go-mode.el), and adding `gofmt-before-save` to `before-save-hook`. An LSP-based solution requires code such as https://github.com/joaotavora/eglot/discussions/1409.
+-- **CLI**: `gopls format file.go`
+-
+-
+-## `source.organizeImports`: Organize imports
+-
+-A `codeActions` request in a file whose imports are not organized will
+-return an action of the standard kind `source.organizeImports`.
+-Its command has the effect of organizing the imports:
+-deleting existing imports that are duplicate or unused,
+-adding new ones for undefined symbols,
+-and sorting them into the conventional order.
+-
+-The addition of new imports is based on heuristics that depend on
+-your workspace and the contents of your GOMODCACHE directory; they may
+-sometimes make surprising choices.
+-
+-Many editors automatically organize imports and format the code before
+-saving any edited file.
+-
+-Some users dislike the automatic removal of imports that are
+-unreferenced because, for example, the sole line that refers to the
+-import is temporarily commented out for debugging; see https://go.dev/issue/54362.
+-
+-Settings:
+-
+-- The [`local`](../settings.md#local) setting is a comma-separated list of
+- prefixes of import paths that are "local" to the current file and
+- should appear after standard and third-party packages in the sort order.
+-
+-Client support:
+-
+-- **VS Code**: automatically invokes `source.organizeImports` before save.
+- To disable it, use the snippet below, and invoke the "Organize Imports" command manually as needed.
+- ```
+- "[go]": {
+- "editor.codeActionsOnSave": { "source.organizeImports": false }
+- }
+- ```
+-- **Emacs + eglot**: Use `M-x eglot-code-action-organize-imports` to invoke manually.
+- Many users of [go-mode](https://github.com/dominikh/go-mode.el) use these lines to
+- organize imports and reformat each modified file before saving it, but this
+- approach is based on the legacy
+- [`goimports`](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) tool, not gopls:
+- ```lisp
+- (setq gofmt-command "goimports")
+- (add-hook 'before-save-hook 'gofmt-before-save)
+- ```
+-- **CLI**: `gopls fix -a file.go:#offset source.organizeImports`
+-
+-
+-## `source.addTest`: Add test for function or method
+-
+-If the selected chunk of code is part of a function or method declaration F,
+-gopls will offer the "Add test for F" code action, which adds a new test for the
+-selected function in the corresponding `_test.go` file. The generated test takes
+-into account its signature, including input parameters and results.
+-
+-**Test file**: if the `_test.go` file does not exist, gopls creates it, based on
+-the name of the current file (`a.go` -> `a_test.go`), copying any copyright and
+-build constraint comments from the original file.
+-
+-**Test package**: for new files that test code in package `p`, the test file
+-uses `p_test` package name whenever possible, to encourage testing only exported
+-functions. (If the test file already exists, the new test is added to that file.)
+-
+-**Parameters**: each of the function's non-blank parameters becomes an item in
+-the struct used for the table-driven test. (For each blank `_` parameter, the
+-value has no effect, so the test provides a zero-valued argument.)
+-
+-**Contexts**: If the first parameter is `context.Context`, the test passes
+-`context.Background()`.
+-
+-**Results**: the function's results are assigned to variables (`got`, `got2`,
+-and so on) and compared with expected values (`want`, `want2`, etc.`) defined in
+-the test case struct. The user should edit the logic to perform the appropriate
+-comparison. If the final result is an `error`, the test case defines a `wantErr`
+-boolean.
+-
+-**Method receivers**: When testing a method `T.F` or `(*T).F`, the test must
+-construct an instance of T to pass as the receiver. Gopls searches the package
+-for a suitable function that constructs a value of type T or \*T, optionally with
+-an error, preferring a function named `NewT`.
+-
+-**Imports**: Gopls adds missing imports to the test file, using the last
+-corresponding import specifier from the original file. It avoids duplicate
+-imports, preserving any existing imports in the test file.
+-
+-
+-
+-
+-## Rename
+-
+-The LSP
+-[`textDocument/rename`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename)
+-request renames a symbol.
+-
+-Renaming is a two-stage process. The first step, a
+-[`prepareRename`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_prepareRename) query, returns the current
+-name of the identifier under the cursor (if indeed there is one).
+-The client then displays a dialog prompting the user to choose a new
+-name by editing the old one. The second step, `rename` proper, applies
+-the changes. (This simple dialog support is unique among LSP
+-refactoring operations; see microsoft/language-server-protocol#1164.)
+-
+-Gopls' renaming algorithm takes great care to detect situations in
+-which renaming might introduce a compilation error.
+-For example, changing a name may cause a symbol to become "shadowed",
+-so that some existing references are no longer in scope. Gopls will
+-report an error, stating the pair of symbols and the shadowed reference:
+-
+-
+-
+-As another example, consider renaming a method of a concrete type.
+-Renaming may cause the type to no longer satisfy the same interfaces
+-as before, which could cause the program to fail to compile.
+-To avoid this, gopls inspects each conversion (explicit or implicit)
+-from the affected type to an interface type, and checks whether it
+-would remain valid after the renaming. If not, it aborts the renaming
+-with an error.
+-
+-If you intend to rename both the original method and the corresponding
+-methods of any matching interface types (as well as any methods of
+-types matching them in turn), you can indicate this by invoking the
+-rename operation on the interface method.
+-
+-Similarly, gopls will report an error if you rename a field of a
+-struct that happens to be an "anonymous" field that embeds a type,
+-since that would require a larger renaming involving the type as well.
+-If that is what you intend, you can again indicate this by
+-invoking the rename operation on the type.
+-
+-Renaming should never introduce a compilation error, but it may
+-introduce dynamic errors. For example, in a method renaming, if there
+-is no direct conversion of the affected type to the interface type,
+-but there is an intermediate conversion to a broader type (such as `any`) followed by a
+-type assertion to the interface type, then gopls may proceed to rename
+-the method, causing the type assertion to fail at run time.
+-Similar problems may arise with packages that use reflection, such as
+-`encoding/json` or `text/template`. There is no substitute for good
+-judgment and testing.
+-
+-Special cases:
+-
+-- When renaming the declaration of a method receiver, the tool also
+- attempts to rename the receivers of all other methods associated
+- with the same named type. Each other receiver that cannot be fully
+- renamed is quietly skipped. Renaming any _use_ of a receiver affects
+- only that variable.
+-
+- ```go
+- type Counter struct { x int }
+-
+- Rename here to affect only this method
+- ↓
+- func (c *Counter) Inc() { c.x++ }
+- func (c *Counter) Dec() { c.x++ }
+- ↑
+- Rename here to affect all methods
+- ```
+-
+-- Renaming a package declaration additionally causes the package's
+- directory to be renamed.
+-
+-Some tips for best results:
+-
+-- The safety checks performed by the Rename algorithm require type
+- information. If the program is grossly malformed, there may be
+- insufficient information for it to run (https://go.dev/issue/41870),
+- and renaming cannot generally be used to fix a type error (https://go.dev/issue/41851).
+- When refactoring, we recommend working in small steps, repairing any
+- problems as you go, so that as much as possible of the program
+- compiles at each step.
+-- Sometimes it may be desirable for a renaming operation to change the
+- reference structure of the program, for example to intentionally
+- combine two variables x and y by renaming y to x.
+- The renaming tool is too strict to help in this case (https://go.dev/issue/41852).
+-
+-
+-
+-For the gory details of gopls' rename algorithm, you may be interested
+-in the latter half of this 2015 GothamGo talk:
+-[Using go/types for Code Comprehension and Refactoring Tools](https://www.youtube.com/watch?v=p_cz7AxVdfg).
+-
+-Client support:
+-
+-- **VS Code**: Use "[Rename symbol](https://code.visualstudio.com/docs/editor/editingevolved#_rename-symbol)" menu item (`F2`).
+-- **Emacs + eglot**: Use `M-x eglot-rename`, or `M-x go-rename` from [go-mode](https://github.com/dominikh/go-mode.el).
+-- **Vim + coc.nvim**: Use the `coc-rename` command.
+-- **CLI**: `gopls rename file.go:#offset newname`
+-
+-
+-## `refactor.extract`: Extract function/method/variable
+-
+-The `refactor.extract` family of code actions all return commands that
+-replace the selected expression or statements with a reference to a
+-newly created declaration that contains the selected code:
+-
+-- **`refactor.extract.function`** replaces one or more complete statements by a
+- call to a new function named `newFunction` whose body contains the
+- statements. The selection must enclose fewer statements than the
+- entire body of the existing function.
+-
+- 
+- 
+-
+-- **`refactor.extract.method`** is a variant of "Extract function" offered when
+- the selected statements belong to a method. The newly created function
+- will be a method of the same receiver type.
+-
+-- **`refactor.extract.variable`** replaces an expression by a reference to a new
+- local variable named `newVar` initialized by the expression:
+-
+- 
+- 
+-
+-- **`refactor.extract.constant** does the same thing for a constant
+- expression, introducing a local const declaration.
+-- **`refactor.extract.variable-all`** replaces all occurrences of the selected expression
+-within the function with a reference to a new local variable named `newVar`.
+-This extracts the expression once and reuses it wherever it appears in the function.
+-
+- 
+- 
+-
+- - **`refactor.extract.constant-all** does the same thing for a constant
+- expression, introducing a local const declaration.
+-If the default name for the new declaration is already in use, gopls
+-generates a fresh name.
+-
+-Extraction is a challenging problem requiring consideration of
+-identifier scope and shadowing, control
+-flow such as `break`/`continue` in a loop or `return` in a
+-function, cardinality of variables, and even subtle issues of style.
+-In each case, the tool will try to update the extracted statements
+-as needed to avoid build breakage or behavior changes.
+-Unfortunately, gopls' Extract algorithms are considerably less
+-rigorous than the Rename and Inline operations, and we are aware of a
+-number of cases where it falls short, including:
+-
+-- https://github.com/golang/go/issues/66289
+-- https://github.com/golang/go/issues/65944
+-- https://github.com/golang/go/issues/63394
+-- https://github.com/golang/go/issues/61496
+-
+-The following Extract features are planned for 2024 but not yet supported:
+-
+-- **Extract parameter struct** will replace two or more parameters of a
+- function by a struct type with one field per parameter; see https://go.dev/issue/65552.
+-
+-
+-- **Extract interface for type** will create a declaration of an
+- interface type with all the methods of the selected concrete type;
+- see https://go.dev/issue/65721 and https://go.dev/issue/46665.
+-
+-
+-## `refactor.extract.toNewFile`: Extract declarations to new file
+-
+-(Available from gopls/v0.17.0)
+-
+-If you select one or more top-level declarations, gopls will offer an
+-"Extract declarations to new file" code action that moves the selected
+-declarations into a new file whose name is based on the first declared
+-symbol.
+-Import declarations are created as needed.
+-Gopls also offers this code action when the selection is just the
+-first token of the declaration, such as `func` or `type`.
+-
+-
+-
+-
+-
+-
+-## `refactor.inline.call`: Inline call to function
+-
+-For a `codeActions` request where the selection is (or is within) a
+-call of a function or method, gopls will return a command of kind
+-`refactor.inline.call`, whose effect is to inline the function call.
+-
+-The screenshots below show a call to `sum` before and after inlining:
+-
+-
+-
+-
+-
+-
+-Inlining replaces the call expression by a copy of the function body,
+-with parameters replaced by arguments.
+-Inlining is useful for a number of reasons.
+-Perhaps you want to eliminate a call to a deprecated
+-function such as `ioutil.ReadFile` by replacing it with a call to the
+-newer `os.ReadFile`; inlining will do that for you.
+-Or perhaps you want to copy and modify an existing function in some
+-way; inlining can provide a starting point.
+-The inlining logic also provides a building block for
+-other refactorings, such as "change signature".
+-
+-Not every call can be inlined.
+-Of course, the tool needs to know which function is being called, so
+-you can't inline a dynamic call through a function value or interface
+-method; but static calls to methods are fine.
+-Nor can you inline a call if the callee is declared in another package
+-and refers to non-exported parts of that package, or to [internal
+-packages](https://go.dev/doc/go1.4#internalpackages) that are
+-inaccessible to the caller.
+-Calls to generic functions are not yet supported
+-(https://go.dev/issue/63352), though we plan to fix that.
+-
+-When inlining is possible, it's critical that the tool preserve
+-the original behavior of the program.
+-We don't want refactoring to break the build, or, worse, to introduce
+-subtle latent bugs.
+-This is especially important when inlining tools are used to perform
+-automated clean-ups in large code bases;
+-we must be able to trust the tool.
+-Our inliner is very careful not to make guesses or unsound
+-assumptions about the behavior of the code.
+-However, that does mean it sometimes produces a change that differs
+-from what someone with expert knowledge of the same code might have
+-written by hand.
+-
+-In the most difficult cases, especially with complex control flow, it
+-may not be safe to eliminate the function call at all.
+-For example, the behavior of a `defer` statement is intimately tied to
+-its enclosing function call, and `defer` is the only control
+-construct that can be used to handle panics, so it cannot be reduced
+-into simpler constructs.
+-So, for example, given a function f defined as:
+-
+-```go
+-func f(s string) {
+- defer fmt.Println("goodbye")
+- fmt.Println(s)
+-}
+-```
+-
+-a call `f("hello")` will be inlined to:
+-
+-```go
+- func() {
+- defer fmt.Println("goodbye")
+- fmt.Println("hello")
+- }()
+-```
+-
+-Although the parameter was eliminated, the function call remains.
+-
+-An inliner is a bit like an optimizing compiler.
+-A compiler is considered "correct" if it doesn't change the meaning of
+-the program in translation from source language to target language.
+-An _optimizing_ compiler exploits the particulars of the input to
+-generate better code, where "better" usually means more efficient.
+-As users report inputs that cause the compiler to emit suboptimal
+-code, the compiler is improved to recognize more cases, or more rules,
+-and more exceptions to rules---but this process has no end.
+-Inlining is similar, except that "better" code means tidier code.
+-The most conservative translation provides a simple but (hopefully)
+-correct foundation, on top of which endless rules, and exceptions to
+-rules, can embellish and improve the quality of the output.
+-
+-Here are some of the technical challenges involved in sound inlining:
+-
+-- **Effects:** When replacing a parameter by its argument expression,
+- we must be careful not to change the effects of the call. For
+- example, if we call a function `func twice(x int) int { return x + x }`
+- with `twice(g())`, we do not want to see `g() + g()`, which would
+- cause g's effects to occur twice, and potentially each call might
+- return a different value. All effects must occur the same number of
+- times, and in the same order. This requires analyzing both the
+- arguments and the callee function to determine whether they are
+- "pure", whether they read variables, or whether (and when) they
+- update them too. The inliner will introduce a declaration such as
+- `var x int = g()` when it cannot prove that it is safe to substitute
+- the argument throughout.
+-
+-- **Constants:** If inlining always replaced a parameter by its argument
+- when the value is constant, some programs would no longer build
+- because checks previously done at run time would happen at compile time.
+- For example `func index(s string, i int) byte { return s[i] }`
+- is a valid function, but if inlining were to replace the call `index("abc", 3)`
+- by the expression `"abc"[3]`, the compiler will report that the
+- index `3` is out of bounds for the string `"abc"`.
+- The inliner will prevent substitution of parameters by problematic
+- constant arguments, again introducing a `var` declaration instead.
+-
+-- **Referential integrity:** When a parameter variable is replaced by
+- its argument expression, we must ensure that any names in the
+- argument expression continue to refer to the same thing---not to a
+- different declaration in the callee function body that happens to
+- use the same name. The inliner must replace local references such as
+- `Printf` by qualified references such as `fmt.Printf`, and add an
+- import of package `fmt` as needed.
+-
+-- **Implicit conversions:** When passing an argument to a function, it is
+- implicitly converted to the parameter type. If we eliminate the parameter
+- variable, we don't want to lose the conversion as it may be important. For
+- example, in `func f(x any) { y := x; fmt.Printf("%T", &y) }` the type of
+- variable y is `any`, so the program prints `"*interface{}"`. But if inlining
+- the call `f(1)` were to produce the statement `y := 1`, then the type of y
+- would have changed to `int`, which could cause a compile error or, as in this
+- case, a bug, as the program now prints `"*int"`. When the inliner substitutes
+- a parameter variable by its argument value, it may need to introduce explicit
+- conversions of each value to the original parameter type, such as `y :=
+- any(1)`.
+-
+-- **Last reference:** When an argument expression has no effects
+- and its corresponding parameter is never used, the expression
+- may be eliminated. However, if the expression contains the last
+- reference to a local variable at the caller, this may cause a compile
+- error because the variable is now unused. So the inliner must be
+- cautious about eliminating references to local variables.
+-
+-This is just a taste of the problem domain. If you're curious, the
+-documentation for [golang.org/x/tools/internal/refactor/inline](https://pkg.go.dev/golang.org/x/tools/internal/refactor/inline) has
+-more detail. All of this is to say, it's a complex problem, and we aim
+-for correctness first of all. We've already implemented a number of
+-important "tidiness optimizations" and we expect more to follow.
+-
+-
+-
+-## `refactor.inline.variable`: Inline local variable
+-
+-For a `codeActions` request where the selection is (or is within) an
+-identifier that is a use of a local variable whose declaration has an
+-initializer expression, gopls will return a code action of kind
+-`refactor.inline.variable`, whose effect is to inline the variable:
+-that is, to replace the reference by the variable's initializer
+-expression.
+-
+-For example, if invoked on the identifier `s` in the call `println(s)`:
+-```go
+-func f(x int) {
+- s := fmt.Sprintf("+%d", x)
+- println(s)
+-}
+-```
+-the code action transforms the code to:
+-
+-```go
+-func f(x int) {
+- s := fmt.Sprintf("+%d", x)
+- println(fmt.Sprintf("+%d", x))
+-}
+-```
+-
+-(In this instance, `s` becomes an unreferenced variable which you will
+-need to remove.)
+-
+-The code action always replaces the reference by the initializer
+-expression, even if there are later assignments to the variable (such
+-as `s = ""`).
+-
+-The code action reports an error if it is not possible to make the
+-transformation because one of the identifiers within the initializer
+-expression (e.g. `x` in the example above) is shadowed by an
+-intervening declaration, as in this example:
+-
+-```go
+-func f(x int) {
+- s := fmt.Sprintf("+%d", x)
+- {
+- x := 123
+- println(s, x) // error: cannot replace s with fmt.Sprintf(...) since x is shadowed
+- }
+-}
+-```
+-
+-
+-## `refactor.rewrite`: Miscellaneous rewrites
+-
+-This section covers a number of transformations that are accessible as
+-code actions whose kinds are children of `refactor.rewrite`.
+-
+-
+-### `refactor.rewrite.removeUnusedParam`: Remove unused parameter
+-
+-The [`unusedparams` analyzer](../analyzers.md#unusedparams) reports a
+-diagnostic for each parameter that is not used within the function body.
+-For example:
+-
+-```go
+-func f(x, y int) { // "unused parameter: x"
+- fmt.Println(y)
+-}
+-```
+-
+-It does _not_ report diagnostics for address-taken functions, which
+-may need all their parameters, even unused ones, in order to conform
+-to a particular function signature.
+-Nor does it report diagnostics for exported functions,
+-which may be address-taken by another package.
+-(A function is _address-taken_ if it is used other than in call position, `f(...)`.)
+-
+-In addition to the diagnostic, it suggests two possible fixes:
+-
+-1. rename the parameter to `_` to emphasize that it is unreferenced (an immediate edit); or
+-2. delete the parameter altogether, using a `ChangeSignature` command, updating all callers.
+-
+-Fix \#2 uses the same machinery as "Inline function call" (see above)
+-to ensure that the behavior of all existing calls is preserved, even
+-when the argument expression for the deleted parameter has side
+-effects, as in the example below.
+-
+-
+-
+-
+-Observe that in the first call, the argument `chargeCreditCard()` was
+-not deleted because of potential side effects, whereas in the second
+-call, the argument 2, a constant, was safely deleted.
+-
+-
+-
+-### `refactor.rewrite.moveParam{Left,Right}`: Move function parameters
+-
+-When the selection is a parameter in a function or method signature, gopls
+-offers a code action to move the parameter left or right (if feasible),
+-updating all callers accordingly.
+-
+-For example:
+-
+-```go
+-func Foo(x, y int) int {
+- return x + y
+-}
+-
+-func _() {
+- _ = Foo(0, 1)
+-}
+-```
+-
+-becomes
+-
+-```go
+-func Foo(y, x int) int {
+- return x + y
+-}
+-
+-func _() {
+- _ = Foo(1, 0)
+-}
+-```
+-
+-following a request to move `x` right, or `y` left.
+-
+-This is a primitive building block of more general "Change signature"
+-operations. We plan to generalize this to arbitrary signature rewriting, but
+-the language server protocol does not currently offer good support for user
+-input into refactoring operations (see
+-[microsoft/language-server-protocol#1164](https://github.com/microsoft/language-server-protocol/issues/1164)).
+-Therefore, any such refactoring will require custom client-side logic. (As a
+-very hacky workaround, you can express arbitrary parameter movement by invoking
+-Rename on the `func` keyword of a function declaration, but this interface is
+-just a temporary stopgap.)
+-
+-
+-### `refactor.rewrite.changeQuote`: Convert string literal between raw and interpreted
+-
+-When the selection is a string literal, gopls offers a code action
+-to convert the string between raw form (`` `abc` ``) and interpreted
+-form (`"abc"`) where this is possible:
+-
+-
+-
+-
+-Applying the code action a second time reverts back to the original
+-form.
+-
+-
+-### `refactor.rewrite.invertIf`: Invert 'if' condition
+-
+-When the selection is within an `if`/`else` statement that is not
+-followed by `else if`, gopls offers a code action to invert the
+-statement, negating the condition and swapping the `if` and and `else`
+-blocks.
+-
+-
+-
+-
+-
+-
+-
+-
+-### `refactor.rewrite.{split,join}Lines`: Split elements into separate lines
+-
+-When the selection is within a bracketed list of items such as:
+-
+-- the **elements** of a composite literal, `[]T{a, b, c}`,
+-- the **arguments** of a function call, `f(a, b, c)`,
+-- the **groups of parameters** of a function signature, `func(a, b, c int, d, e bool)`, or
+-- its **groups of results**, `func() (x, y string, z rune)`,
+-
+-gopls will offer the "Split [items] into separate lines" code
+-action, which would transform the forms above into these forms:
+-
+-```go
+-[]T{
+- a,
+- b,
+- c,
+-}
+-
+-f(
+- a,
+- b,
+- c,
+-)
+-
+-func(
+- a, b, c int,
+- d, e bool,
+-)
+-
+-func() (
+- x, y string,
+- z rune,
+-)
+-```
+-
+-Observe that in the last two cases, each
+-[group](https://pkg.go.dev/go/ast#Field) of parameters or results is
+-treated as a single item.
+-
+-The opposite code action, "Join [items] into one line", undoes the operation.
+-Neither action is offered if the list is already full split or joined,
+-respectively, or trivial (fewer than two items).
+-
+-These code actions are not offered for lists containing `//`-style
+-comments, which run to the end of the line.
+-
+-
+-
+-
+-### `refactor.rewrite.fillStruct`: Fill struct literal
+-
+-When the cursor is within a struct literal `S{}`, gopls offers the
+-"Fill S" code action, which populates each missing field of the
+-literal that is accessible.
+-
+-It uses the following heuristic to choose the value assigned to each
+-field: it finds candidate variables, constants, and functions that are
+-assignable to the field, and picks the one whose name is the closest
+-match to the field name.
+-If there are none, it uses the zero value (such as `0`, `""`, or
+-`nil`) of the field's type.
+-
+-In the example below, a
+-[`slog.HandlerOptions`](https://pkg.go.dev/golang.org/x/exp/slog#HandlerOptions)
+-struct literal is filled in using two local variables (`level` and
+-`add`) and a function (`replace`):
+-
+-
+-
+-
+-Caveats:
+-
+-- This code action requires type information for the struct type, so
+- if it is defined in another package that is not yet imported, you
+- may need to "organize imports" first, for example by saving the
+- file.
+-- Candidate declarations are sought only in the current file, and only
+- above the current point. Symbols declared beneath the current point,
+- or in other files in the package, are not considered; see
+- https://go.dev/issue/68224.
+-
+-
+-### `refactor.rewrite.fillSwitch`: Fill switch
+-
+-When the cursor is within a switch statement whose operand type is an
+-_enum_ (a finite set of named constants), or within a type switch,
+-gopls offers the "Add cases for T" code action, which populates the
+-switch statement by adding a case for each accessible named constant
+-of the enum type, or, for a type switch, by adding a case for each
+-accessible named non-interface type that implements the interface.
+-Only missing cases are added.
+-
+-The screenshots below show a type switch whose operand has the
+-[`net.Addr`](https://pkg.go.dev/net#Addr) interface type. The code
+-action adds one case per concrete network address type, plus a default
+-case that panics with an informative message if an unexpected operand
+-is encountered.
+-
+-
+-
+-
+-And these screenshots illustrate the code action adding cases for each
+-value of the
+-[`html.TokenType`](https://pkg.go.dev/golang.org/x/net/html#TokenType)
+-enum type, which represents the various types of token from
+-which HTML documents are composed:
+-
+-
+-
+-
+-
+-
+-### `refactor.rewrite.eliminateDotImport`: Eliminate dot import
+-
+-When the cursor is on a dot import gopls can offer the "Eliminate dot import"
+-code action, which removes the dot from the import and qualifies uses of the
+-package throughout the file. This code action is offered only if
+-each use of the package can be qualified without collisions with existing names.
+-
+-
+-### `refactor.rewrite.addTags`: Add struct tags
+-
+-When the cursor is within a struct, this code action adds to each field a `json`
+-struct tag that specifies its JSON name, using lower case with underscores
+-(e.g. LinkTarget becomes link_target). For a highlighted selection, it only
+-adds tags on selected fields.
+-
+-
+-### `refactor.rewrite.removeTags`: Remove struct tags
+-
+-When the cursor is within a struct, this code action clears struct tags on
+-all struct fields. For a highlighted selection, it removes tags from only
+-the selected fields.
+diff -urN a/gopls/doc/features/web.md b/gopls/doc/features/web.md
+--- a/gopls/doc/features/web.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/features/web.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,185 +0,0 @@
+----
+-title: "Gopls: Web-based features"
+----
+-
+-The LSP
+-[`window.showDocument`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showDocument) request
+-allows the server to instruct the client to open a file in the editor
+-or a web page in a browser. It is the basis for a number of gopls
+-features that report information about your program through a web
+-interface.
+-
+-We recognize that a web interface is not ideal for everyone: some
+-users prefer a full-screen editor layout and dislike switching
+-windows; others may work in a text-only terminal without a window
+-system, perhaps over remote ssh or on the Linux console.
+-Unfortunately, the LSP lacks several natural kinds of extensibility,
+-including the ability for servers to define:
+-
+-- queries that [generalize a References
+- query](https://github.com/microsoft/language-server-protocol/issues/1911),
+- displaying results using similar UI elements;
+-- commands that [produce a stream of
+- text](https://github.com/joaotavora/eglot/discussions/1402), like a
+- typical shell command or compiler, that the client can redirect to
+- the editor's usual terminal-like UI element; or
+-- refactoring operations that, like Rename, [prompt the
+- user](https://github.com/microsoft/language-server-protocol/issues/1164)
+- for additional information.
+-
+-The web-based UI can help fill these gaps until such time as the LSP
+-provides standard ways of implementing these features.
+-
+-Gopls' web server listens on a `localhost` port. For security, all its
+-endpoints include a random string that serves as an authentication
+-token. The client, provided authenticated URLs by the server, will be
+-able to access your source code, but arbitrary processes running on
+-your machine will not.
+-Restarting the gopls process causes this secret to change, rendering
+-all existing previous URLs invalid; existing pages will display a banner
+-indicating that they have become disconnected.
+-
+-TODO: combine the web server and the debug server; see https://go.dev/issue/68229.
+-
+-Gopls supports two-way communication between the web browser and the
+-client editor. All of the web-based reports contain links to
+-declarations in your source code. Clicking on one of these links
+-causes gopls to send a `showDocument` request to your editor to open
+-the relevant source file at the appropriate line. This works even when
+-your source code has been modified but not saved.
+-(VS Code users: please upvote microsoft/vscode#208093 if you would
+-like your editor to raise its window when handling this event.)
+-
+-
+-## `source.doc`: Browse package documentation
+-
+-In any Go source file, a code action request returns a command to
+-"Browse package documentation". This command opens a browser window
+-showing the documentation for the current Go package, presented using
+-a similar design to https://pkg.go.dev.
+-
+-This allows you to preview the documentation for your packages, even
+-internal ones that may be unpublished externally. Reloading the page
+-updates the documentation to reflect your changes. It is not necessary
+-to save modified Go source files.
+-
+-
+-
+-Clicking on the link for a package-level symbol or method, which in
+-`pkg.go.dev` would ordinarily take you to a source-code viewer such as
+-GitHub or Google Code Search, causes your editor to navigate to the
+-relevant source file and line.
+-
+-Client support:
+-- **VS Code**: Use the "Source Action... > Browse documentation for package P" menu.
+-- **Emacs + eglot**: Use `M-x go-browse-doc` in [go-mode](https://github.com/dominikh/go-mode.el).
+-- **Vim + coc.nvim**: ??
+-
+-
+-
+-## `source.freesymbols`: Browse free symbols
+-
+-When studying code, either to understand it or to evaluate a different
+-organization or factoring, it is common to need to know what the
+-"inputs" are to a given chunk of code, either because you are
+-considering extracting it into its own function and want to know what
+-parameters it would take, or just to understand how one piece of a long
+-function relates to the preceding pieces.
+-
+-If you select a chunk of code, and invoke the "Browse free symbols"
+-[code action](transformation.md#code-actions), your editor will
+-open a browser displaying a report on the free symbols of the
+-selection. A symbol is "free" if it is referenced from within the
+-selection but defined outside of it. In essence, these are the inputs
+-to the selected chunk.
+-
+-
+-
+-The report classifies the symbols into imported, local, and
+-package-level symbols. The imported symbols are grouped by package,
+-and link to the documentation for the package, as described above.
+-Each of the remaining symbols is presented as a link that causes your
+-editor to navigate to its declaration.
+-
+-TODO: explain dotted paths.
+-
+-Client support:
+-- **VS Code**: Use the "Source Action... > Browse free symbols" menu.
+-- **Emacs + eglot**: Use `M-x go-browse-freesymbols` in [go-mode](https://github.com/dominikh/go-mode.el).
+-- **Vim + coc.nvim**: ??
+-
+-
+-
+-## `source.assembly`: Browse assembly
+-
+-When you're optimizing the performance of your code or investigating
+-an unexpected crash, it may sometimes be helpful to inspect the
+-assembly code produced by the compiler for a given Go function.
+-
+-If you position the cursor or selection within a function f,
+-gopls offers the "Browse assembly for f" [code action](transformation.md#code-actions).
+-This opens a web-based listing of the assembly for the function, plus
+-any functions nested within it.
+-
+-Each time you edit your source and reload the page, the current
+-package is recompiled and the listing is updated. It is not necessary
+-to save your modified files.
+-
+-The compiler's target architecture is the same as the one gopls uses
+-when analyzing the file: typically, this is your machine's GOARCH, but
+-when viewing a file with a build tag, such as one named `foo_amd64.go`
+-or containing the comment `//go:build amd64`, the tags determine the
+-architecture.
+-
+-Each instruction is displayed with a link that causes your editor to
+-navigate to the source line responsible for the instruction, according
+-to the debug information.
+-
+-
+-
+-The example above shows the arm64 assembly listing of
+-[`time.NewTimer`](https://pkg.go.dev/time#NewTimer).
+-Observe that the indicated instruction links to a source location
+-inside a different function, `syncTimer`, because the compiler
+-inlined the call from `NewTimer` to `syncTimer`.
+-
+-Browsing assembly is not yet supported for generic functions, package
+-initializers (`func init`), or functions in test packages.
+-(Contributions welcome!)
+-
+-Client support:
+-- **VS Code**: Use the "Source Action... > Browse GOARCH assembly for f" menu.
+-- **Emacs + eglot**: Use `M-x go-browse-assembly` in [go-mode](https://github.com/dominikh/go-mode.el).
+-- **Vim + coc.nvim**: ??
+-
+-
+-
+-## `source.splitPackage`: Split package into components
+-
+-The web-based "Split package" tool can help you split a complex
+-package into two or more components, ensuring that the dependencies
+-among those components are acyclic.
+-
+-Follow the instructions on the page to choose a set of named components,
+-assign each declaration to the most appropriate component, and then
+-visualize the dependencies between those components created by references
+-from one symbol to another.
+-
+-The figure below shows the tool operating on the `fmt` package, which
+-could (in principle) be split into three subpackages, one for
+-formatting (`Printf` and friends), one for scanning (`Scanf`), and one
+-for their common dependencies.
+-
+-
+-
+-(Try playing with the tool on this package: it's an instructive
+-exercise. The figure below shows the solution.)
+-
+-
+-
+-The tool does not currently perform the code transformation (moving
+-declarations to new packages, renaming symbols to export them as
+-needed), but we hope to add that in a future release.
+-
+-Client support:
+-- **VS Code**: Use the "Source Action... > Split package P" menu.
+diff -urN a/gopls/doc/index.md b/gopls/doc/index.md
+--- a/gopls/doc/index.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/index.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,196 +0,0 @@
+----
+-title: "Gopls: The language server for Go"
+----
+-
+-
+-`gopls` (pronounced "Go please") is the official [language
+-server](https://langserver.org) for Go, developed by the Go team. It
+-provides a wide variety of [IDE features](features/) to any
+-[LSP](https://microsoft.github.io/language-server-protocol/)-compatible
+-editor.
+-
+-
+-
+-You should not need to interact with `gopls` directly--it will be automatically
+-integrated into your editor. The specific features and settings vary slightly
+-by editor, so we recommend that you proceed to the
+-[documentation for your editor](#editors) below.
+-Also, the gopls documentation for each feature describes whether it is
+-supported in each client editor.
+-
+-This documentation (https://go.dev/gopls) describes the most recent release of gopls.
+-To preview documentation for the release under development, visit https://tip.golang.org/gopls.
+-
+-## Features
+-
+-Gopls supports a wide range of standard LSP features for navigation,
+-completion, diagnostics, analysis, and refactoring, and a number of
+-additional features not found in other language servers.
+-
+-See the [Index of features](features/) for complete
+-documentation on what Gopls can do for you.
+-
+-## Editors
+-
+-To get started with `gopls`, install an LSP plugin in your editor of choice.
+-
+-
+-
+-* [Acme](https://github.com/fhs/acme-lsp/blob/master/README.md)
+-* [Atom](https://github.com/MordFustang21/ide-gopls/blob/master/README.md)
+-* [Emacs](editor/emacs.md)
+-* [Helix](editor/helix.md)
+-* [Lapce](https://github.com/lapce-community/lapce-go/blob/master/README.md)
+-* [Sublime Text](editor/sublime.md)
+-* [VS Code](https://github.com/golang/vscode-go/blob/master/README.md)
+-* [Vim or Neovim](editor/vim.md)
+-* [Zed](editor/zed.md)
+-
+-If you use `gopls` with an editor that is not on this list, please send us a CL
+-[updating this documentation](contributing.md).
+-
+-## Installation
+-
+-To install the latest stable release of `gopls`, run the following command:
+-
+-```sh
+-go install golang.org/x/tools/gopls@latest
+-```
+-
+-Some editors, such as VS Code, will handle this step for you, and
+-ensure that Gopls is updated when a new stable version is released.
+-
+-After updating, you may need to restart running Gopls processes to
+-observe the effect. Each client has its own way to restart the server.
+-(On a UNIX machine, you can use the command `killall gopls`.)
+-
+-Learn more in the
+-[advanced installation instructions](advanced.md#installing-unreleased-versions).
+-
+-## Releases
+-
+-Gopls [releases](release/) follow [semantic versioning](http://semver.org), with
+-major changes and new features introduced only in new minor versions
+-(i.e. versions of the form `v*.N.0` for some N). Subsequent patch
+-releases contain only cherry-picked fixes or superficial updates.
+-
+-In order to align with the
+-[Go release timeline](https://github.com/golang/go/wiki/Go-Release-Cycle#timeline),
+-we aim to release a new minor version of Gopls approximately every three
+-months, with patch releases approximately every month, according to the
+-following table:
+-
+-| Month | Version(s) |
+-| ---- | ------- |
+-| Jan | `v*..0` |
+-| Jan-Mar | `v*..*` |
+-| Apr | `v*..0` |
+-| Apr-Jun | `v*..*` |
+-| Jul | `v*..0` |
+-| Jul-Sep | `v*..*` |
+-| Oct | `v*..0` |
+-| Oct-Dec | `v*..*` |
+-
+-For more background on this policy, see https://go.dev/issue/55267.
+-
+-## Setting up your workspace
+-
+-`gopls` supports both Go module, multi-module and GOPATH modes. See the
+-[workspace documentation](workspace.md) for information on supported
+-workspace layouts.
+-
+-## Configuration
+-
+-You can configure `gopls` to change your editor experience or view additional
+-debugging information. Configuration options will be made available by your
+-editor, so see your [editor's instructions](#editors) for specific details. A
+-full list of `gopls` settings can be found in the [settings documentation](settings.md).
+-
+-### Environment variables
+-
+-`gopls` inherits your editor's environment, so be aware of any environment
+-variables you configure. Some editors, such as VS Code, allow users to
+-selectively override the values of some environment variables.
+-
+-## Support policy
+-
+-Gopls is maintained by engineers on the
+-[Go tools team](https://github.com/orgs/golang/teams/tools-team/members),
+-who actively monitor the
+-[Go](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+label%3Agopls)
+-and
+-[VS Code Go](https://github.com/golang/vscode-go/issues) issue trackers.
+-
+-### Supported Go versions
+-
+-`gopls` follows the
+-[Go Release Policy](https://go.dev/doc/devel/release#policy), meaning
+-that it officially supports only the two most recent major Go releases.
+-
+-When using gopls, there are three versions to be aware of:
+-1. The _gopls build go version_: the version of Go used to build gopls.
+-2. The _go command version_: the version of the go list command executed by
+- gopls to load information about your workspace.
+-3. The _language version_: the version in the go directive of the current
+- file's enclosing go.mod file, which determines the file's Go language
+- semantics.
+-
+-Starting with the release of Go 1.23.0 and gopls@v0.17.0 in August 2024, we
+-will only support the most recent Go version as the _gopls build go version_.
+-However, due to the [forward compatibility](https://go.dev/blog/toolchain)
+-support added in Go 1.21, as long as Go 1.21 or later are used to install
+-gopls, any necessary toolchain upgrade will be handled automatically, just like
+-any other dependency.
+-
+-Additionally, starting with gopls@v0.17.0, the _go command version_ will narrow
+-from 4 versions to 3. This is more consistent with the Go Release Policy.
+-
+-Gopls supports **all** Go versions as its _language version_, by providing
+-compiler errors based on the language version and filtering available standard
+-library symbols based on the standard library APIs available at that Go
+-version.
+-
+-Maintaining support for building gopls with legacy versions of Go caused
+-[significant friction](https://go.dev/issue/50825) for gopls maintainers and
+-held back other improvements. If you are unable to install a supported version
+-of Go on your system, you can still install an older version of gopls. The
+-following table shows the final gopls version that supports a given Go version.
+-Go releases more recent than those in the table can be used with any version of
+-gopls.
+-
+-| Go Version | Final gopls version with support (without warnings) |
+-| ----------- | --------------------------------------------------- |
+-| Go 1.12 | [gopls@v0.7.5](https://github.com/golang/tools/releases/tag/gopls%2Fv0.7.5) |
+-| Go 1.15 | [gopls@v0.9.5](https://github.com/golang/tools/releases/tag/gopls%2Fv0.9.5) |
+-| Go 1.17 | [gopls@v0.11.0](https://github.com/golang/tools/releases/tag/gopls%2Fv0.11.0) |
+-| Go 1.18 | [gopls@v0.14.2](https://github.com/golang/tools/releases/tag/gopls%2Fv0.14.2) |
+-| Go 1.20 | [gopls@v0.15.3](https://github.com/golang/tools/releases/tag/gopls%2Fv0.15.3) |
+-
+-### Supported build systems
+-
+-`gopls` currently only supports the `go` command, so if you are using
+-a different build system, `gopls` will not work well. Bazel is not officially
+-supported, but may be made to work with an appropriately configured
+-[go/packages driver](https://pkg.go.dev/golang.org/x/tools/go/packages#hdr-The_driver_protocol).
+-See [bazelbuild/rules_go#512](https://github.com/bazelbuild/rules_go/issues/512)
+-for more information.
+-You can follow [these instructions](https://github.com/bazelbuild/rules_go/wiki/Editor-setup)
+-to configure your `gopls` to work with Bazel.
+-
+-### Troubleshooting
+-
+-If you are having issues with `gopls`, please follow the steps described in the
+-[troubleshooting guide](troubleshooting.md).
+-
+-## Additional information
+-
+-* [Command-line interface](command-line.md)
+-* [Advanced topics](advanced.md)
+-* [Open issues](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+label%3Agopls)
+-* [Contributing to `gopls`](contributing.md)
+diff -urN a/gopls/doc/inlayHints.md b/gopls/doc/inlayHints.md
+--- a/gopls/doc/inlayHints.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/inlayHints.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,112 +0,0 @@
+----
+-title: "Gopls: Inlay hints"
+----
+-
+-Inlay hints are helpful annotations that the editor can optionally
+-display in-line in the source code, such as the names of parameters in
+-a function call. This document describes the inlay hints available
+-from `gopls`.
+-
+-
+-
+-## **assignVariableTypes**
+-
+-`"assignVariableTypes"` controls inlay hints for variable types in assign statements:
+-```go
+- i/* int*/, j/* int*/ := 0, len(r)-1
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"assignVariableTypes": true}`.**
+-
+-## **compositeLiteralFields**
+-
+-`"compositeLiteralFields"` inlay hints for composite literal field names:
+-```go
+- {/*in: */"Hello, world", /*want: */"dlrow ,olleH"}
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"compositeLiteralFields": true}`.**
+-
+-## **compositeLiteralTypes**
+-
+-`"compositeLiteralTypes"` controls inlay hints for composite literal types:
+-```go
+- for _, c := range []struct {
+- in, want string
+- }{
+- /*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"},
+- }
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"compositeLiteralTypes": true}`.**
+-
+-## **constantValues**
+-
+-`"constantValues"` controls inlay hints for constant values:
+-```go
+- const (
+- KindNone Kind = iota/* = 0*/
+- KindPrint/* = 1*/
+- KindPrintf/* = 2*/
+- KindErrorf/* = 3*/
+- )
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"constantValues": true}`.**
+-
+-## **functionTypeParameters**
+-
+-`"functionTypeParameters"` inlay hints for implicit type parameters on generic functions:
+-```go
+- myFoo/*[int, string]*/(1, "hello")
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"functionTypeParameters": true}`.**
+-
+-## **ignoredError**
+-
+-`"ignoredError"` inlay hints for implicitly discarded errors:
+-```go
+- f.Close() // ignore error
+-```
+-This check inserts an `// ignore error` hint following any
+-statement that is a function call whose error result is
+-implicitly ignored.
+-
+-To suppress the hint, write an actual comment containing
+-"ignore error" following the call statement, or explictly
+-assign the result to a blank variable. A handful of common
+-functions such as `fmt.Println` are excluded from the
+-check.
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"ignoredError": true}`.**
+-
+-## **parameterNames**
+-
+-`"parameterNames"` controls inlay hints for parameter names:
+-```go
+- parseInt(/* str: */ "123", /* radix: */ 8)
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"parameterNames": true}`.**
+-
+-## **rangeVariableTypes**
+-
+-`"rangeVariableTypes"` controls inlay hints for variable types in range statements:
+-```go
+- for k/* int*/, v/* string*/ := range []string{} {
+- fmt.Println(k, v)
+- }
+-```
+-
+-
+-**Disabled by default. Enable it by setting `"hints": {"rangeVariableTypes": true}`.**
+-
+-
+diff -urN a/gopls/doc/README.md b/gopls/doc/README.md
+--- a/gopls/doc/README.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/README.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1 +0,0 @@
+-See [index.md](index.md).
+diff -urN a/gopls/doc/release/README b/gopls/doc/release/README
+--- a/gopls/doc/release/README 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/release/README 1969-12-31 18:00:00.000000000 -0600
+@@ -1,10 +0,0 @@
+-This directory contains the draft release notes for each upcoming release.
+-
+-Be sure to update the file for the forthcoming release in the same CL
+-that you add new features or fix noteworthy bugs.
+-
+-See https://github.com/golang/tools/releases for all past releases.
+-
+-Tip: when reviewing edits to markdown files in Gerrit, to see the
+-rendered form, click the "Open in Code Search" link (magnifying glass
+-in blue square) then click "View in > gitiles" (shortcut: `v g`).
+diff -urN a/gopls/doc/release/v0.16.0.md b/gopls/doc/release/v0.16.0.md
+--- a/gopls/doc/release/v0.16.0.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/release/v0.16.0.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,288 +0,0 @@
+----
+-title: "Gopls release v0.16.0"
+----
+-
+-```
+-go install golang.org/x/tools/gopls@v0.16.2
+-```
+-
+-This release includes several features and bug fixes, and is the first
+-version of gopls to support Go 1.23. To install it, run:
+-
+-## New support policy; end of support for Go 1.19 and Go 1.20
+-
+-**TL;DR: We are narrowing gopls' support window, but this is unlikely to
+-affect you as long as you use at least Go 1.21 to build gopls. This doesn't
+-affect gopls' support for the code you are writing.**
+-
+-This is the last release of gopls that may be built with Go 1.19 or Go 1.20,
+-and also the last to support integrating with go command versions 1.19 and
+-1.20. If built or used with either of these Go versions, it will display
+-a message advising the user to upgrade.
+-
+-When using gopls, there are three versions to be aware of:
+-
+-1. The _gopls build go version_: the version of Go used to build gopls.
+-2. The _go command version_: the version of the go list command executed by
+- gopls to load information about your workspace.
+-3. The _language version_: the version in the go directive of the current
+- file's enclosing go.mod file, which determines the file's Go language
+- semantics.
+-
+-This gopls release, v0.16.0, is the final release to support Go 1.19 and Go
+-1.20 as the _gopls build go version_ or _go command version_. There is no
+-change to gopls' support for all _language versions_--in fact this support has
+-somewhat improved with the addition of the `stdversion` analyzer (see below).
+-
+-Starting with gopls@v0.17.0, which will be released after Go 1.23.0 is released
+-in August, gopls will only support the latest version of Go as the
+-_gopls build go version_.
+-However, thanks to the [forward compatibility](https://go.dev/blog/toolchain)
+-added to Go 1.21, any necessary toolchain upgrade should be handled
+-automatically for users of Go 1.21 or later, just like any other dependency.
+-Additionally, we are reducing our _go command version_ support window from
+-4 versions to 3. Note that this means if you have at least Go 1.21 installed on
+-your system, you should still be able to `go install` and use gopls@v0.17.0.
+-
+-We have no plans to ever change our _language version_ support: we expect that
+-gopls will always support developing programs that target _any_ Go version.
+-
+-By focusing on building gopls with the latest Go version, we can significantly
+-reduce our maintenance burden and help improve the stability of future gopls
+-releases. See the newly updated
+-[support policy](https://github.com/golang/tools/tree/master/gopls#support-policy)
+-for details. Please comment on golang/go#65917 if
+-you have concerns about this change.
+-
+-## Configuration changes
+-
+-- The experimental `allowImplicitNetworkAccess` setting is deprecated (but not
+- yet removed). Please comment on golang/go#66861 if you use this
+- setting and would be impacted by its removal.
+-
+-## New features
+-
+-### Go 1.23 support
+-
+-This version of gopls is the first to support the new language features of Go 1.23,
+-including
+-[range-over-func](https://go.dev/wiki/RangefuncExperiment) iterators
+-and support for the
+-[`godebug` directive](https://go.dev/ref/mod#go-mod-file-godebug)
+-in go.mod files.
+-
+-### Integrated documentation viewer
+-
+-Gopls now offers a "Browse documentation" code action that opens a
+-local web page displaying the generated documentation for Go packages
+-and symbols in a form similar to https://pkg.go.dev.
+-The package or symbol is chosen based on the current selection.
+-
+-Use this feature to preview the marked-up documentation as you prepare API
+-changes, or to read the documentation for locally edited packages,
+-even ones that have not yet been saved. Reload the page after an edit
+-to see updated documentation.
+-
+-
+-
+-As in `pkg.go.dev`, the heading for each symbol contains a link to the
+-source code of its declaration. In `pkg.go.dev`, these links would refer
+-to a source code page on a site such as GitHub or Google Code Search.
+-However, in gopls' internal viewer, clicking on one of these links will
+-cause your editor to navigate to the declaration.
+-(This feature requires that your LSP client honors the `showDocument` downcall.)
+-
+-
+-
+-Editor support:
+-
+-- VS Code: use the "Source action > Browse documentation for func fmt.Println" menu item.
+- Note: source links navigate the editor but don't yet raise the window yet.
+- Please upvote microsoft/vscode#208093 and microsoft/vscode#207634 (temporarily closed).
+-- Emacs: requires eglot v1.17. Use `M-x go-browse-doc` from github.com/dominikh/go-mode.el.
+-
+-The `linksInHover` setting now supports a new value, `"gopls"`,
+-that causes documentation links in the Markdown output
+-of the Hover operation to link to gopls' internal doc viewer.
+-
+-### Browse free symbols
+-
+-Gopls offers another web-based code action, "Browse free symbols",
+-which displays the free symbols referenced by the selected code.
+-
+-A symbol is "free" if it is referenced within the selection but
+-declared outside of it. The free symbols that are variables are
+-approximately the set of parameters that would be needed if the block
+-were extracted into its own function.
+-
+-Even when you don't intend to extract a block into a new function,
+-this information can help you to tell at a glance what names a block
+-of code depends on.
+-
+-Each dotted path of identifiers (such as `file.Name.Pos`) is reported
+-as a separate item, so that you can see which parts of a complex
+-type are actually needed.
+-
+-The free symbols of the body of a function may reveal that
+-only a small part (a single field of a struct, say) of one of the
+-function's parameters is used, allowing you to simplify and generalize
+-the function by choosing a different type for that parameter.
+-
+-
+-
+-Editor support:
+-
+-- VS Code: use the `Source action > Browse free symbols` menu item.
+-- Emacs: requires eglot v1.17. Use `M-x go-browse-freesymbols` from github.com/dominikh/go-mode.el.
+-
+-### Browse assembly
+-
+-Gopls offers a third web-based code action, "Browse assembly for f",
+-which displays an assembly listing of the declaration of the function
+-f enclosing the selected code, plus any nested functions such as
+-function literals or deferred calls.
+-
+-Gopls invokes the compiler to generate the report;
+-reloading the page updates the report.
+-
+-The machine architecture is determined by the build
+-configuration that gopls selects for the current file.
+-This is usually the same as your machine's GOARCH unless you are
+-working in a file with `go:build` tags for a different architecture.
+-
+-
+-
+-Gopls cannot yet display assembly for generic functions:
+-generic functions are not fully compiled until they are instantiated,
+-but any function declaration enclosing the selection cannot be an
+-instantiated generic function.
+-
+-
+-
+-Editor support:
+-
+-- VS Code: use the "Source action > Browse assembly for f" menu item.
+-- Emacs: requires eglot v1.17. Use `M-x go-browse-assembly` from github.com/dominikh/go-mode.el.
+-
+-### `unusedwrite` analyzer
+-
+-The new
+-[unusedwrite](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite)
+-analyzer reports assignments, often to fields of structs, that have no
+-effect because, for example, the struct is never used again:
+-
+-```go
+-func scheme(host string) string {
+- u := &url.URL{
+- Host: host, // "unused write to field Host" (no need to construct a URL)
+- Scheme: "https:",
+- }
+- return u.Scheme
+-}
+-```
+-
+-This is at best an indication that the code is unnecessarily complex
+-(for instance, some dead code could be removed), but often indicates a
+-bug, as in this example:
+-
+-```go
+-type S struct { x int }
+-
+-func (s S) set(x int) {
+- s.x = x // "unused write to field x" (s should be a *S pointer)
+-}
+-```
+-
+-### `stdversion` analyzer
+-
+-The new
+-[`stdversion`](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion)
+-analyzer warns about the use of too-new standard library symbols based on the
+-version of the `go` directive in your `go.mod` file. This improves our support
+-for older _language versions_ (see above), even when gopls is built with
+-a recent Go version.
+-
+-Consider the go.mod file and Go file below.
+-The declaration of `var `alias refers to a type, `types.Alias`,
+-introduced in go1.22, but the file belongs to a module that requires
+-only go1.21, so the analyzer reports a diagnostic:
+-
+-```
+-module example.com
+-go 1.21
+-```
+-
+-```go
+-package p
+-
+-import "go/types"
+-
+-var alias types.Alias // types.Alias requires go1.22 or later (module is go1.21)
+-```
+-
+-When an individual file is build-tagged for a release of Go other than
+-than module's version, the analyzer will apply appropriate checks for
+-the file's version.
+-
+-### Two more vet analyzers
+-
+-The [framepointer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer)
+-and [sigchanyzer](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer)
+-analyzers have long been part of go vet's suite,
+-but had been overlooked in previous versions of gopls.
+-
+-Henceforth, gopls will always include any analyzers run by vet.
+-
+-### Hover shows size/offset info, and struct tags
+-
+-Hovering over the identifier that declares a type or struct field now
+-displays the size information for the type:
+-
+-
+-
+-and the offset information for the field:
+-
+-
+-
+-In addition, it reports the percentage of wasted space due to
+-suboptimal ordering of struct fields, if this figure is 20% or higher:
+-
+-
+-
+-In the struct above, alignment rules require each of the two boolean
+-fields (1 byte) to occupy a complete word (8 bytes), leading to (7 + 7) / (3 \* 8) = 58% waste.
+-Placing the two booleans together would save a word.
+-
+-This information may be helpful when making space optimizations to
+-your data structures, or when reading assembly code.
+-
+-Also, hovering over a reference to a field with a struct tag now also
+-display the tag:
+-
+-
+-
+-### Hover and "Go to Definition" work on symbols in doc comments
+-
+-Go 1.19 added support for [doc links](https://go.dev/doc/comment#links),
+-allowing the doc comment for one symbol to reference another.
+-
+-Gopls' Hover and Definition operations now treat these links just
+-like identifiers, so hovering over one will display information about
+-the symbol:
+-
+-
+-
+-Similarly, "Go to definition" will navigate to its declaration.
+-Thanks to @rogeryk for contributing this feature.
+-
+-## Bugs fixed
+-
+-## Thank you to our contributors!
+-
+-@guodongli-google for the `unusedwrite` analyzer.
+-TODO: they're a xoogler; is there a more current GH account?
+-
+-@rogeryk
+diff -urN a/gopls/doc/release/v0.17.0.md b/gopls/doc/release/v0.17.0.md
+--- a/gopls/doc/release/v0.17.0.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/release/v0.17.0.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,200 +0,0 @@
+----
+-title: "Gopls release v0.17.0"
+----
+-
+-
+-
+-```
+-go install golang.org/x/tools/gopls@v0.17.0-pre.4
+-```
+-
+-## New support policies
+-
+-With this release, we are narrowing our official support window to align with
+-the [Go support policy](https://go.dev/doc/devel/release#policy). This will
+-reduce the considerable costs to us of testing against older Go versions,
+-allowing us to spend more time fixing bugs and adding features that benefit the
+-majority of gopls users who run recent versions of Go.
+-
+-This narrowing is occurring in two dimensions: **build compatibility** refers to
+-the versions of the Go toolchain that can be used to build gopls, and **go
+-command compatibility** refers to the versions of the `go` command that can be
+-used by gopls to list information about packages and modules in your workspace.
+-
+-### Build compatibility: the most recent major Go version
+-
+-As described in the [v0.16.0 release
+-notes](https://github.com/golang/tools/releases/tag/gopls%2Fv0.16.0), building the
+-latest version of gopls will now require the latest major version of the Go
+-toolchain. Therefore this release (gopls@v0.17.0) must be built with Go 1.23.0
+-or later. Thanks to [automatic toolchain
+-upgrades](https://go.dev/blog/toolchain), if your system Go version is at least
+-Go 1.21.0 and you have `GOTOOLCHAIN=auto` set (the default), the `go` command
+-will automatically download the new Go toolchain as needed, similar to
+-upgrading a module dependency.
+-
+-### Go command compatibility: the 2 most recent major Go versions
+-
+-The gopls@v0.17.x releases will be the final versions of gopls to nominally
+-support integrating with more than the 2 most recent Go releases. In the past,
+-we implied "best effort" support for up to 4 versions, though in practice we
+-did not have resources to fix bugs that were present only with older Go
+-versions. With gopls@v0.17.0, we narrowed this best effort support to 3
+-versions, primarily because users need at least Go 1.21 to benefit from
+-automatic toolchain upgrades (see above).
+-
+-Starting with gopls@v0.18.0, we will officially support integrating with only
+-the 2 most recent major versions of the `go` command. This is consistent with
+-the Go support policy. See golang/go#69321 (or [this
+-comment](https://github.com/golang/go/issues/69321#issuecomment-2344996677)
+-specifically) for details.
+-
+-We won't prevent gopls from being used with older Go versions (just as we
+-don't disallow integration with arbitrary
+-[`go/packages`](https://pkg.go.dev/golang.org/x/tools/go/packages) drivers),
+-but we won't run integration tests against older Go versions, and won't fix
+-bugs that are only present when used with old Go versions.
+-
+-## Configuration Changes
+-
+-- The `fieldalignment` analyzer, previously disabled by default, has
+- been removed: it is redundant with the hover size/offset information
+- displayed by v0.16.0 and its diagnostics were confusing.
+-- The `undeclaredname` analyzer has been replaced with an ordinary code action.
+-- The kind (identifiers) of all of gopls' code actions have changed
+- to use more specific hierarchical names. For example, "Inline call"
+- has changed from `refactor.inline` to `refactor.inline.call`.
+- This allows clients to request particular code actions more precisely.
+- The user manual now includes the identifier in the documentation for each code action.
+-- The experimental `allowImplicitNetworkAccess` setting is removed, following
+- its deprecation in gopls@v0.16.0. See golang/go#66861 for details.
+-
+-## New features
+-
+-### Refactoring
+-
+-This release contains a number of new features related to refactoring.
+-Additionally, it fixes [many
+-bugs](https://github.com/golang/go/issues?q=is%3Aissue+milestone%3Agopls%2Fv0.17.0+label%3ARefactoring+is%3Aclosed)
+-in existing refactoring operations, primarily related to **extract**, and **inline**.
+-
+-These improvements move us toward a longer term goal of offering a more robust
+-and complete set of refactoring tools. We still have [much to
+-do](https://github.com/golang/go/issues?q=is%3Aissue+label%3Agopls+label%3ARefactoring+is%3Aopen+),
+-and this effort will continue into 2025.
+-
+-#### Move parameter refactorings
+-
+-Gopls now offers code actions to move function and method parameters left or
+-right in the function signature, updating all callers.
+-
+-Unfortunately, there is no native LSP operation that provides a good user
+-interface for arbitrary "change signature" refactoring. We plan to build such
+-an interface within VS Code. In the short term, we have made it possible to
+-express more complicated parameter transformations by invoking 'rename' on the
+-'func' keyword. This user interface is a temporary stop-gap until a better
+-mechanism is available for LSP commands that enable client-side dialogs.
+-
+-#### Extract declarations to new file
+-
+-Gopls now offers another code action,
+-"Extract declarations to new file" (`refactor.extract.toNewFile`),
+-which moves selected code sections to a newly created file within the
+-same package. The created filename is chosen as the first {function, type,
+-const, var} name encountered. In addition, import declarations are added or
+-removed as needed.
+-
+-The user can invoke this code action by selecting a function name, the keywords
+-`func`, `const`, `var`, `type`, or by placing the caret on them without selecting,
+-or by selecting a whole declaration or multiple declarations.
+-
+-In order to avoid ambiguity and surprise about what to extract, some kinds
+-of partial selection of a declaration cannot invoke this code action.
+-
+-#### Extract constant
+-
+-When the selection is a constant expression, gopls now offers "Extract
+-constant" instead of "Extract variable", and generates a `const`
+-declaration instead of a local variable.
+-
+-Also, extraction of a constant or variable now works at top-level,
+-outside of any function.
+-
+-#### Generate missing method from function call
+-
+-When you attempt to call a method on a type that lacks that method, the
+-compiler will report an error like “type T has no field or method f”. Gopls now
+-offers a new code action, “Declare missing method of T.f”, where T is the
+-concrete type and f is the undefined method. The stub method's signature is
+-inferred from the context of the call.
+-
+-#### Generate a test for a function or method
+-
+-If the selected chunk of code is part of a function or method declaration F,
+-gopls will offer the "Add test for F" code action, which adds a new test for the
+-selected function in the corresponding `_test.go` file. The generated test takes
+-into account its signature, including input parameters and results.
+-
+-Since this feature is implemented by the server (gopls), it is compatible with
+-all LSP-compliant editors. VS Code users may continue to use the client-side
+-`Go: Generate Unit Tests For file/function/package` command, which runs the
+-[gotests](https://github.com/cweill/gotests) tool.
+-
+-### Initial support for pull diagnostics
+-
+-When initialized with the option `"pullDiagnostics": true`, gopls will advertise support for the
+-`textDocument.diagnostic`
+-[client capability](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics),
+-which allows editors to request diagnostics directly from gopls using a
+-`textDocument/diagnostic` request, rather than wait for a
+-`textDocument/publishDiagnostics` notification. This feature is off by default
+-until the feature set of pull diagnostics is comparable to push diagnostics.
+-
+-### Hover improvements
+-
+-The `textDocument/hover` response has slightly tweaked markdown rendering, and
+-includes the following additional information:
+-
+-- Hovering over a standard library symbol now displays information about the
+- first Go release containing the symbol. For example, hovering over
+- `errors.As` shows "Added in go1.13".
+-- Hovering over the package name in a package declaration includes additional
+- package metadata.
+-
+-### Semantic token modifiers of top-level constructor of types
+-
+-The semantic tokens response now includes additional modifiers for the top-level
+-constructor of the type of each symbol:
+-`interface`, `struct`, `signature`, `pointer`, `array`, `map`, `slice`, `chan`, `string`, `number`, `bool`, and `invalid`.
+-Editors may use this for syntax coloring.
+-
+-### SignatureHelp for ident and values.
+-
+-Now, function signature help can be used on any identifier with a function
+-signature, not just within the parentheses of a function being called.
+-
+-### Jump to assembly definition
+-
+-A Definition query on a reference to a function jumps to the
+-function's Go `func` declaration. If the function is implemented in C
+-or assembly, the function has no body. Executing a second Definition
+-query (while already at the Go declaration) will navigate you to the
+-assembly implementation.
+-
+-### `yield` analyzer
+-
+-The new `yield` analyzer detects mistakes using the `yield` function
+-in a Go 1.23 iterator, such as failure to check its boolean result and
+-break out of a loop.
+-
+-### `waitgroup` analyzer
+-
+-The new `waitgroup` analyzer detects calls to the `Add` method of
+-`sync.WaitGroup` that are (mistakenly) made within the new goroutine,
+-causing `Add` to race with `Wait`.
+-(This check is equivalent to
+-[staticcheck's SA2000](https://staticcheck.dev/docs/checks#SA2000),
+-but is enabled by default.)
+diff -urN a/gopls/doc/release/v0.18.0.md b/gopls/doc/release/v0.18.0.md
+--- a/gopls/doc/release/v0.18.0.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/release/v0.18.0.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,184 +0,0 @@
+----
+-title: "Gopls release v0.18.0"
+----
+-
+-## Configuration Changes
+-
+-
+-
+-- The experimental `Structured` value for the `hoverKind` option is no longer
+- supported.
+-
+-- The `gc_details` code lens has been deleted. (It was previously disabled by
+- default.) This functionality is now available through the
+- `toggleCompilerOptDetails` code action (documented below), as code
+- actions are better supported than code lenses across a range of clients.
+-
+- VS Code's special "Go: Toggle GC details" command continues to work.
+-
+-- The experimental `semanticTokenTypes` and `semanticTokenModifiers` options
+- allow selectively disabling certain types of tokens or token modifiers in
+- `textDocument/semanticTokens` responses.
+-
+- These options supersede the `noSemanticString` and `noSemanticTokenNumber`
+- options, which are now deprecated. Users can instead set
+- `"semanticTokenTypes": {"string": false, "number": false}` to achieve the
+- same result. For now, gopls still honors `noSemanticTokenString` and
+- `noSemanticToken`, but will stop supporting them in a future release.
+-
+-- The new `workspaceFiles` option allows configuring glob patterns matching
+- files that define the logical build of the workspace. This option is only
+- needed in environments that use a custom golang.org/x/tools/go/packages
+- driver.
+-
+-## New features
+-
+-### "{Show,Hide} compiler optimization details" code action
+-
+-This code action, accessible through the "Source Action" menu in VS
+-Code, toggles a per-directory flag that causes Go compiler optimization
+-details to be reported as diagnostics. For example, it indicates which
+-variables escape to the heap, and which array accesses require bounds
+-checks.
+-
+-TODO: add links to the complete manual for each item.
+-
+-### New `modernize` analyzer
+-
+-Gopls now reports when code could be simplified or clarified by
+-using more modern features of Go, and provides a quick fix to apply
+-the change.
+-
+-For example, a conditional assignment using an if/else statement may
+-be replaced by a call to the `min` or `max` built-in functions added
+-in Go 1.18.
+-
+-Use this command to apply modernization fixes en masse:
+-```
+-$ go run golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize@latest -fix ./...
+-```
+-
+-### New `unusedfunc` analyzer
+-
+-Gopls now reports unused functions and methods, giving you near
+-real-time feedback about dead code that may be safely deleted.
+-Because the analysis is local to each package, only unexported
+-functions and methods are candidates.
+-(For a more precise analysis that may report unused exported
+-functions too, use the `golang.org/x/tools/cmd/deadcode` command.)
+-
+-### New `hostport` analyzer
+-
+-With the growing use of IPv6, forming a "host:port" string using
+-`fmt.Sprintf("%s:%d")` is no longer appropriate because host names may
+-contain colons. Gopls now reports places where a string constructed in
+-this fashion (or with `%s` for the port) is passed to `net.Dial` or a
+-related function, and offers a fix to use `net.JoinHostPort`
+-instead.
+-
+-### Other analyzer changes
+-
+-- The `unusedvariable` quickfix is now on by default.
+-- The `unusedparams` analyzer no longer reports finding for generated files.
+-
+-### New `gofix` analyzer
+-
+-Gopls now reports when a function call or a use of a constant should be inlined.
+-These diagnostics and the associated code actions are triggered by "//go:fix inline"
+-directives at the function and constant definitions.
+-(See [the go:fix proposal](https://go.dev/issue/32816).)
+-
+-For example, consider a package `intmath` with a function `Square(int) int`.
+-Later the more general `Pow(int, int) int` is introduced, and `Square` is deprecated
+-in favor of calling `Pow` with a second argument of 2. The author of `intmath`
+-can write this:
+-```
+-//go:fix inline
+-func Square(x int) int { return Pow(x, 2) }
+-```
+-If gopls sees a call to `intmath.Square` in your code, it will suggest inlining
+-it, and will offer a code action to do so.
+-
+-The same feature works for constants.
+-With a constant definition like this:
+-```
+-//go:fix inline
+-const Ptr = Pointer
+-```
+-gopls will suggest replacing `Ptr` in your code with `Pointer`.
+-
+-Use this command to apply such fixes en masse:
+-
+-```
+-$ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./...
+-```
+-
+-### "Implementations" supports generics
+-
+-At long last, the "Go to Implementations" feature now fully supports
+-generic types and functions (#59224).
+-
+-For example, invoking the feature on the interface method `Stack.Push`
+-below will report the concrete method `C[T].Push`, and vice versa.
+-
+-```go
+-package p
+-
+-type Stack[T any] interface {
+- Push(T) error
+- Pop() (T, bool)
+-}
+-
+-type C[T any] struct{}
+-
+-func (C[T]) Push(t T) error { ... }
+-func (C[T]) Pop() (T, bool) { ... }
+-
+-var _ Stack[int] = C[int]{}
+-```
+-
+-### Extract all occurrences of the same expression under selection
+-
+-When you have multiple instances of the same expression in a function,
+-you can use this code action to extract it into a variable.
+-All occurrences of the expression will be replaced with a reference to the new variable.
+-
+-### Improvements to "Definition"
+-
+-The Definition query now supports additional locations:
+-
+-- When invoked on a return statement, it reports the location
+- of the function's result variables.
+-- When invoked on a break, goto, or continue statement, it reports
+- the location of the label, the closing brace of the relevant
+- block statement, or the start of the relevant loop, respectively.
+-
+-### Improvements to "Hover"
+-
+-When invoked on a return statement, hover reports the types of
+-the function's result variables.
+-
+-### UX improvements to format strings
+-
+-#### "DocumentHighlight"
+-
+-When your cursor is inside a printf-like function, gopls now highlights the relationship between
+-formatting verbs and arguments as visual cues to differentiate how operands are used in the format string.
+-
+-```go
+-fmt.Printf("Hello %s, you scored %d", name, score)
+-```
+-
+-If the cursor is either on `%s` or `name`, gopls will highlight `%s` as a write operation,
+-and `name` as a read operation.
+-
+-#### "SemanticHighlight"
+-
+-Similar to the improvements to DocumentHighlight, gopls also reports formatting verbs
+-as "format" modifier for token type "string" to better distinguish them with other parts of the format string.
+-
+-```go
+-fmt.Printf("Hello %s, you scored %d", name, score)
+-```
+-
+-`%s` and `%d` will have token type "string" and modifier "format".
+diff -urN a/gopls/doc/release/v0.19.0.md b/gopls/doc/release/v0.19.0.md
+--- a/gopls/doc/release/v0.19.0.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/release/v0.19.0.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,249 +0,0 @@
+----
+-title: "Gopls release v0.19.0"
+----
+-
+-## Configuration Changes
+-
+-- The `gopls check` subcommand now accepts a `-severity` flag to set a minimum
+- severity for the diagnostics it reports. By default, the minimum severity
+- is "warning", so `gopls check` may report fewer diagnostics than before. Set
+- `-severity=hint` to reproduce the previous behavior.
+-
+-## Navigation features
+-
+-### "Implementations" supports signature types (within same package)
+-
+-
+-
+-The Implementations query reports the correspondence between abstract
+-and concrete types and their methods based on their method sets.
+-Now, it also reports the correspondence between function types,
+-dynamic function calls, and function definitions, based on their signatures.
+-
+-To use it, invoke an Implementations query on the `func` token of the
+-definition of a named function, named method, or function literal.
+-Gopls reports the set of function signature types that abstract this
+-function, and the set of dynamic calls through values of such types.
+-
+-Conversely, an Implementations query on the `func` token of a
+-signature type, or on the `(` paren of a dynamic function call,
+-reports the set of concrete functions that the signature abstracts
+-or that the call dispatches to.
+-
+-Since a type may be both a function type and a named type with methods
+-(for example, `http.HandlerFunc`), it may participate in both kinds of
+-Implements queries (method-sets and function signatures).
+-Queries using method-sets should be invoked on the type or method name,
+-and queries using signatures should be invoked on a `func` or `(` token.
+-
+-Only the local (same-package) algorithm is currently supported.
+-(https://go.dev/issue/56572 tracks the global algorithm.)
+-
+-### "Go to Implementation" reports interface-to-interface relations
+-
+-
+-
+-The "Go to Implementation" operation now reports relationships between
+-interfaces. Gopls now uses the concreteness of the query type to
+-determine whether a query is "downwards" (from an interface to the
+-types that implement it) or "upwards" (from a concrete type to the
+-interfaces to which it may be assigned). So, for example:
+-
+-- `implementation(io.Reader)` subinterfaces such as `io.ReadCloser`,
+- and concrete implementations such as `*os.File`.
+-
+-- `implementation(os.File)` includes only interfaces, such as
+- `io.Reader` and `io.ReadCloser`.
+-
+-To request an "upwards" query starting from an interface, for example
+-to find the superinterfaces of `io.ReadCloser`, use the Type Hierarchy
+-feature described below.
+-(See https://github.com/microsoft/language-server-protocol/issues/2037.)
+-
+-### Support for Type Hierarchy
+-
+-
+-
+-Gopls now implements the three LSP methods related to the Type
+-Hierarchy viewer: `textDocument/prepareTypeHierarchy`,
+-`typeHierarchy/supertypes`, `typeHierarchy/subtypes`.
+-
+-In VS Code, select "Show Type Hierarchy" from the context menu
+-to see a tree widget displaying all the supertypes or subtypes
+-of the selected named type.
+-
+-
+-
+-
+-
+-
+-## Editing features
+-
+-### Completion: auto-complete package clause for new Go files
+-
+-Gopls now automatically adds the appropriate `package` clause to newly created Go files,
+-so that you can immediately get started writing the interesting part.
+-
+-It requires client support for `workspace/didCreateFiles`
+-
+-### New GOMODCACHE index for faster Organize Imports and unimported completions
+-
+-By default, gopls now builds and maintains a persistent index of
+-packages in the module cache (GOMODCACHE). The operations of Organize
+-Imports and completion of symbols from unimported pacakges are an
+-order of magnitude faster.
+-
+-To revert to the old behavior, set the `importsSource` option (whose
+-new default is `"gopls"`) to `"goimports"`. Users who don't want the
+-module cache used at all for imports or completions can change the
+-option to "off".
+-
+-## Analysis features
+-
+-### Most `staticcheck` analyzers are enabled by default
+-
+-Slightly more than half of the analyzers in the
+-[Staticcheck](https://staticcheck.dev/docs/checks) suite are now
+-enabled by default. This subset has been chosen for precision and
+-efficiency.
+-
+-Previously, Staticcheck analyzers (all of them) would be run only if
+-the experimental `staticcheck` boolean option was set to `true`. This
+-value continues to enable the complete set, and a value of `false`
+-continues to disable the complete set. Leaving the option unspecified
+-enables the preferred subset of analyzers.
+-
+-Staticcheck analyzers, like all other analyzers, can be explicitly
+-enabled or disabled using the `analyzers` configuration setting; this
+-setting now takes precedence over the `staticcheck` setting, so,
+-regardless of what value of `staticcheck` you use (true/false/unset),
+-you can make adjustments to your preferred set of analyzers.
+-
+-### `recursiveiter`: "inefficient recursive iterator"
+-
+-A common pitfall when writing a function that returns an iterator
+-(`iter.Seq`) for a recursive data type is to recursively call the
+-function from its own implementation, leading to a stack of nested
+-coroutines, which is inefficient.
+-
+-The new `recursiveiter` analyzer detects such mistakes; see
+-[its documentation](https://golang.org/x/tools/gopls/internal/analysis/recursiveiter)
+-for details, including tips on how to define simple and efficient
+-recursive iterators.
+-
+-### `maprange`: "inefficient range over maps.Keys/Values"
+-
+-The new `maprange` analyzer detects redundant calls to `maps.Keys` or
+-`maps.Values` as the operand of a range loop; maps can of course be
+-ranged over directly. See
+-[its documentation](https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/maprange)
+-for details).
+-
+-## Code transformation features
+-
+-### Rename method receivers
+-
+-
+-
+-The Rename operation, when applied to the declaration of a method
+-receiver, now also attempts to rename the receivers of all other
+-methods associated with the same named type. Each other receiver that
+-cannot be fully renamed is quietly skipped.
+-
+-Renaming a _use_ of a method receiver continues to affect only that
+-variable.
+-
+-```go
+-type Counter struct { x int }
+-
+- Rename here to affect only this method
+- ↓
+-func (c *Counter) Inc() { c.x++ }
+-func (c *Counter) Dec() { c.x++ }
+- ↑
+- Rename here to affect all methods
+-```
+-
+-### "Eliminate dot import" code action
+-
+-
+-
+-This code action, available on a dotted import, will offer to replace
+-the import with a regular one and qualify each use of the package
+-with its name.
+-
+-### Add/remove tags from struct fields
+-
+-Gopls now provides two new code actions, available on an entire struct
+-or some of its fields, that allow you to add and remove struct tags.
+-It adds only 'json' tags with a snakecase naming format, or clears all
+-tags within the selection.
+-
+-Add tags example:
+-```go
+-type Info struct {
+- LinkTarget string -> LinkTarget string `json:"link_target"`
+- ...
+-}
+-```
+-
+-### Inline local variable
+-
+-
+-
+-The new `refactor.inline.variable` code action replaces a reference to
+-a local variable by that variable's initializer expression. For
+-example, when applied to `s` in `println(s)`:
+-
+-```go
+-func f(x int) {
+- s := fmt.Sprintf("+%d", x)
+- println(s)
+-}
+-```
+-it transforms the code to:
+-```go
+-func f(x int) {
+- s := fmt.Sprintf("+%d", x)
+- println(fmt.Sprintf("+%d", x))
+-}
+-```
+-
+-Only a single reference is replaced; issue https://go.dev/issue/70085
+-tracks the feature to "inline all" uses of the variable and eliminate
+-it.
+-
+-
+-## Thank you to our contributors!
+-
+-[@acehinnnqru](https://github.com/acehinnnqru)
+-[@adonovan](https://github.com/adonovan)
+-[@albfan](https://github.com/albfan)
+-[@aarzilli](https://github.com/aarzilli)
+-[@ashurbekovz](https://github.com/ashurbekovz)
+-[@cuonglm](https://github.com/cuonglm)
+-[@dmitshur](https://github.com/dmitshur)
+-[@neild](https://github.com/neild)
+-[@egonelbre](https://github.com/egonelbre)
+-[@shashank](https://github.com/shashank)
+-[priyadarshi](https://github.compriyadarshi)
+-[@firelizzard18](https://github.com/firelizzard18)
+-[@gopherbot](https://github.com/gopherbot)
+-[@h9jiang](https://github.com/h9jiang)
+-[@cuishuang](https://github.com/cuishuang)
+-[@jakebailey](https://github.com/jakebailey)
+-[@jba](https://github.com/jba)
+-[@madelinekalil](https://github.com/madelinekalil)
+-[@karamaru](https://github.com/karamaru)
+-[alpha](https://github.comalpha)
+-[@danztran](https://github.com/danztran)
+-[@nsrip](https://github.com/nsrip)
+-[dd](https://github.comdd)
+-[@pjweinb](https://github.com/pjweinb)
+-[@findleyr](https://github.com/findleyr)
+-[@samthanawalla](https://github.com/samthanawalla)
+-[@seankhliao](https://github.com/seankhliao)
+-[@tklauser](https://github.com/tklauser)
+-[@vikblom](https://github.com/vikblom)
+-[@kwjw](https://github.com/kwjw)
+-[@xieyuschen](https://github.com/xieyuschen)
+diff -urN a/gopls/doc/release/v0.20.0.md b/gopls/doc/release/v0.20.0.md
+--- a/gopls/doc/release/v0.20.0.md 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/doc/release/v0.20.0.md 1969-12-31 18:00:00.000000000 -0600
+@@ -1,99 +0,0 @@
+----
+-title: "Gopls release v0.20.0"
+----
+-
+-This release contains a new experimental Model Context Protocol (MCP)
+-server for gopls, which may be used to integrate a subset of gopls'
+-features in AI-assisted environments.
+-
+-Gopls' documentation is now available on the Go project's website at
+-https://go.dev/gopls. (This link reflects the latest gopls release;
+-use https://tip.golang.org/gopls to see docs for the latest commit.)
+-Unlike Markdown files in GitHub, these pages are crawled by Google's
+-web search index.
+-
+-## Configuration changes
+-
+-This release enables by default the new persistent index of packages
+-in the module cache. This was first attempted in [v0.19](./v0.19.0.md) but reverted
+-due to problems that have since been fixed.
+-
+-## Web-based features
+-
+-### "Split package" tool
+-
+-The `source.splitPackage` code action opens a web-based tool that
+-helps you split a package into two or more components whose
+-dependencies are acyclic.
+-
+-To use it, name a set of components, assign each declaration to a
+-component, then visualize the dependencies among the components
+-(including whether they form a cycle).
+-Refresh the page each time you edit your code to see the latest
+-information.
+-
+-
+-
+-{{end}}
+-`))
+diff -urN a/gopls/internal/debug/template_test.go b/gopls/internal/debug/template_test.go
+--- a/gopls/internal/debug/template_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/debug/template_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,148 +0,0 @@
+-// Copyright 2020 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package debug_test
+-
+-// Provide 'static type checking' of the templates. This guards against changes in various
+-// gopls datastructures causing template execution to fail. The checking is done by
+-// the github.com/jba/templatecheck package. Before that is run, the test checks that
+-// its list of templates and their arguments corresponds to the arguments in
+-// calls to render(). The test assumes that all uses of templates are done through render().
+-
+-import (
+- "go/ast"
+- "html/template"
+- "os"
+- "runtime"
+- "strings"
+- "testing"
+-
+- "github.com/jba/templatecheck"
+- "golang.org/x/tools/go/packages"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/debug"
+- "golang.org/x/tools/gopls/internal/util/moremaps"
+- "golang.org/x/tools/internal/testenv"
+-)
+-
+-var templates = map[string]struct {
+- tmpl *template.Template
+- data any // a value of the needed type
+-}{
+- "MainTmpl": {debug.MainTmpl, &debug.Instance{}},
+- "DebugTmpl": {debug.DebugTmpl, nil},
+- "RPCTmpl": {debug.RPCTmpl, &debug.Rpcs{}},
+- "TraceTmpl": {debug.TraceTmpl, debug.TraceResults{}},
+- "CacheTmpl": {debug.CacheTmpl, &cache.Cache{}},
+- "SessionTmpl": {debug.SessionTmpl, &cache.Session{}},
+- "ClientTmpl": {debug.ClientTmpl, &debug.Client{}},
+- "ServerTmpl": {debug.ServerTmpl, &debug.Server{}},
+- "FileTmpl": {debug.FileTmpl, *new(debug.FileWithKind)},
+- "MetadataTmpl": {debug.MetadataTmpl, &metadata.Graph{}},
+- "InfoTmpl": {debug.InfoTmpl, "something"},
+- "MemoryTmpl": {debug.MemoryTmpl, runtime.MemStats{}},
+- "AnalysisTmpl": {debug.AnalysisTmpl, new(debug.State).Analysis()},
+-}
+-
+-func TestTemplates(t *testing.T) {
+- testenv.NeedsGoPackages(t)
+- testenv.NeedsLocalXTools(t)
+-
+- cfg := &packages.Config{
+- Mode: packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo,
+- }
+- cfg.Env = os.Environ()
+- cfg.Env = append(cfg.Env,
+- "GOPACKAGESDRIVER=off",
+- "GOWORK=off", // necessary for -mod=mod below
+- "GOFLAGS=-mod=mod",
+- )
+-
+- pkgs, err := packages.Load(cfg, "golang.org/x/tools/gopls/internal/debug")
+- if err != nil {
+- t.Fatal(err)
+- }
+- if len(pkgs) != 1 {
+- t.Fatalf("expected a single package, but got %d", len(pkgs))
+- }
+- p := pkgs[0]
+- if len(p.Errors) != 0 {
+- t.Fatalf("compiler error, e.g. %v", p.Errors[0])
+- }
+- // find the calls to render in serve.go
+- tree := treeOf(p, "serve.go")
+- if tree == nil {
+- t.Fatalf("found no syntax tree for %s", "serve.go")
+- }
+- renders := callsOf(tree, "render")
+- if len(renders) == 0 {
+- t.Fatalf("found no calls to render")
+- }
+- var found = make(map[string]bool)
+- for _, r := range renders {
+- if len(r.Args) != 2 {
+- // template, func
+- t.Fatalf("got %d args, expected 2", len(r.Args))
+- }
+- t0, ok := p.TypesInfo.Types[r.Args[0]]
+- if !ok || !t0.IsValue() || t0.Type.String() != "*html/template.Template" {
+- t.Fatalf("no type info for template")
+- }
+- if id, ok := r.Args[0].(*ast.Ident); !ok {
+- t.Errorf("expected *ast.Ident, got %T", r.Args[0])
+- } else {
+- found[id.Name] = true
+- }
+- }
+- // make sure found and templates have the same templates
+- for k := range found {
+- if _, ok := templates[k]; !ok {
+- t.Errorf("code has template %s, but test does not", k)
+- }
+- }
+- for k := range templates {
+- if _, ok := found[k]; !ok {
+- t.Errorf("test has template %s, code does not", k)
+- }
+- }
+- // now check all the known templates, in alphabetic order, for determinacy
+- for k, v := range moremaps.Sorted(templates) {
+- // the FuncMap is an annoyance; should not be necessary
+- if err := templatecheck.CheckHTML(v.tmpl, v.data); err != nil {
+- t.Errorf("%s: %v", k, err)
+- continue
+- }
+- t.Logf("%s ok", k)
+- }
+-}
+-
+-func callsOf(tree *ast.File, name string) []*ast.CallExpr {
+- var ans []*ast.CallExpr
+- f := func(n ast.Node) bool {
+- x, ok := n.(*ast.CallExpr)
+- if !ok {
+- return true
+- }
+- if y, ok := x.Fun.(*ast.Ident); ok {
+- if y.Name == name {
+- ans = append(ans, x)
+- }
+- }
+- return true
+- }
+- ast.Inspect(tree, f)
+- return ans
+-}
+-
+-func treeOf(p *packages.Package, fname string) *ast.File {
+- for _, tree := range p.Syntax {
+- loc := tree.Package
+- pos := p.Fset.PositionFor(loc, false)
+- if strings.HasSuffix(pos.Filename, fname) {
+- return tree
+- }
+- }
+- return nil
+-}
+diff -urN a/gopls/internal/debug/trace.go b/gopls/internal/debug/trace.go
+--- a/gopls/internal/debug/trace.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/debug/trace.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,321 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package debug
+-
+-import (
+- "bytes"
+- "context"
+- "fmt"
+- "html/template"
+- "net/http"
+- "runtime/trace"
+- "slices"
+- "sort"
+- "strings"
+- "sync"
+- "time"
+-
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/event/core"
+- "golang.org/x/tools/internal/event/export"
+- "golang.org/x/tools/internal/event/label"
+-)
+-
+-// TraceTmpl extends BaseTemplate and renders a TraceResults, e.g. from getData().
+-var TraceTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
+-{{define "title"}}Trace Information{{end}}
+-{{define "body"}}
+- {{range .Traces}}{{.Name}} last: {{.Last.Duration}}, longest: {{.Longest.Duration}} {{end}}
+- {{if .Selected}}
+-
{{.Selected.Name}}
+- {{if .Selected.Last}}
Last
{{template "completeSpan" .Selected.Last}}
{{end}}
+- {{if .Selected.Longest}}
Longest
{{template "completeSpan" .Selected.Longest}}
{{end}}
+- {{end}}
+-
+-
Recent spans (oldest first)
+-
+- A finite number of recent span start/end times are shown below.
+- The nesting represents the children of a parent span (and the log events within a span).
+- A span may appear twice: chronologically at toplevel, and nested within its parent.
+-
{{end}}
+-{{end}}
+-`))
+-
+-type traces struct {
+- mu sync.Mutex
+- sets map[string]*traceSet
+- unfinished map[export.SpanContext]*traceSpan
+- recent []spanStartEnd
+- recentEvictions int
+-}
+-
+-// A spanStartEnd records the start or end of a span.
+-// If Start, the span may be unfinished, so some fields (e.g. Finish)
+-// may be unset and others (e.g. Events) may be being actively populated.
+-type spanStartEnd struct {
+- Start bool
+- Span *traceSpan
+-}
+-
+-func (ev spanStartEnd) Time() time.Time {
+- if ev.Start {
+- return ev.Span.Start
+- } else {
+- return ev.Span.Finish
+- }
+-}
+-
+-// A TraceResults is the subject for the /trace HTML template.
+-type TraceResults struct { // exported for testing
+- Traces []*traceSet
+- Selected *traceSet
+- Recent []spanStartEnd
+-}
+-
+-// A traceSet holds two representative spans of a given span name.
+-type traceSet struct {
+- Name string
+- Last *traceSpan
+- Longest *traceSpan
+-}
+-
+-// A traceSpan holds information about a single span.
+-type traceSpan struct {
+- TraceID export.TraceID
+- SpanID export.SpanID
+- ParentID export.SpanID
+- Name string
+- Start time.Time
+- Finish time.Time // set at end
+- Duration time.Duration // set at end
+- Tags string
+- Events []traceEvent // set at end
+- ChildStartEnd []spanStartEnd // populated while active
+-
+- parent *traceSpan
+-}
+-
+-const timeFormat = "15:04:05.000"
+-
+-// Header renders the time, name, tags, and (if !start),
+-// duration of a span start or end event.
+-func (span *traceSpan) Header(start bool) string {
+- if start {
+- return fmt.Sprintf("%s start %s %s",
+- span.Start.Format(timeFormat), span.Name, span.Tags)
+- } else {
+- return fmt.Sprintf("%s end %s (+%s) %s",
+- span.Finish.Format(timeFormat), span.Name, span.Duration, span.Tags)
+- }
+-}
+-
+-type traceEvent struct {
+- Time time.Time
+- Offset time.Duration // relative to start of span
+- Tags string
+-}
+-
+-func (ev traceEvent) Header() string {
+- return fmt.Sprintf("%s event (+%s) %s", ev.Time.Format(timeFormat), ev.Offset, ev.Tags)
+-}
+-
+-func StdTrace(exporter event.Exporter) event.Exporter {
+- return func(ctx context.Context, ev core.Event, lm label.Map) context.Context {
+- span := export.GetSpan(ctx)
+- if span == nil {
+- return exporter(ctx, ev, lm)
+- }
+- switch {
+- case event.IsStart(ev):
+- if span.ParentID.IsValid() {
+- region := trace.StartRegion(ctx, span.Name)
+- ctx = context.WithValue(ctx, traceKey, region)
+- } else {
+- var task *trace.Task
+- ctx, task = trace.NewTask(ctx, span.Name)
+- ctx = context.WithValue(ctx, traceKey, task)
+- }
+- // Log the start event as it may contain useful labels.
+- msg := formatEvent(ev, lm)
+- trace.Log(ctx, "start", msg)
+- case event.IsLog(ev):
+- category := ""
+- if event.IsError(ev) {
+- category = "error"
+- }
+- msg := formatEvent(ev, lm)
+- trace.Log(ctx, category, msg)
+- case event.IsEnd(ev):
+- if v := ctx.Value(traceKey); v != nil {
+- v.(interface{ End() }).End()
+- }
+- }
+- return exporter(ctx, ev, lm)
+- }
+-}
+-
+-func formatEvent(ev core.Event, lm label.Map) string {
+- buf := &bytes.Buffer{}
+- p := export.Printer{}
+- p.WriteEvent(buf, ev, lm)
+- return buf.String()
+-}
+-
+-func (t *traces) ProcessEvent(ctx context.Context, ev core.Event, lm label.Map) context.Context {
+- span := export.GetSpan(ctx)
+- if span == nil {
+- return ctx
+- }
+-
+- switch {
+- case event.IsStart(ev):
+- // Just starting: add it to the unfinished map.
+- // Allocate before the critical section.
+- td := &traceSpan{
+- TraceID: span.ID.TraceID,
+- SpanID: span.ID.SpanID,
+- ParentID: span.ParentID,
+- Name: span.Name,
+- Start: span.Start().At(),
+- Tags: renderLabels(span.Start()),
+- }
+-
+- t.mu.Lock()
+- defer t.mu.Unlock()
+-
+- t.addRecentLocked(td, true) // add start event
+-
+- if t.sets == nil {
+- t.sets = make(map[string]*traceSet)
+- t.unfinished = make(map[export.SpanContext]*traceSpan)
+- }
+- t.unfinished[span.ID] = td
+-
+- // Wire up parents if we have them.
+- if span.ParentID.IsValid() {
+- parentID := export.SpanContext{TraceID: span.ID.TraceID, SpanID: span.ParentID}
+- if parent, ok := t.unfinished[parentID]; ok {
+- td.parent = parent
+- parent.ChildStartEnd = append(parent.ChildStartEnd, spanStartEnd{true, td})
+- }
+- }
+-
+- case event.IsEnd(ev):
+- // Finishing: must be already in the map.
+- // Allocate events before the critical section.
+- events := span.Events()
+- tdEvents := make([]traceEvent, len(events))
+- for i, event := range events {
+- tdEvents[i] = traceEvent{
+- Time: event.At(),
+- Tags: renderLabels(event),
+- }
+- }
+-
+- t.mu.Lock()
+- defer t.mu.Unlock()
+- td, found := t.unfinished[span.ID]
+- if !found {
+- return ctx // if this happens we are in a bad place
+- }
+- delete(t.unfinished, span.ID)
+- td.Finish = span.Finish().At()
+- td.Duration = span.Finish().At().Sub(span.Start().At())
+- td.Events = tdEvents
+- t.addRecentLocked(td, false) // add end event
+-
+- set, ok := t.sets[span.Name]
+- if !ok {
+- set = &traceSet{Name: span.Name}
+- t.sets[span.Name] = set
+- }
+- set.Last = td
+- if set.Longest == nil || set.Last.Duration > set.Longest.Duration {
+- set.Longest = set.Last
+- }
+- if td.parent != nil {
+- td.parent.ChildStartEnd = append(td.parent.ChildStartEnd, spanStartEnd{false, td})
+- } else {
+- fillOffsets(td, td.Start)
+- }
+- }
+- return ctx
+-}
+-
+-// addRecentLocked appends a start or end event to the "recent" log,
+-// evicting an old entry if necessary.
+-func (t *traces) addRecentLocked(span *traceSpan, start bool) {
+- t.recent = append(t.recent, spanStartEnd{Start: start, Span: span})
+-
+- const maxRecent = 100 // number of log entries before eviction
+- for len(t.recent) > maxRecent {
+- t.recent[0] = spanStartEnd{} // aid GC
+- t.recent = t.recent[1:]
+- t.recentEvictions++
+-
+- // Using a slice as a FIFO queue leads to unbounded growth
+- // as Go's GC cannot collect the ever-growing unused prefix.
+- // So, compact it periodically.
+- if t.recentEvictions%maxRecent == 0 {
+- t.recent = slices.Clone(t.recent)
+- }
+- }
+-}
+-
+-// getData returns the TraceResults rendered by TraceTmpl for the /trace[/name] endpoint.
+-func (t *traces) getData(req *http.Request) any {
+- // TODO(adonovan): the HTTP request doesn't acquire the mutex
+- // for t or for each span! Audit and fix.
+-
+- // Sort last/longest sets by name.
+- traces := make([]*traceSet, 0, len(t.sets))
+- for _, set := range t.sets {
+- traces = append(traces, set)
+- }
+- sort.Slice(traces, func(i, j int) bool {
+- return traces[i].Name < traces[j].Name
+- })
+-
+- return TraceResults{
+- Traces: traces,
+- Selected: t.sets[strings.TrimPrefix(req.URL.Path, "/trace/")], // may be nil
+- Recent: t.recent,
+- }
+-}
+-
+-func fillOffsets(td *traceSpan, start time.Time) {
+- for i := range td.Events {
+- td.Events[i].Offset = td.Events[i].Time.Sub(start)
+- }
+- for _, child := range td.ChildStartEnd {
+- if !child.Start {
+- fillOffsets(child.Span, start)
+- }
+- }
+-}
+-
+-func renderLabels(labels label.List) string {
+- buf := &bytes.Buffer{}
+- for index := 0; labels.Valid(index); index++ {
+- // The 'start' label duplicates the span name, so discard it.
+- if l := labels.Label(index); l.Valid() && l.Key().Name() != "start" {
+- fmt.Fprintf(buf, "%v ", l)
+- }
+- }
+- return buf.String()
+-}
+diff -urN a/gopls/internal/doc/api.go b/gopls/internal/doc/api.go
+--- a/gopls/internal/doc/api.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/doc/api.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,80 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-//go:generate go run ./generate
+-
+-// The doc package provides JSON metadata that documents gopls' public
+-// interfaces.
+-package doc
+-
+-import _ "embed"
+-
+-// JSON is a JSON encoding of value of type API.
+-// The 'gopls api-json' command prints it.
+-//
+-//go:embed api.json
+-var JSON string
+-
+-// API is a JSON-encodable representation of gopls' public interfaces.
+-//
+-// TODO(adonovan): document these data types.
+-type API struct {
+- Options map[string][]*Option
+- Lenses []*Lens
+- Analyzers []*Analyzer
+- Hints []*Hint
+-}
+-
+-type Option struct {
+- Name string
+- Type string // T = bool | string | int | enum | any | []T | map[T]T | time.Duration
+- Doc string
+- EnumKeys EnumKeys
+- EnumValues []EnumValue
+- Default string
+- Status string
+- Hierarchy string
+- DeprecationMessage string
+-}
+-
+-type EnumKeys struct {
+- ValueType string
+- Keys []EnumKey
+-}
+-
+-type EnumKey struct {
+- Name string // in JSON syntax (quoted)
+- Doc string
+- Default string
+- Status string // = "" | "advanced" | "experimental" | "deprecated"
+-}
+-
+-type EnumValue struct {
+- Value string // in JSON syntax (quoted)
+- Doc string // doc comment; always starts with `Value`
+- Status string // = "" | "advanced" | "experimental" | "deprecated"
+-}
+-
+-type Lens struct {
+- FileType string // e.g. "Go", "go.mod"
+- Lens string
+- Title string
+- Doc string
+- Default bool
+- Status string // = "" | "advanced" | "experimental" | "deprecated"
+-}
+-
+-type Analyzer struct {
+- Name string
+- Doc string // from analysis.Analyzer.Doc ("title: summary\ndescription"; go/doc/comment, not Markdown)
+- URL string
+- Default bool
+-}
+-
+-type Hint struct {
+- Name string
+- Doc string
+- Default bool
+- Status string // = "" | "advanced" | "experimental" | "deprecated"
+-}
+diff -urN a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
+--- a/gopls/internal/doc/api.json 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/doc/api.json 1969-12-31 18:00:00.000000000 -0600
+@@ -1,3748 +0,0 @@
+-{
+- "Options": {
+- "User": [
+- {
+- "Name": "buildFlags",
+- "Type": "[]string",
+- "Doc": "buildFlags is the set of flags passed on to the build system when invoked.\nIt is applied to queries like `go list`, which is used when discovering files.\nThe most common use is to set `-tags`.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "[]",
+- "Status": "",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "env",
+- "Type": "map[string]string",
+- "Doc": "env adds environment variables to external commands run by `gopls`, most notably `go list`.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "{}",
+- "Status": "",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "directoryFilters",
+- "Type": "[]string",
+- "Doc": "directoryFilters can be used to exclude unwanted directories from the\nworkspace. By default, all directories are included. Filters are an\noperator, `+` to include and `-` to exclude, followed by a path prefix\nrelative to the workspace folder. They are evaluated in order, and\nthe last filter that applies to a path controls whether it is included.\nThe path prefix can be empty, so an initial `-` excludes everything.\n\nDirectoryFilters also supports the `**` operator to match 0 or more directories.\n\nExamples:\n\nExclude node_modules at current depth: `-node_modules`\n\nExclude node_modules at any depth: `-**/node_modules`\n\nInclude only project_a: `-` (exclude everything), `+project_a`\n\nInclude only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "[\"-**/node_modules\"]",
+- "Status": "",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "templateExtensions",
+- "Type": "[]string",
+- "Doc": "templateExtensions gives the extensions of file names that are treated\nas template files. (The extension\nis the part of the file name after the final dot.)\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "[]",
+- "Status": "",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "memoryMode",
+- "Type": "string",
+- "Doc": "obsolete, no effect\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "\"\"",
+- "Status": "experimental",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "expandWorkspaceToModule",
+- "Type": "bool",
+- "Doc": "expandWorkspaceToModule determines which packages are considered\n\"workspace packages\" when the workspace is using modules.\n\nWorkspace packages affect the scope of workspace-wide operations. Notably,\ngopls diagnoses all packages considered to be part of the workspace after\nevery keystroke, so by setting \"ExpandWorkspaceToModule\" to false, and\nopening a nested workspace directory, you can reduce the amount of work\ngopls has to do to keep your workspace up to date.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "true",
+- "Status": "experimental",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "standaloneTags",
+- "Type": "[]string",
+- "Doc": "standaloneTags specifies a set of build constraints that identify\nindividual Go source files that make up the entire main package of an\nexecutable.\n\nA common example of standalone main files is the convention of using the\ndirective `//go:build ignore` to denote files that are not intended to be\nincluded in any package, for example because they are invoked directly by\nthe developer using `go run`.\n\nGopls considers a file to be a standalone main file if and only if it has\npackage name \"main\" and has a build directive of the exact form\n\"//go:build tag\" or \"// +build tag\", where tag is among the list of tags\nconfigured by this setting. Notably, if the build constraint is more\ncomplicated than a simple tag (such as the composite constraint\n`//go:build tag \u0026\u0026 go1.18`), the file is not considered to be a standalone\nmain file.\n\nThis setting is only supported when gopls is built with Go 1.16 or later.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "[\"ignore\"]",
+- "Status": "",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "workspaceFiles",
+- "Type": "[]string",
+- "Doc": "workspaceFiles configures the set of globs that match files defining the\nlogical build of the current workspace. Any on-disk changes to any files\nmatching a glob specified here will trigger a reload of the workspace.\n\nThis setting need only be customized in environments with a custom\nGOPACKAGESDRIVER.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "[]",
+- "Status": "",
+- "Hierarchy": "build",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "hoverKind",
+- "Type": "enum",
+- "Doc": "hoverKind controls the information that appears in the hover text.\nSingleLine is intended for use only by authors of editor plugins.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"FullDocumentation\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"NoDocumentation\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"SingleLine\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"Structured\"",
+- "Doc": "`\"Structured\"` is a misguided experimental setting that returns a JSON\nhover format. This setting should not be used, as it will be removed in a\nfuture release of gopls.\n",
+- "Status": ""
+- },
+- {
+- "Value": "\"SynopsisDocumentation\"",
+- "Doc": "",
+- "Status": ""
+- }
+- ],
+- "Default": "\"FullDocumentation\"",
+- "Status": "",
+- "Hierarchy": "ui.documentation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "linkTarget",
+- "Type": "string",
+- "Doc": "linkTarget is the base URL for links to Go package\ndocumentation returned by LSP operations such as Hover and\nDocumentLinks and in the CodeDescription field of each\nDiagnostic.\n\nIt might be one of:\n\n* `\"godoc.org\"`\n* `\"pkg.go.dev\"`\n\nIf company chooses to use its own `godoc.org`, its address can be used as well.\n\nModules matching the GOPRIVATE environment variable will not have\ndocumentation links in hover.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "\"pkg.go.dev\"",
+- "Status": "",
+- "Hierarchy": "ui.documentation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "linksInHover",
+- "Type": "enum",
+- "Doc": "linksInHover controls the presence of documentation links in hover markdown.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "false",
+- "Doc": "false: do not show links",
+- "Status": ""
+- },
+- {
+- "Value": "true",
+- "Doc": "true: show links to the `linkTarget` domain",
+- "Status": ""
+- },
+- {
+- "Value": "\"gopls\"",
+- "Doc": "`\"gopls\"`: show links to gopls' internal documentation viewer",
+- "Status": ""
+- }
+- ],
+- "Default": "true",
+- "Status": "",
+- "Hierarchy": "ui.documentation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "usePlaceholders",
+- "Type": "bool",
+- "Doc": "placeholders enables placeholders for function parameters or struct\nfields in completion responses.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "",
+- "Hierarchy": "ui.completion",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "completionBudget",
+- "Type": "time.Duration",
+- "Doc": "completionBudget is the soft latency goal for completion requests. Most\nrequests finish in a couple milliseconds, but in some cases deep\ncompletions can take much longer. As we use up our budget we\ndynamically reduce the search scope to ensure we return timely\nresults. Zero means unlimited.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "\"100ms\"",
+- "Status": "debug",
+- "Hierarchy": "ui.completion",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "matcher",
+- "Type": "enum",
+- "Doc": "matcher sets the algorithm that is used when calculating completion\ncandidates.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"CaseInsensitive\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"CaseSensitive\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"Fuzzy\"",
+- "Doc": "",
+- "Status": ""
+- }
+- ],
+- "Default": "\"Fuzzy\"",
+- "Status": "advanced",
+- "Hierarchy": "ui.completion",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "experimentalPostfixCompletions",
+- "Type": "bool",
+- "Doc": "experimentalPostfixCompletions enables artificial method snippets\nsuch as \"someSlice.sort!\".\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "true",
+- "Status": "experimental",
+- "Hierarchy": "ui.completion",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "completeFunctionCalls",
+- "Type": "bool",
+- "Doc": "completeFunctionCalls enables function call completion.\n\nWhen completing a statement, or when a function return type matches the\nexpected of the expression being completed, completion may suggest call\nexpressions (i.e. may include parentheses).\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "true",
+- "Status": "",
+- "Hierarchy": "ui.completion",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "importShortcut",
+- "Type": "enum",
+- "Doc": "importShortcut specifies whether import statements should link to\ndocumentation or go to definitions.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"Both\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"Definition\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"Link\"",
+- "Doc": "",
+- "Status": ""
+- }
+- ],
+- "Default": "\"Both\"",
+- "Status": "",
+- "Hierarchy": "ui.navigation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "symbolMatcher",
+- "Type": "enum",
+- "Doc": "symbolMatcher sets the algorithm that is used when finding workspace symbols.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"CaseInsensitive\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"CaseSensitive\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"FastFuzzy\"",
+- "Doc": "",
+- "Status": ""
+- },
+- {
+- "Value": "\"Fuzzy\"",
+- "Doc": "",
+- "Status": ""
+- }
+- ],
+- "Default": "\"FastFuzzy\"",
+- "Status": "advanced",
+- "Hierarchy": "ui.navigation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "symbolStyle",
+- "Type": "enum",
+- "Doc": "symbolStyle controls how symbols are qualified in symbol responses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"symbolStyle\": \"Dynamic\",\n...\n}\n```\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"Dynamic\"",
+- "Doc": "`\"Dynamic\"` uses whichever qualifier results in the highest scoring\nmatch for the given symbol query. Here a \"qualifier\" is any \"/\" or \".\"\ndelimited suffix of the fully qualified symbol. i.e. \"to/pkg.Foo.Field\" or\njust \"Foo.Field\".\n",
+- "Status": ""
+- },
+- {
+- "Value": "\"Full\"",
+- "Doc": "`\"Full\"` is fully qualified symbols, i.e.\n\"path/to/pkg.Foo.Field\".\n",
+- "Status": ""
+- },
+- {
+- "Value": "\"Package\"",
+- "Doc": "`\"Package\"` is package qualified symbols i.e.\n\"pkg.Foo.Field\".\n",
+- "Status": ""
+- }
+- ],
+- "Default": "\"Dynamic\"",
+- "Status": "advanced",
+- "Hierarchy": "ui.navigation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "symbolScope",
+- "Type": "enum",
+- "Doc": "symbolScope controls which packages are searched for workspace/symbol\nrequests. When the scope is \"workspace\", gopls searches only workspace\npackages. When the scope is \"all\", gopls searches all loaded packages,\nincluding dependencies and the standard library.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"all\"",
+- "Doc": "`\"all\"` matches symbols in any loaded package, including\ndependencies.\n",
+- "Status": ""
+- },
+- {
+- "Value": "\"workspace\"",
+- "Doc": "`\"workspace\"` matches symbols in workspace packages only.\n",
+- "Status": ""
+- }
+- ],
+- "Default": "\"all\"",
+- "Status": "",
+- "Hierarchy": "ui.navigation",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "analyses",
+- "Type": "map[string]bool",
+- "Doc": "analyses specify analyses that the user would like to enable or disable.\nA map of the names of analysis passes that should be enabled/disabled.\nA full list of analyzers that gopls uses can be found in\n[analyzers.md](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md).\n\nExample Usage:\n\n```json5\n...\n\"analyses\": {\n \"unreachable\": false, // Disable the unreachable analyzer.\n \"unusedvariable\": true // Enable the unusedvariable analyzer.\n}\n...\n```\n",
+- "EnumKeys": {
+- "ValueType": "bool",
+- "Keys": [
+- {
+- "Name": "\"QF1001\"",
+- "Doc": "Apply De Morgan's law\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1002\"",
+- "Doc": "Convert untagged switch to tagged switch\n\nAn untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n switch {\n case x == 1 || x == 2, x == 3:\n ...\n case x == 4:\n ...\n default:\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2, 3:\n ...\n case 4:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1003\"",
+- "Doc": "Convert if/else-if chain to tagged switch\n\nA series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n if x == 1 || x == 2 {\n ...\n } else if x == 3 {\n ...\n } else {\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2:\n ...\n case 3:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1004\"",
+- "Doc": "Use strings.ReplaceAll instead of strings.Replace with n == -1\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1005\"",
+- "Doc": "Expand call to math.Pow\n\nSome uses of math.Pow can be simplified to basic multiplication.\n\nBefore:\n\n math.Pow(x, 2)\n\nAfter:\n\n x * x\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1006\"",
+- "Doc": "Lift if+break into loop condition\n\nBefore:\n\n for {\n if done {\n break\n }\n ...\n }\n\nAfter:\n\n for !done {\n ...\n }\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1007\"",
+- "Doc": "Merge conditional assignment into variable declaration\n\nBefore:\n\n x := false\n if someCondition {\n x = true\n }\n\nAfter:\n\n x := someCondition\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1008\"",
+- "Doc": "Omit embedded fields from selector expression\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1009\"",
+- "Doc": "Use time.Time.Equal instead of == operator\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1010\"",
+- "Doc": "Convert slice of bytes to string when printing it\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1011\"",
+- "Doc": "Omit redundant type from variable declaration\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"QF1012\"",
+- "Doc": "Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))\n\nAvailable since\n 2022.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1000\"",
+- "Doc": "Use plain channel send or receive instead of single-case select\n\nSelect statements with a single case can be replaced with a simple\nsend or receive.\n\nBefore:\n\n select {\n case x := \u003c-ch:\n fmt.Println(x)\n }\n\nAfter:\n\n x := \u003c-ch\n fmt.Println(x)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1001\"",
+- "Doc": "Replace for loop with call to copy\n\nUse copy() for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.\n\nBefore:\n\n for i, x := range src {\n dst[i] = x\n }\n\nAfter:\n\n copy(dst, src)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1002\"",
+- "Doc": "Omit comparison with boolean constant\n\nBefore:\n\n if x == true {}\n\nAfter:\n\n if x {}\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1003\"",
+- "Doc": "Replace call to strings.Index with strings.Contains\n\nBefore:\n\n if strings.Index(x, y) != -1 {}\n\nAfter:\n\n if strings.Contains(x, y) {}\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1004\"",
+- "Doc": "Replace call to bytes.Compare with bytes.Equal\n\nBefore:\n\n if bytes.Compare(x, y) == 0 {}\n\nAfter:\n\n if bytes.Equal(x, y) {}\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1005\"",
+- "Doc": "Drop unnecessary use of the blank identifier\n\nIn many cases, assigning to the blank identifier is unnecessary.\n\nBefore:\n\n for _ = range s {}\n x, _ = someMap[key]\n _ = \u003c-ch\n\nAfter:\n\n for range s{}\n x = someMap[key]\n \u003c-ch\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1006\"",
+- "Doc": "Use 'for { ... }' for infinite loops\n\nFor infinite loops, using for { ... } is the most idiomatic choice.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1007\"",
+- "Doc": "Simplify regular expression by using raw string literal\n\nRaw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.\n\nBefore:\n\n regexp.Compile(\"\\\\A(\\\\w+) profile: total \\\\d+\\\\n\\\\z\")\n\nAfter:\n\n regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1008\"",
+- "Doc": "Simplify returning boolean expression\n\nBefore:\n\n if \u003cexpr\u003e {\n return true\n }\n return false\n\nAfter:\n\n return \u003cexpr\u003e\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1009\"",
+- "Doc": "Omit redundant nil check on slices, maps, and channels\n\nThe len function is defined for all slices, maps, and\nchannels, even nil ones, which have a length of zero. It is not necessary to\ncheck for nil before checking that their length is not zero.\n\nBefore:\n\n if x != nil \u0026\u0026 len(x) != 0 {}\n\nAfter:\n\n if len(x) != 0 {}\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1010\"",
+- "Doc": "Omit default slice index\n\nWhen slicing, the second index defaults to the length of the value,\nmaking s[n:len(s)] and s[n:] equivalent.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1011\"",
+- "Doc": "Use a single append to concatenate two slices\n\nBefore:\n\n for _, e := range y {\n x = append(x, e)\n }\n \n for i := range y {\n x = append(x, y[i])\n }\n \n for i := range y {\n v := y[i]\n x = append(x, v)\n }\n\nAfter:\n\n x = append(x, y...)\n x = append(x, y...)\n x = append(x, y...)\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1012\"",
+- "Doc": "Replace time.Now().Sub(x) with time.Since(x)\n\nThe time.Since helper has the same effect as using time.Now().Sub(x)\nbut is easier to read.\n\nBefore:\n\n time.Now().Sub(x)\n\nAfter:\n\n time.Since(x)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1016\"",
+- "Doc": "Use a type conversion instead of manually copying struct fields\n\nTwo struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.\n\nBefore:\n\n var x T1\n y := T2{\n Field1: x.Field1,\n Field2: x.Field2,\n }\n\nAfter:\n\n var x T1\n y := T2(x)\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1017\"",
+- "Doc": "Replace manual trimming with strings.TrimPrefix\n\nInstead of using strings.HasPrefix and manual slicing, use the\nstrings.TrimPrefix function. If the string doesn't start with the\nprefix, the original string will be returned. Using strings.TrimPrefix\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.\n\nBefore:\n\n if strings.HasPrefix(str, prefix) {\n str = str[len(prefix):]\n }\n\nAfter:\n\n str = strings.TrimPrefix(str, prefix)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1018\"",
+- "Doc": "Use 'copy' for sliding elements\n\ncopy() permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.\n\nBefore:\n\n for i := 0; i \u003c n; i++ {\n bs[i] = bs[offset+i]\n }\n\nAfter:\n\n copy(bs[:n], bs[offset:])\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1019\"",
+- "Doc": "Simplify 'make' call by omitting redundant arguments\n\nThe 'make' function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1020\"",
+- "Doc": "Omit redundant nil check in type assertion\n\nBefore:\n\n if _, ok := i.(T); ok \u0026\u0026 i != nil {}\n\nAfter:\n\n if _, ok := i.(T); ok {}\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1021\"",
+- "Doc": "Merge variable declaration and assignment\n\nBefore:\n\n var x uint\n x = 1\n\nAfter:\n\n var x uint = 1\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1023\"",
+- "Doc": "Omit redundant control flow\n\nFunctions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1024\"",
+- "Doc": "Replace x.Sub(time.Now()) with time.Until(x)\n\nThe time.Until helper has the same effect as using x.Sub(time.Now())\nbut is easier to read.\n\nBefore:\n\n x.Sub(time.Now())\n\nAfter:\n\n time.Until(x)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1025\"",
+- "Doc": "Don't use fmt.Sprintf(\"%s\", x) unnecessarily\n\nIn many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n type T1 string\n type T2 int\n\n func (T2) String() string { return \"Hello, world\" }\n\n var x string\n var y T1\n var z T2\n\nwe can simplify\n\n fmt.Sprintf(\"%s\", x)\n fmt.Sprintf(\"%s\", y)\n fmt.Sprintf(\"%s\", z)\n\nto\n\n x\n string(y)\n z.String()\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1028\"",
+- "Doc": "Simplify error construction with fmt.Errorf\n\nBefore:\n\n errors.New(fmt.Sprintf(...))\n\nAfter:\n\n fmt.Errorf(...)\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1029\"",
+- "Doc": "Range over the string directly\n\nRanging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.\n\nBefore:\n\n for _, r := range []rune(s) {}\n\nAfter:\n\n for _, r := range s {}\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1030\"",
+- "Doc": "Use bytes.Buffer.String or bytes.Buffer.Bytes\n\nbytes.Buffer has both a String and a Bytes method. It is almost never\nnecessary to use string(buf.Bytes()) or []byte(buf.String()) – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\nm[string(buf.Bytes())] is more efficient than m[buf.String()].\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1031\"",
+- "Doc": "Omit redundant nil check around loop\n\nYou can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.\n\nBefore:\n\n if s != nil {\n for _, x := range s {\n ...\n }\n }\n\nAfter:\n\n for _, x := range s {\n ...\n }\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1032\"",
+- "Doc": "Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)\n\nThe sort.Ints, sort.Float64s and sort.Strings functions are easier to\nread than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))\nand sort.Sort(sort.StringSlice(x)).\n\nBefore:\n\n sort.Sort(sort.StringSlice(x))\n\nAfter:\n\n sort.Strings(x)\n\nAvailable since\n 2019.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1033\"",
+- "Doc": "Unnecessary guard around call to 'delete'\n\nCalling delete on a nil map is a no-op.\n\nAvailable since\n 2019.2\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1034\"",
+- "Doc": "Use result of type assertion to simplify cases\n\nAvailable since\n 2019.2\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1035\"",
+- "Doc": "Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header\n\nThe methods on net/http.Header, namely Add, Del, Get\nand Set, already canonicalize the given header name.\n\nAvailable since\n 2020.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1036\"",
+- "Doc": "Unnecessary guard around map access\n\nWhen accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n if _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n } else {\n m[\"foo\"] = []string{\"bar\"}\n }\n\ncan be simplified to\n\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n\nand\n\n if _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n } else {\n m2[\"k\"] = 4\n }\n\ncan be simplified to\n\n m[\"k\"] += 4\n\nAvailable since\n 2020.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1037\"",
+- "Doc": "Elaborate way of sleeping\n\nUsing a select statement with a single case receiving\nfrom the result of time.After is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.\n\nAvailable since\n 2020.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1038\"",
+- "Doc": "Unnecessarily complex way of printing formatted string\n\nInstead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).\n\nAvailable since\n 2020.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1039\"",
+- "Doc": "Unnecessary use of fmt.Sprint\n\nCalling fmt.Sprint with a single string argument is unnecessary\nand identical to using the string directly.\n\nAvailable since\n 2020.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"S1040\"",
+- "Doc": "Type assertion to current type\n\nThe type assertion x.(SomeInterface), when x already has type\nSomeInterface, can only fail if x is nil. Usually, this is\nleft-over code from when x had a different type and you can safely\ndelete the type assertion. If you want to check that x is not nil,\nconsider being explicit and using an actual if x == nil comparison\ninstead of relying on the type assertion panicking.\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1000\"",
+- "Doc": "Invalid regular expression\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1001\"",
+- "Doc": "Invalid template\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1002\"",
+- "Doc": "Invalid format in time.Parse\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1003\"",
+- "Doc": "Unsupported argument to functions in encoding/binary\n\nThe encoding/binary package can only serialize types with known sizes.\nThis precludes the use of the int and uint types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, bool wasn't supported, either.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1004\"",
+- "Doc": "Suspiciously small untyped constant in time.Sleep\n\nThe time.Sleep function takes a time.Duration as its only argument.\nDurations are expressed in nanoseconds. Thus, calling time.Sleep(1)\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe time package provides constants such as time.Second to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example 5 * time.Second for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\nn * time.Nanosecond to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1005\"",
+- "Doc": "Invalid first argument to exec.Command\n\nos/exec runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n ls / /tmp\n\nwould be\n\n exec.Command(\"ls\", \"/\", \"/tmp\")\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a /bin/sh program:\n\n exec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1007\"",
+- "Doc": "Invalid URL in net/url.Parse\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1008\"",
+- "Doc": "Non-canonical key in http.Header map\n\nKeys in http.Header maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\nhttp.Header.Add and http.Header.Del convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating http.Header maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n h := http.Header{}\n h[\"etag\"] = []string{\"1234\"}\n h.Add(\"etag\", \"5678\")\n fmt.Println(h)\n\n // Output:\n // map[Etag:[5678] etag:[1234]]\n\nThe easiest way of obtaining the canonical form of a key is to use\nhttp.CanonicalHeaderKey.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1010\"",
+- "Doc": "(*regexp.Regexp).FindAll called with n == 0, which will always return zero results\n\nIf n \u003e= 0, the function returns at most n matches/submatches. To\nreturn all results, specify a negative number.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1011\"",
+- "Doc": "Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1012\"",
+- "Doc": "A nil context.Context is being passed to a function, consider using context.TODO instead\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1013\"",
+- "Doc": "io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1014\"",
+- "Doc": "Non-pointer value passed to Unmarshal or Decode\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1015\"",
+- "Doc": "Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions\n\nBefore Go 1.23, time.Tickers had to be closed to be able to be garbage\ncollected. Since time.Tick doesn't make it possible to close the underlying\nticker, using it repeatedly would leak memory.\n\nGo 1.23 fixes this by allowing tickers to be collected even if they weren't closed.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1016\"",
+- "Doc": "Trapping a signal that cannot be trapped\n\nNot all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1017\"",
+- "Doc": "Channels used with os/signal.Notify should be buffered\n\nThe os/signal package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1018\"",
+- "Doc": "strings.Replace called with n == 0, which does nothing\n\nWith n == 0, zero instances will be replaced. To replace all\ninstances, use a negative number, or use strings.ReplaceAll.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1020\"",
+- "Doc": "Using an invalid host:port pair with a net.Listen-related function\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1021\"",
+- "Doc": "Using bytes.Equal to compare two net.IP\n\nA net.IP stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two net.IPs, the net.IP.Equal method should\nbe used, as it takes both representations into account.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1023\"",
+- "Doc": "Modifying the buffer in an io.Writer implementation\n\nWrite must not modify the slice data, even temporarily.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1024\"",
+- "Doc": "A string cutset contains duplicate characters\n\nThe strings.TrimLeft and strings.TrimRight functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n strings.TrimLeft(\"42133word\", \"1234\")\n\nwill result in the string \"word\" – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use strings.TrimPrefix instead.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1025\"",
+- "Doc": "It is not possible to use (*time.Timer).Reset's return value correctly\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1026\"",
+- "Doc": "Cannot marshal channels or functions\n\nAvailable since\n 2019.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1027\"",
+- "Doc": "Atomic access to 64-bit variable must be 64-bit aligned\n\nOn ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.\n\nAvailable since\n 2019.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1028\"",
+- "Doc": "sort.Slice can only be used on slices\n\nThe first argument of sort.Slice must be a slice.\n\nAvailable since\n 2020.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1029\"",
+- "Doc": "Inappropriate key in call to context.WithValue\n\nThe provided key must be comparable and should not be\nof type string or any other built-in type to avoid collisions between\npackages using context. Users of WithValue should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an interface{},\ncontext keys often have concrete type struct{}. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.\n\nAvailable since\n 2020.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1030\"",
+- "Doc": "Invalid argument in call to a strconv function\n\nThis check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in strconv.\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1031\"",
+- "Doc": "Overlapping byte slices passed to an encoder\n\nIn an encoding function of the form Encode(dst, src), dst and\nsrc were found to reference the same memory. This can result in\nsrc bytes being overwritten before they are read, when the encoder\nwrites more than one byte per src byte.\n\nAvailable since\n 2024.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA1032\"",
+- "Doc": "Wrong order of arguments to errors.Is\n\nThe first argument of the function errors.Is is the error\nthat we have and the second argument is the error we're trying to match against.\nFor example:\n\n\tif errors.Is(err, io.EOF) { ... }\n\nThis check detects some cases where the two arguments have been swapped. It\nflags any calls where the first argument is referring to a package-level error\nvariable, such as\n\n\tif errors.Is(io.EOF, err) { /* this is wrong */ }\n\nAvailable since\n 2024.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA2001\"",
+- "Doc": "Empty critical section, did you mean to defer the unlock?\n\nEmpty critical sections of the kind\n\n mu.Lock()\n mu.Unlock()\n\nare very often a typo, and the following was intended instead:\n\n mu.Lock()\n defer mu.Unlock()\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a //lint:ignore directive can be used to suppress this\nrare false positive.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA2002\"",
+- "Doc": "Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA2003\"",
+- "Doc": "Deferred Lock right after locking, likely meant to defer Unlock instead\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA3000\"",
+- "Doc": "TestMain doesn't call os.Exit, hiding test failures\n\nTest executables (and in turn 'go test') exit with a non-zero status\ncode if any tests failed. When specifying your own TestMain function,\nit is your responsibility to arrange for this, by calling os.Exit with\nthe correct code. The correct code is returned by (*testing.M).Run, so\nthe usual way of implementing TestMain is to end it with\nos.Exit(m.Run()).\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA3001\"",
+- "Doc": "Assigning to b.N in benchmarks distorts the results\n\nThe testing package dynamically sets b.N to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter b.N as this would\nfalsify results.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4000\"",
+- "Doc": "Binary operator has identical expressions on both sides\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4001\"",
+- "Doc": "\u0026*x gets simplified to x, it does not copy x\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4003\"",
+- "Doc": "Comparing unsigned values against negative values is pointless\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4004\"",
+- "Doc": "The loop exits unconditionally after one iteration\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4005\"",
+- "Doc": "Field assignment that will never be observed. Did you mean to use a pointer receiver?\n\nAvailable since\n 2021.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4006\"",
+- "Doc": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4008\"",
+- "Doc": "The variable in the loop condition never changes, are you incrementing the wrong variable?\n\nFor example:\n\n\tfor i := 0; i \u003c 10; j++ { ... }\n\nThis may also occur when a loop can only execute once because of unconditional\ncontrol flow that terminates the loop. For example, when a loop body contains an\nunconditional break, return, or panic:\n\n\tfunc f() {\n\t\tpanic(\"oops\")\n\t}\n\tfunc g() {\n\t\tfor i := 0; i \u003c 10; i++ {\n\t\t\t// f unconditionally calls panic, which means \"i\" is\n\t\t\t// never incremented.\n\t\t\tf()\n\t\t}\n\t}\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4009\"",
+- "Doc": "A function argument is overwritten before its first use\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4010\"",
+- "Doc": "The result of append will never be observed anywhere\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4011\"",
+- "Doc": "Break statement with no effect. Did you mean to break out of an outer loop?\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4012\"",
+- "Doc": "Comparing a value against NaN even though no value is equal to NaN\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4013\"",
+- "Doc": "Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4014\"",
+- "Doc": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4015\"",
+- "Doc": "Calling functions like math.Ceil on floats converted from integers doesn't do anything useful\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4016\"",
+- "Doc": "Certain bitwise operations, such as x ^ 0, do not do anything useful\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4017\"",
+- "Doc": "Discarding the return values of a function without side effects, making the call pointless\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4018\"",
+- "Doc": "Self-assignment of variables\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4019\"",
+- "Doc": "Multiple, identical build constraints in the same file\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4020\"",
+- "Doc": "Unreachable case clause in a type switch\n\nIn a type switch like the following\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n\n var v any = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case T:\n // unreachable\n }\n\nthe second case clause can never be reached because T implements\nio.Reader and case clauses are evaluated in source order.\n\nAnother example:\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n func (T) Close() error { return nil }\n\n var v any = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case io.ReadCloser:\n // unreachable\n }\n\nEven though T has a Close method and thus implements io.ReadCloser,\nio.Reader will always match first. The method set of io.Reader is a\nsubset of io.ReadCloser. Thus it is impossible to match the second\ncase without matching the first case.\n\n\nStructurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n type T error\n type V error\n\n func doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n }\n\nthe following type switch will have an unreachable case clause:\n\n switch doSomething().(type) {\n case T:\n // ...\n case V:\n // unreachable\n }\n\nT will always match before V because they are structurally equivalent\nand therefore doSomething()'s return value implements both.\n\nAvailable since\n 2019.2\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4022\"",
+- "Doc": "Comparing the address of a variable against nil\n\nCode such as 'if \u0026x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.\n\nAvailable since\n 2020.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4023\"",
+- "Doc": "Impossible comparison of interface value with untyped nil\n\nUnder the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n func returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n }\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n func returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n }\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as *MyError, to help guarantee the error is\ncreated correctly. As an example, os.Open returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection at https://golang.org/doc/articles/laws_of_reflection.html.\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.\n\nAvailable since\n 2020.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4024\"",
+- "Doc": "Checking for impossible return value from a builtin function\n\nReturn values of the len and cap builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n if len(slice) \u003c 0 {\n fmt.Println(\"unreachable code\")\n }\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4025\"",
+- "Doc": "Integer division of literals that results in zero\n\nWhen dividing two integer constants, the result will\nalso be an integer. Thus, a division such as 2 / 3 results in 0.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4026\"",
+- "Doc": "Go constants cannot express negative zero\n\nIn IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals -0.0 and 0.0 have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\nmath.Copysign function: math.Copysign(0, -1).\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4027\"",
+- "Doc": "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL\n\n(*net/url.URL).Query parses the current value of net/url.URL.RawQuery\nand returns it as a map of type net/url.Values. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's RawQuery.\n\nAs a consequence, the following code pattern is an expensive no-op:\nu.Query().Add(key, value).\n\nAvailable since\n 2021.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4028\"",
+- "Doc": "x % 1 is always zero\n\nAvailable since\n 2022.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4029\"",
+- "Doc": "Ineffective attempt at sorting slice\n\nsort.Float64Slice, sort.IntSlice, and sort.StringSlice are\ntypes, not functions. Doing x = sort.StringSlice(x) does nothing,\nespecially not sort any values. The correct usage is\nsort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),\nbut there are more convenient helpers, namely sort.Float64s,\nsort.Ints, and sort.Strings.\n\nAvailable since\n 2022.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4030\"",
+- "Doc": "Ineffective attempt at generating random number\n\nFunctions in the math/rand package that accept upper limits, such\nas Intn, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be \u003e= 0 and \u003c n – they\ndon't include n. rand.Intn(1) therefore doesn't generate 0\nor 1, it always generates 0.\n\nAvailable since\n 2022.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4031\"",
+- "Doc": "Checking never-nil value against nil\n\nAvailable since\n 2022.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA4032\"",
+- "Doc": "Comparing runtime.GOOS or runtime.GOARCH against impossible value\n\nAvailable since\n 2024.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5000\"",
+- "Doc": "Assignment to nil map\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5001\"",
+- "Doc": "Deferring Close before checking for a possible error\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5002\"",
+- "Doc": "The empty for loop ('for {}') spins and can block the scheduler\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5003\"",
+- "Doc": "Defers in infinite loops will never execute\n\nDefers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5004\"",
+- "Doc": "'for { select { ...' with an empty default branch spins\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5005\"",
+- "Doc": "The finalizer references the finalized object, preventing garbage collection\n\nA finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5007\"",
+- "Doc": "Infinite recursive call\n\nA function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5008\"",
+- "Doc": "Invalid struct tag\n\nAvailable since\n 2019.2\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5010\"",
+- "Doc": "Impossible type assertion\n\nSome type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.\n\nAvailable since\n 2020.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5011\"",
+- "Doc": "Possible nil pointer dereference\n\nA pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n func fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n }\n\n func TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n }\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to panic or log.Fatal. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n func Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n }\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n }\n\n func fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n }\n\nStaticcheck will flag the dereference of x, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n }\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.\n\nAvailable since\n 2020.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA5012\"",
+- "Doc": "Passing odd-sized slice to function expecting even size\n\nSome functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, strings.NewReplacer takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.\n\nAvailable since\n 2020.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA6000\"",
+- "Doc": "Using regexp.Match or related in a loop, should use regexp.Compile\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA6001\"",
+- "Doc": "Missing an optimization opportunity when indexing maps by byte slices\n\nMap keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes m[string(b)] and\nuses the data of b directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n k := string(b)\n println(m[k])\n println(m[k])\n\nwill be less efficient than\n\n println(m[string(b)])\n println(m[string(b)])\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA6002\"",
+- "Doc": "Storing non-pointer values in sync.Pool allocates memory\n\nA sync.Pool is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA6003\"",
+- "Doc": "Converting a string to a slice of runes before ranging over it\n\nYou may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n for _, r := range s {}\n\nand\n\n for _, r := range []rune(s) {}\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA6005\"",
+- "Doc": "Inefficient string comparison with strings.ToLower or strings.ToUpper\n\nConverting two strings to the same case and comparing them like so\n\n if strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n }\n\nis significantly more expensive than comparing them with\nstrings.EqualFold(s1, s2). This is due to memory usage as well as\ncomputational complexity.\n\nstrings.ToLower will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/\n\nAvailable since\n 2019.2\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA6006\"",
+- "Doc": "Using io.WriteString to write []byte\n\nUsing io.WriteString to write a slice of bytes, as in\n\n io.WriteString(w, string(b))\n\nis both unnecessary and inefficient. Converting from []byte to string\nhas to allocate and copy the data, and we could simply use w.Write(b)\ninstead.\n\nAvailable since\n 2024.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9001\"",
+- "Doc": "Defers in range loops may not run when you expect them to\n\nAvailable since\n 2017.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9002\"",
+- "Doc": "Using a non-octal os.FileMode that looks like it was meant to be in octal.\n\nAvailable since\n 2017.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9003\"",
+- "Doc": "Empty body in an if or else branch\n\nAvailable since\n 2017.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9004\"",
+- "Doc": "Only the first constant has an explicit type\n\nIn a constant declaration such as the following:\n\n const (\n First byte = 1\n Second = 2\n )\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n const (\n First byte = iota\n Second\n )\n\nwhere First and Second do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n const (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n )\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\nWrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n package pkg\n\n const (\n EnumFirst uint8 = 1\n EnumSecond = 2\n )\n\n func fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n }\n\nfails to compile with\n\n ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n\n\nLosing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n package main\n\n import \"fmt\"\n\n type Enum int\n\n func (e Enum) String() string {\n return \"an enum\"\n }\n\n const (\n EnumFirst Enum = 1\n EnumSecond = 2\n )\n\n func main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n }\n\nThis code will output\n\n an enum\n 2\n\nas EnumSecond has no explicit type, and thus defaults to int.\n\nAvailable since\n 2019.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9005\"",
+- "Doc": "Trying to marshal a struct with no public fields nor custom marshaling\n\nThe encoding/json and encoding/xml packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via MarshalJSON methods. It will also not\nflag empty structs.\n\nAvailable since\n 2019.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9006\"",
+- "Doc": "Dubious bit shifting of a fixed size integer value\n\nBit shifting a value past its size will always clear the value.\n\nFor instance:\n\n v := int8(42)\n v \u003e\u003e= 8\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n // Clear any value above 32 bits if integers are more than 32 bits.\n func f(i int) int {\n v := i \u003e\u003e 32\n v = v \u003c\u003c 32\n return i-v\n }\n\nAvailable since\n 2020.2\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9007\"",
+- "Doc": "Deleting a directory that shouldn't be deleted\n\nIt is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakenly using os.TempDir instead\nof ioutil.TempDir, or by forgetting to add a suffix to the result\nof os.UserHomeDir.\n\nWriting\n\n d := os.TempDir()\n defer os.RemoveAll(d)\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir\n\nAvailable since\n 2022.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9008\"",
+- "Doc": "else branch of a type assertion is probably not reading the right value\n\nWhen declaring variables as part of an if statement (like in 'if\nfoo := ...; foo {'), the same variables will also be in the scope of\nthe else branch. This means that in the following example\n\n if x, ok := x.(int); ok {\n // ...\n } else {\n fmt.Printf(\"unexpected type %T\", x)\n }\n\nx in the else branch will refer to the x from x, ok\n:=; it will not refer to the x that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so x in the else branch will always have the\nvalue 0 and the type int.\n\nAvailable since\n 2022.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"SA9009\"",
+- "Doc": "Ineffectual Go compiler directive\n\nA potential Go compiler directive was found, but is ineffectual as it begins\nwith whitespace.\n\nAvailable since\n 2024.1\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1000\"",
+- "Doc": "Incorrect or missing package comment\n\nPackages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://go.dev/wiki/CodeReviewComments#package-comments.\n\nAvailable since\n 2019.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1001\"",
+- "Doc": "Dot imports are discouraged\n\nDot imports that aren't in external test packages are discouraged.\n\nThe dot_import_whitelist option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n\u003e The import . form can be useful in tests that, due to circular\n\u003e dependencies, cannot be made part of the package being tested:\n\u003e \n\u003e package foo_test\n\u003e \n\u003e import (\n\u003e \"bar/testutil\" // also imports \"foo\"\n\u003e . \"foo\"\n\u003e )\n\u003e \n\u003e In this case, the test file cannot be in package foo because it\n\u003e uses bar/testutil, which imports foo. So we use the import .\n\u003e form to let the file pretend to be part of package foo even though\n\u003e it is not. Except for this one case, do not use import . in your\n\u003e programs. It makes the programs much harder to read because it is\n\u003e unclear whether a name like Quux is a top-level identifier in the\n\u003e current package or in an imported package.\n\nAvailable since\n 2019.1\n\nOptions\n dot_import_whitelist\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1003\"",
+- "Doc": "Poorly chosen identifier\n\nIdentifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://go.dev/doc/effective_go#package-names\n- https://go.dev/doc/effective_go#mixed-caps\n- https://go.dev/wiki/CodeReviewComments#initialisms\n- https://go.dev/wiki/CodeReviewComments#variable-names\n\nAvailable since\n 2019.1, non-default\n\nOptions\n initialisms\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1005\"",
+- "Doc": "Incorrectly formatted error string\n\nError strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n\u003e Error strings should not be capitalized (unless beginning with\n\u003e proper nouns or acronyms) or end with punctuation, since they are\n\u003e usually printed following other context. That is, use\n\u003e fmt.Errorf(\"something bad\") not fmt.Errorf(\"Something bad\"), so\n\u003e that log.Printf(\"Reading %s: %v\", filename, err) formats without a\n\u003e spurious capital letter mid-message.\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1006\"",
+- "Doc": "Poorly chosen receiver name\n\nQuoting Go Code Review Comments:\n\n\u003e The name of a method's receiver should be a reflection of its\n\u003e identity; often a one or two letter abbreviation of its type\n\u003e suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n\u003e names such as \"me\", \"this\" or \"self\", identifiers typical of\n\u003e object-oriented languages that place more emphasis on methods as\n\u003e opposed to functions. The name need not be as descriptive as that\n\u003e of a method argument, as its role is obvious and serves no\n\u003e documentary purpose. It can be very short as it will appear on\n\u003e almost every line of every method of the type; familiarity admits\n\u003e brevity. Be consistent, too: if you call the receiver \"c\" in one\n\u003e method, don't call it \"cl\" in another.\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1008\"",
+- "Doc": "A function's error value should be its last return value\n\nA function's error value should be its last return value.\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1011\"",
+- "Doc": "Poorly chosen name for variable of type time.Duration\n\ntime.Duration values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like 5 * time.Microsecond\nyields the value 5000. It is therefore not appropriate to suffix a\nvariable of type time.Duration with any time unit, such as Msec or\nMilli.\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1012\"",
+- "Doc": "Poorly chosen name for error variable\n\nError variables that are part of an API should be called errFoo or\nErrFoo.\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1013\"",
+- "Doc": "Should use constants for HTTP error codes, not magic numbers\n\nHTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The net/http\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.\n\nAvailable since\n 2019.1\n\nOptions\n http_status_code_whitelist\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1015\"",
+- "Doc": "A switch's default case should be the first or last case\n\nAvailable since\n 2019.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1016\"",
+- "Doc": "Use consistent method receiver names\n\nAvailable since\n 2019.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1017\"",
+- "Doc": "Don't use Yoda conditions\n\nYoda conditions are conditions of the kind 'if 42 == x', where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic 'if x == 42'.\n\nAvailable since\n 2019.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1018\"",
+- "Doc": "Avoid zero-width and control characters in string literals\n\nAvailable since\n 2019.2\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1019\"",
+- "Doc": "Importing the same package multiple times\n\nGo allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n import (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n )\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.\n\nAvailable since\n 2020.1\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1020\"",
+- "Doc": "The documentation of an exported function should start with the function's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1021\"",
+- "Doc": "The documentation of an exported type should start with type's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1022\"",
+- "Doc": "The documentation of an exported variable or constant should start with variable's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ST1023\"",
+- "Doc": "Redundant type in variable declaration\n\nAvailable since\n 2021.1, non-default\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"any\"",
+- "Doc": "replace interface{} with any\n\nThe any analyzer suggests replacing uses of the empty interface type,\n`interface{}`, with the `any` alias, which was introduced in Go 1.18.\nThis is a purely stylistic change that makes code more readable.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"appendclipped\"",
+- "Doc": "simplify append chains using slices.Concat\n\nThe appendclipped analyzer suggests replacing chains of append calls with a\nsingle call to slices.Concat, which was added in Go 1.21. For example,\nappend(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2).\n\nIn the simple case of appending to a newly allocated slice, such as\nappend([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s).\nFor byte slices, it will prefer bytes.Clone if the \"bytes\" package is\nalready imported.\n\nThis fix is only applied when the base of the append tower is a\n\"clipped\" slice, meaning its length and capacity are equal (e.g.\nx[:0:0] or []T{}). This is to avoid changing program behavior by\neliminating intended side effects on the base slice's underlying\narray.\n\nThis analyzer is currently disabled by default as the\ntransformation does not preserve the nilness of the base slice in\nall cases; see https://go.dev/issue/73557.",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"appends\"",
+- "Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"asmdecl\"",
+- "Doc": "report mismatches between assembly files and Go declarations",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"assign\"",
+- "Doc": "check for useless assignments\n\nThis checker reports assignments of the form x = x or a[i] = a[i].\nThese are almost always useless, and even when they aren't they are\nusually a mistake.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"atomic\"",
+- "Doc": "check for common mistakes using the sync/atomic package\n\nThe atomic checker looks for assignment statements of the form:\n\n\tx = atomic.AddUint64(\u0026x, 1)\n\nwhich are not atomic.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"atomicalign\"",
+- "Doc": "check for non-64-bits-aligned arguments to sync/atomic functions",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"bloop\"",
+- "Doc": "replace for-range over b.N with b.Loop\n\nThe bloop analyzer suggests replacing benchmark loops of the form\n`for i := 0; i \u003c b.N; i++` or `for range b.N` with the more modern\n`for b.Loop()`, which was added in Go 1.24.\n\nThis change makes benchmark code more readable and also removes the need for\nmanual timer control, so any preceding calls to b.StartTimer, b.StopTimer,\nor b.ResetTimer within the same function will also be removed.\n\nCaveats: The b.Loop() method is designed to prevent the compiler from\noptimizing away the benchmark loop, which can occasionally result in\nslower execution due to increased allocations in some specific cases.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"bools\"",
+- "Doc": "check for common mistakes involving boolean operators",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"buildtag\"",
+- "Doc": "check //go:build and // +build directives",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"cgocall\"",
+- "Doc": "detect some violations of the cgo pointer passing rules\n\nCheck for invalid cgo pointer passing.\nThis looks for code that uses cgo to call C code passing values\nwhose types are almost always invalid according to the cgo pointer\nsharing rules.\nSpecifically, it warns about attempts to pass a Go chan, map, func,\nor slice to C, either directly, or via a pointer, array, or struct.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"composites\"",
+- "Doc": "check for unkeyed composite literals\n\nThis analyzer reports a diagnostic for composite literals of struct\ntypes imported from another package that do not use the field-keyed\nsyntax. Such literals are fragile because the addition of a new field\n(even if unexported) to the struct will cause compilation to fail.\n\nAs an example,\n\n\terr = \u0026net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = \u0026net.DNSConfigError{Err: err}\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"copylocks\"",
+- "Doc": "check for locks erroneously passed by value\n\nInadvertently copying a value containing a lock, such as sync.Mutex or\nsync.WaitGroup, may cause both copies to malfunction. Generally such\nvalues should be referred to through a pointer.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"deepequalerrors\"",
+- "Doc": "check for calls of reflect.DeepEqual on error values\n\nThe deepequalerrors checker looks for calls of the form:\n\n reflect.DeepEqual(err1, err2)\n\nwhere err1 and err2 are errors. Using reflect.DeepEqual to compare\nerrors is discouraged.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"defers\"",
+- "Doc": "report common mistakes in defer statements\n\nThe defers analyzer reports a diagnostic when a defer statement would\nresult in a non-deferred call to time.Since, as experience has shown\nthat this is nearly always a mistake.\n\nFor example:\n\n\tstart := time.Now()\n\t...\n\tdefer recordLatency(time.Since(start)) // error: call to time.Since is not deferred\n\nThe correct code is:\n\n\tdefer func() { recordLatency(time.Since(start)) }()",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"deprecated\"",
+- "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"directive\"",
+- "Doc": "check Go toolchain directives such as //go:debug\n\nThis analyzer checks for problems with known Go toolchain directives\nin all Go source files in a package directory, even those excluded by\n//go:build constraints, and all non-Go source files too.\n\nFor //go:debug (see https://go.dev/doc/godebug), the analyzer checks\nthat the directives are placed only in Go source files, only above the\npackage comment, and only in package main or *_test.go files.\n\nSupport for other known directives may be added in the future.\n\nThis analyzer does not check //go:build, which is handled by the\nbuildtag analyzer.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"embed\"",
+- "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"errorsas\"",
+- "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analyzer reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"errorsastype\"",
+- "Doc": "replace errors.As with errors.AsType[T]\n\nThis analyzer suggests fixes to simplify uses of [errors.As] of\nthis form:\n\n\tvar myerr *MyErr\n\tif errors.As(err, \u0026myerr) {\n\t\thandle(myerr)\n\t}\n\nby using the less error-prone generic [errors.AsType] function,\nintroduced in Go 1.26:\n\n\tif myerr, ok := errors.AsType[*MyErr](err); ok {\n\t\thandle(myerr)\n\t}\n\nThe fix is only offered if the var declaration has the form shown and\nthere are no uses of myerr outside the if statement.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"fillreturns\"",
+- "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"fmtappendf\"",
+- "Doc": "replace []byte(fmt.Sprintf) with fmt.Appendf\n\nThe fmtappendf analyzer suggests replacing `[]byte(fmt.Sprintf(...))` with\n`fmt.Appendf(nil, ...)`. This avoids the intermediate allocation of a string\nby Sprintf, making the code more efficient. The suggestion also applies to\nfmt.Sprint and fmt.Sprintln.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"forvar\"",
+- "Doc": "remove redundant re-declaration of loop variables\n\nThe forvar analyzer removes unnecessary shadowing of loop variables.\nBefore Go 1.22, it was common to write `for _, x := range s { x := x ... }`\nto create a fresh variable for each iteration. Go 1.22 changed the semantics\nof `for` loops, making this pattern redundant. This analyzer removes the\nunnecessary `x := x` statement.\n\nThis fix only applies to `range` loops.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"framepointer\"",
+- "Doc": "report assembly that clobbers the frame pointer before saving it",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"hostport\"",
+- "Doc": "check format of addresses passed to net.Dial\n\nThis analyzer flags code that produce network address strings using\nfmt.Sprintf, as in this example:\n\n addr := fmt.Sprintf(\"%s:%d\", host, 12345) // \"will not work with IPv6\"\n ...\n conn, err := net.Dial(\"tcp\", addr) // \"when passed to dial here\"\n\nThe analyzer suggests a fix to use the correct approach, a call to\nnet.JoinHostPort:\n\n addr := net.JoinHostPort(host, \"12345\")\n ...\n conn, err := net.Dial(\"tcp\", addr)\n\nA similar diagnostic and fix are produced for a format string of \"%s:%s\".\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"httpresponse\"",
+- "Doc": "check for mistakes using HTTP responses\n\nA common mistake when using the net/http package is to defer a function\ncall to close the http.Response Body before checking the error that\ndetermines whether the response is valid:\n\n\tresp, err := http.Head(url)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// (defer statement belongs here)\n\nThis checker helps uncover latent nil dereference bugs by reporting a\ndiagnostic for such mistakes.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"ifaceassert\"",
+- "Doc": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"infertypeargs\"",
+- "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"inline\"",
+- "Doc": "apply fixes based on 'go:fix inline' comment directives\n\nThe inline analyzer inlines functions and constants that are marked for inlining.\n\n## Functions\n\nGiven a function that is marked for inlining, like this one:\n\n\t//go:fix inline\n\tfunc Square(x int) int { return Pow(x, 2) }\n\nthis analyzer will recommend that calls to the function elsewhere, in the same\nor other packages, should be inlined.\n\nInlining can be used to move off of a deprecated function:\n\n\t// Deprecated: prefer Pow(x, 2).\n\t//go:fix inline\n\tfunc Square(x int) int { return Pow(x, 2) }\n\nIt can also be used to move off of an obsolete package,\nas when the import path has changed or a higher major version is available:\n\n\tpackage pkg\n\n\timport pkg2 \"pkg/v2\"\n\n\t//go:fix inline\n\tfunc F() { pkg2.F(nil) }\n\nReplacing a call pkg.F() by pkg2.F(nil) can have no effect on the program,\nso this mechanism provides a low-risk way to update large numbers of calls.\nWe recommend, where possible, expressing the old API in terms of the new one\nto enable automatic migration.\n\nThe inliner takes care to avoid behavior changes, even subtle ones,\nsuch as changes to the order in which argument expressions are\nevaluated. When it cannot safely eliminate all parameter variables,\nit may introduce a \"binding declaration\" of the form\n\n\tvar params = args\n\nto evaluate argument expressions in the correct order and bind them to\nparameter variables. Since the resulting code transformation may be\nstylistically suboptimal, such inlinings may be disabled by specifying\nthe -inline.allow_binding_decl=false flag to the analyzer driver.\n\n(In cases where it is not safe to \"reduce\" a call—that is, to replace\na call f(x) by the body of function f, suitably substituted—the\ninliner machinery is capable of replacing f by a function literal,\nfunc(){...}(). However, the inline analyzer discards all such\n\"literalizations\" unconditionally, again on grounds of style.)\n\n## Constants\n\nGiven a constant that is marked for inlining, like this one:\n\n\t//go:fix inline\n\tconst Ptr = Pointer\n\nthis analyzer will recommend that uses of Ptr should be replaced with Pointer.\n\nAs with functions, inlining can be used to replace deprecated constants and\nconstants in obsolete packages.\n\nA constant definition can be marked for inlining only if it refers to another\nnamed constant.\n\nThe \"//go:fix inline\" comment must appear before a single const declaration on its own,\nas above; before a const declaration that is part of a group, as in this case:\n\n\tconst (\n\t C = 1\n\t //go:fix inline\n\t Ptr = Pointer\n\t)\n\nor before a group, applying to every constant in the group:\n\n\t//go:fix inline\n\tconst (\n\t\tPtr = Pointer\n\t Val = Value\n\t)\n\nThe proposal https://go.dev/issue/32816 introduces the \"//go:fix inline\" directives.\n\nYou can use this command to apply inline fixes en masse:\n\n\t$ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./...",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"loopclosure\"",
+- "Doc": "check references to loop variables from within nested functions\n\nThis analyzer reports places where a function literal references the\niteration variable of an enclosing loop, and the loop calls the function\nin such a way (e.g. with go or defer) that it may outlive the loop\niteration and possibly observe the wrong value of the variable.\n\nNote: An iteration variable can only outlive a loop iteration in Go versions \u003c=1.21.\nIn Go 1.22 and later, the loop variable lifetimes changed to create a new\niteration variable per loop iteration. (See go.dev/issue/60078.)\n\nIn this example, all the deferred functions run after the loop has\ncompleted, so all observe the final value of v [\u003cgo1.22].\n\n\tfor _, v := range list {\n\t defer func() {\n\t use(v) // incorrect\n\t }()\n\t}\n\nOne fix is to create a new variable for each iteration of the loop:\n\n\tfor _, v := range list {\n\t v := v // new var per iteration\n\t defer func() {\n\t use(v) // ok\n\t }()\n\t}\n\nAfter Go version 1.22, the previous two for loops are equivalent\nand both are correct.\n\nThe next example uses a go statement and has a similar problem [\u003cgo1.22].\nIn addition, it has a data race because the loop updates v\nconcurrent with the goroutines accessing it.\n\n\tfor _, v := range elem {\n\t go func() {\n\t use(v) // incorrect, and a data race\n\t }()\n\t}\n\nA fix is the same as before. The checker also reports problems\nin goroutines started by golang.org/x/sync/errgroup.Group.\nA hard-to-spot variant of this form is common in parallel tests:\n\n\tfunc Test(t *testing.T) {\n\t for _, test := range tests {\n\t t.Run(test.name, func(t *testing.T) {\n\t t.Parallel()\n\t use(test) // incorrect, and a data race\n\t })\n\t }\n\t}\n\nThe t.Parallel() call causes the rest of the function to execute\nconcurrent with the loop [\u003cgo1.22].\n\nThe analyzer reports references only in the last statement,\nas it is not deep enough to understand the effects of subsequent\nstatements that might render the reference benign.\n(\"Last statement\" is defined recursively in compound\nstatements such as if, switch, and select.)\n\nSee: https://golang.org/doc/go_faq.html#closures_and_goroutines",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"lostcancel\"",
+- "Doc": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nWithDeadline and variants such as WithCancelCause must be called,\nor the new context will remain live until its parent context is cancelled.\n(The background context is never cancelled.)",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"maprange\"",
+- "Doc": "checks for unnecessary calls to maps.Keys and maps.Values in range statements\n\nConsider a loop written like this:\n\n\tfor val := range maps.Values(m) {\n\t\tfmt.Println(val)\n\t}\n\nThis should instead be written without the call to maps.Values:\n\n\tfor _, val := range m {\n\t\tfmt.Println(val)\n\t}\n\ngolang.org/x/exp/maps returns slices for Keys/Values instead of iterators,\nbut unnecessary calls should similarly be removed:\n\n\tfor _, key := range maps.Keys(m) {\n\t\tfmt.Println(key)\n\t}\n\nshould be rewritten as:\n\n\tfor key := range m {\n\t\tfmt.Println(key)\n\t}",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"mapsloop\"",
+- "Doc": "replace explicit loops over maps with calls to maps package\n\nThe mapsloop analyzer replaces loops of the form\n\n\tfor k, v := range x { m[k] = v }\n\nwith a single call to a function from the `maps` package, added in Go 1.23.\nDepending on the context, this could be `maps.Copy`, `maps.Insert`,\n`maps.Clone`, or `maps.Collect`.\n\nThe transformation to `maps.Clone` is applied conservatively, as it\npreserves the nilness of the source map, which may be a subtle change in\nbehavior if the original code did not handle a nil map in the same way.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"minmax\"",
+- "Doc": "replace if/else statements with calls to min or max\n\nThe minmax analyzer simplifies conditional assignments by suggesting the use\nof the built-in `min` and `max` functions, introduced in Go 1.21. For example,\n\n\tif a \u003c b { x = a } else { x = b }\n\nis replaced by\n\n\tx = min(a, b).\n\nThis analyzer avoids making suggestions for floating-point types,\nas the behavior of `min` and `max` with NaN values can differ from\nthe original if/else statement.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"newexpr\"",
+- "Doc": "simplify code by using go1.26's new(expr)\n\nThis analyzer finds declarations of functions of this form:\n\n\tfunc varOf(x int) *int { return \u0026x }\n\nand suggests a fix to turn them into inlinable wrappers around\ngo1.26's built-in new(expr) function:\n\n\tfunc varOf(x int) *int { return new(x) }\n\nIn addition, this analyzer suggests a fix for each call\nto one of the functions before it is transformed, so that\n\n\tuse(varOf(123))\n\nis replaced by:\n\n\tuse(new(123))\n\n(Wrapper functions such as varOf are common when working with Go\nserialization packages such as for JSON or protobuf, where pointers\nare often used to express optionality.)",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"nilfunc\"",
+- "Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"nilness\"",
+- "Doc": "check for redundant or impossible nil comparisons\n\nThe nilness checker inspects the control-flow graph of each function in\na package and reports nil pointer dereferences, degenerate nil\npointers, and panics with nil values. A degenerate comparison is of the form\nx==nil or x!=nil where x is statically known to be nil or non-nil. These are\noften a mistake, especially in control flow related to errors. Panics with nil\nvalues are checked because they are not detectable by\n\n\tif r := recover(); r != nil {\n\nThis check reports conditions such as:\n\n\tif f == nil { // impossible condition (f is a function)\n\t}\n\nand:\n\n\tp := \u0026v\n\t...\n\tif p != nil { // tautological condition\n\t}\n\nand:\n\n\tif p == nil {\n\t\tprint(*p) // nil dereference\n\t}\n\nand:\n\n\tif p == nil {\n\t\tpanic(p)\n\t}\n\nSometimes the control flow may be quite complex, making bugs hard\nto spot. In the example below, the err.Error expression is\nguaranteed to panic because, after the first return, err must be\nnil. The intervening loop is just a distraction.\n\n\t...\n\terr := g.Wait()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpartialSuccess := false\n\tfor _, err := range errs {\n\t\tif err == nil {\n\t\t\tpartialSuccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif partialSuccess {\n\t\treportStatus(StatusMessage{\n\t\t\tCode: code.ERROR,\n\t\t\tDetail: err.Error(), // \"nil dereference in dynamic method call\"\n\t\t})\n\t\treturn nil\n\t}\n\n...",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"nonewvars\"",
+- "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"noresultvalues\"",
+- "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"omitzero\"",
+- "Doc": "suggest replacing omitempty with omitzero for struct fields\n\nThe omitzero analyzer identifies uses of the `omitempty` JSON struct tag on\nfields that are themselves structs. The `omitempty` tag has no effect on\nstruct-typed fields. The analyzer offers two suggestions: either remove the\ntag, or replace it with `omitzero` (added in Go 1.24), which correctly\nomits the field if the struct value is zero.\n\nReplacing `omitempty` with `omitzero` is a change in behavior. The\noriginal code would always encode the struct field, whereas the\nmodified code will omit it if it is a zero-value.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"printf\"",
+- "Doc": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"rangeint\"",
+- "Doc": "replace 3-clause for loops with for-range over integers\n\nThe rangeint analyzer suggests replacing traditional for loops such\nas\n\n\tfor i := 0; i \u003c n; i++ { ... }\n\nwith the more idiomatic Go 1.22 style:\n\n\tfor i := range n { ... }\n\nThis transformation is applied only if (a) the loop variable is not\nmodified within the loop body and (b) the loop's limit expression\nis not modified within the loop, as `for range` evaluates its\noperand only once.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"recursiveiter\"",
+- "Doc": "check for inefficient recursive iterators\n\nThis analyzer reports when a function that returns an iterator\n(iter.Seq or iter.Seq2) calls itself as the operand of a range\nstatement, as this is inefficient.\n\nWhen implementing an iterator (e.g. iter.Seq[T]) for a recursive\ndata type such as a tree or linked list, it is tempting to\nrecursively range over the iterator for each child element.\n\nHere's an example of a naive iterator over a binary tree:\n\n\ttype tree struct {\n\t\tvalue int\n\t\tleft, right *tree\n\t}\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\tif t != nil {\n\t\t\t\tfor elem := range t.left.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !yield(t.value) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor elem := range t.right.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\nThough it correctly enumerates the elements of the tree, it hides a\nsignificant performance problem--two, in fact. Consider a balanced\ntree of N nodes. Iterating the root node will cause All to be\ncalled once on every node of the tree. This results in a chain of\nnested active range-over-func statements when yield(t.value) is\ncalled on a leaf node.\n\nThe first performance problem is that each range-over-func\nstatement must typically heap-allocate a variable, so iteration of\nthe tree allocates as many variables as there are elements in the\ntree, for a total of O(N) allocations, all unnecessary.\n\nThe second problem is that each call to yield for a leaf of the\ntree causes each of the enclosing range loops to receive a value,\nwhich they then immediately pass on to their respective yield\nfunction. This results in a chain of log(N) dynamic yield calls per\nelement, a total of O(N*log N) dynamic calls overall, when only\nO(N) are necessary.\n\nA better implementation strategy for recursive iterators is to\nfirst define the \"every\" operator for your recursive data type,\nwhere every(f) reports whether an arbitrary predicate f(x) is true\nfor every element x in the data type. For our tree, the every\nfunction would be:\n\n\tfunc (t *tree) every(f func(int) bool) bool {\n\t\treturn t == nil ||\n\t\t\tt.left.every(f) \u0026\u0026 f(t.value) \u0026\u0026 t.right.every(f)\n\t}\n\nFor example, this use of the every operator prints whether every\nelement in the tree is an even number:\n\n\teven := func(x int) bool { return x\u00261 == 0 }\n\tprintln(t.every(even))\n\nThen the iterator can be simply expressed as a trivial wrapper\naround the every operator:\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\t_ = t.every(yield)\n\t\t}\n\t}\n\nIn effect, tree.All computes whether yield returns true for each\nelement, short-circuiting if it ever returns false, then discards\nthe final boolean result.\n\nThis has much better performance characteristics: it makes one\ndynamic call per element of the tree, and it doesn't heap-allocate\nanything. It is also clearer.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"reflecttypefor\"",
+- "Doc": "replace reflect.TypeOf(x) with TypeFor[T]()\n\nThis analyzer suggests fixes to replace uses of reflect.TypeOf(x) with\nreflect.TypeFor, introduced in go1.22, when the desired runtime type\nis known at compile time, for example:\n\n\treflect.TypeOf(uint32(0)) -\u003e reflect.TypeFor[uint32]()\n\treflect.TypeOf((*ast.File)(nil)) -\u003e reflect.TypeFor[*ast.File]()\n\nIt also offers a fix to simplify the construction below, which uses\nreflect.TypeOf to return the runtime type for an interface type,\n\n\treflect.TypeOf((*io.Reader)(nil)).Elem()\n\nto:\n\n\treflect.TypeFor[io.Reader]()\n\nNo fix is offered in cases when the runtime type is dynamic, such as:\n\n\tvar r io.Reader = ...\n\treflect.TypeOf(r)\n\nor when the operand has potential side effects.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"shadow\"",
+- "Doc": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"shift\"",
+- "Doc": "check for shifts that equal or exceed the width of the integer",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"sigchanyzer\"",
+- "Doc": "check for unbuffered channel of os.Signal\n\nThis checker reports call expression of the form\n\n\tsignal.Notify(c \u003c-chan os.Signal, sig ...os.Signal),\n\nwhere c is an unbuffered channel, which can be at risk of missing the signal.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"simplifycompositelit\"",
+- "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"simplifyrange\"",
+- "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"simplifyslice\"",
+- "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"slicescontains\"",
+- "Doc": "replace loops with slices.Contains or slices.ContainsFunc\n\nThe slicescontains analyzer simplifies loops that check for the existence of\nan element in a slice. It replaces them with calls to `slices.Contains` or\n`slices.ContainsFunc`, which were added in Go 1.21.\n\nIf the expression for the target element has side effects, this\ntransformation will cause those effects to occur only once, not\nonce per tested slice element.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"slicesdelete\"",
+- "Doc": "replace append-based slice deletion with slices.Delete\n\nThe slicesdelete analyzer suggests replacing the idiom\n\n\ts = append(s[:i], s[j:]...)\n\nwith the more explicit\n\n\ts = slices.Delete(s, i, j)\n\nintroduced in Go 1.21.\n\nThis analyzer is disabled by default. The `slices.Delete` function\nzeros the elements between the new length and the old length of the\nslice to prevent memory leaks, which is a subtle difference in\nbehavior compared to the append-based idiom; see https://go.dev/issue/73686.",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"slicessort\"",
+- "Doc": "replace sort.Slice with slices.Sort for basic types\n\nThe slicessort analyzer simplifies sorting slices of basic ordered\ntypes. It replaces\n\n\tsort.Slice(s, func(i, j int) bool { return s[i] \u003c s[j] })\n\nwith the simpler `slices.Sort(s)`, which was added in Go 1.21.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"slog\"",
+- "Doc": "check for invalid structured logging calls\n\nThe slog checker looks for calls to functions from the log/slog\npackage that take alternating key-value pairs. It reports calls\nwhere an argument in a key position is neither a string nor a\nslog.Attr, and where a final key is missing its value.\nFor example,it would report\n\n\tslog.Warn(\"message\", 11, \"k\") // slog.Warn arg \"11\" should be a string or a slog.Attr\n\nand\n\n\tslog.Info(\"message\", \"k1\", v1, \"k2\") // call to slog.Info missing a final value",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"sortslice\"",
+- "Doc": "check the argument type of sort.Slice\n\nsort.Slice requires an argument of a slice type. Check that\nthe interface{} value passed to sort.Slice is actually a slice.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stditerators\"",
+- "Doc": "use iterators instead of Len/At-style APIs\n\nThis analyzer suggests a fix to replace each loop of the form:\n\n\tfor i := 0; i \u003c x.Len(); i++ {\n\t\tuse(x.At(i))\n\t}\n\nor its \"for elem := range x.Len()\" equivalent by a range loop over an\niterator offered by the same data type:\n\n\tfor elem := range x.All() {\n\t\tuse(x.At(i)\n\t}\n\nwhere x is one of various well-known types in the standard library.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stdmethods\"",
+- "Doc": "check signature of methods of well-known interfaces\n\nSometimes a type may be intended to satisfy an interface but may fail to\ndo so because of a mistake in its method signature.\nFor example, the result of this WriteTo method should be (int64, error),\nnot error, to satisfy io.WriterTo:\n\n\ttype myWriterTo struct{...}\n\tfunc (myWriterTo) WriteTo(w io.Writer) error { ... }\n\nThis check ensures that each method whose name matches one of several\nwell-known interface methods from the standard library has the correct\nsignature for that interface.\n\nChecked method names include:\n\n\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stdversion\"",
+- "Doc": "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stringintconv\"",
+- "Doc": "check for string(int) conversions\n\nThis checker flags conversions of the form string(x) where x is an integer\n(but not byte or rune) type. Such conversions are discouraged because they\nreturn the UTF-8 representation of the Unicode code point x, and not a decimal\nstring representation of x as one might expect. Furthermore, if x denotes an\ninvalid code point, the conversion cannot be statically rejected.\n\nFor conversions that intend on using the code point, consider replacing them\nwith string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the\nstring representation of the value in the desired base.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stringsbuilder\"",
+- "Doc": "replace += with strings.Builder\n\nThis analyzer replaces repeated string += string concatenation\noperations with calls to Go 1.10's strings.Builder.\n\nFor example:\n\n\tvar s = \"[\"\n\tfor x := range seq {\n\t\ts += x\n\t\ts += \".\"\n\t}\n\ts += \"]\"\n\tuse(s)\n\nis replaced by:\n\n\tvar s strings.Builder\n\ts.WriteString(\"[\")\n\tfor x := range seq {\n\t\ts.WriteString(x)\n\t\ts.WriteString(\".\")\n\t}\n\ts.WriteString(\"]\")\n\tuse(s.String())\n\nThis avoids quadratic memory allocation and improves performance.\n\nThe analyzer requires that all references to s except the final one\nare += operations. To avoid warning about trivial cases, at least one\nmust appear within a loop. The variable s must be a local\nvariable, not a global or parameter.\n\nThe sole use of the finished string must be the last reference to the\nvariable s. (It may appear within an intervening loop or function literal,\nsince even s.String() is called repeatedly, it does not allocate memory.)",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stringscutprefix\"",
+- "Doc": "replace HasPrefix/TrimPrefix with CutPrefix\n\nThe stringscutprefix analyzer simplifies a common pattern where code first\nchecks for a prefix with `strings.HasPrefix` and then removes it with\n`strings.TrimPrefix`. It replaces this two-step process with a single call\nto `strings.CutPrefix`, introduced in Go 1.20. The analyzer also handles\nthe equivalent functions in the `bytes` package.\n\nFor example, this input:\n\n\tif strings.HasPrefix(s, prefix) {\n\t use(strings.TrimPrefix(s, prefix))\n\t}\n\nis fixed to:\n\n\tif after, ok := strings.CutPrefix(s, prefix); ok {\n\t use(after)\n\t}\n\nThe analyzer also offers fixes to use CutSuffix in a similar way.\nThis input:\n\n\tif strings.HasSuffix(s, suffix) {\n\t use(strings.TrimSuffix(s, suffix))\n\t}\n\nis fixed to:\n\n\tif before, ok := strings.CutSuffix(s, suffix); ok {\n\t use(before)\n\t}",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"stringsseq\"",
+- "Doc": "replace ranging over Split/Fields with SplitSeq/FieldsSeq\n\nThe stringsseq analyzer improves the efficiency of iterating over substrings.\nIt replaces\n\n\tfor range strings.Split(...)\n\nwith the more efficient\n\n\tfor range strings.SplitSeq(...)\n\nwhich was added in Go 1.24 and avoids allocating a slice for the\nsubstrings. The analyzer also handles strings.Fields and the\nequivalent functions in the bytes package.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"structtag\"",
+- "Doc": "check that struct field tags conform to reflect.StructTag.Get\n\nAlso report certain struct tags (json, xml) used with unexported fields.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"testingcontext\"",
+- "Doc": "replace context.WithCancel with t.Context in tests\n\nThe testingcontext analyzer simplifies context management in tests. It\nreplaces the manual creation of a cancellable context,\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\nwith a single call to t.Context(), which was added in Go 1.24.\n\nThis change is only suggested if the `cancel` function is not used\nfor any other purpose.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"testinggoroutine\"",
+- "Doc": "report calls to (*testing.T).Fatal from goroutines started by a test\n\nFunctions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and\nSkip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.\nThis checker detects calls to these functions that occur within a goroutine\nstarted by the test. For example:\n\n\tfunc TestFoo(t *testing.T) {\n\t go func() {\n\t t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n\t }()\n\t}",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"tests\"",
+- "Doc": "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark, Fuzzing and Example functions checking\nmalformed names, wrong signatures and examples documenting non-existent\nidentifiers.\n\nPlease see the documentation for package testing in golang.org/pkg/testing\nfor the conventions that are enforced for Tests, Benchmarks, and Examples.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"timeformat\"",
+- "Doc": "check for calls of (time.Time).Format or time.Parse with 2006-02-01\n\nThe timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)\nformat. Internationally, \"yyyy-dd-mm\" does not occur in common calendar date\nstandards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unmarshal\"",
+- "Doc": "report passing non-pointer or non-interface values to unmarshal\n\nThe unmarshal analysis reports calls to functions such as json.Unmarshal\nin which the argument type is not a pointer or an interface.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unreachable\"",
+- "Doc": "check for unreachable code\n\nThe unreachable analyzer finds statements that execution can never reach\nbecause they are preceded by a return statement, a call to panic, an\ninfinite loop, or similar constructs.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unsafeptr\"",
+- "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unusedfunc\"",
+- "Doc": "check for unused functions, methods, etc\n\nThe unusedfunc analyzer reports functions and methods that are\nnever referenced outside of their own declaration.\n\nA function is considered unused if it is unexported and not\nreferenced (except within its own declaration).\n\nA method is considered unused if it is unexported, not referenced\n(except within its own declaration), and its name does not match\nthat of any method of an interface type declared within the same\npackage.\n\nThe tool may report false positives in some situations, for\nexample:\n\n - for a declaration of an unexported function that is referenced\n from another package using the go:linkname mechanism, if the\n declaration's doc comment does not also have a go:linkname\n comment.\n\n (Such code is in any case strongly discouraged: linkname\n annotations, if they must be used at all, should be used on both\n the declaration and the alias.)\n\n - for compiler intrinsics in the \"runtime\" package that, though\n never referenced, are known to the compiler and are called\n indirectly by compiled object code.\n\n - for functions called only from assembly.\n\n - for functions called only from files whose build tags are not\n selected in the current build configuration.\n\nSince these situations are relatively common in the low-level parts\nof the runtime, this analyzer ignores the standard library.\nSee https://go.dev/issue/71686 and https://go.dev/issue/74130 for\nfurther discussion of these limitations.\n\nThe unusedfunc algorithm is not as precise as the\ngolang.org/x/tools/cmd/deadcode tool, but it has the advantage that\nit runs within the modular analysis framework, enabling near\nreal-time feedback within gopls.\n\nThe unusedfunc analyzer also reports unused types, vars, and\nconstants. Enums--constants defined with iota--are ignored since\neven the unused values must remain present to preserve the logical\nordering.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unusedparams\"",
+- "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.\n\nThis analyzer ignores generated code.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unusedresult\"",
+- "Doc": "check for unused results of calls to some functions\n\nSome functions like fmt.Errorf return a result and have no side\neffects, so it is always a mistake to discard the result. Other\nfunctions may return an error that must not be ignored, or a cleanup\noperation that must be called. This analyzer reports calls to\nfunctions like these when the result of the call is ignored.\n\nThe set of functions may be controlled using flags.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unusedvariable\"",
+- "Doc": "check for unused variables and suggest fixes",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"unusedwrite\"",
+- "Doc": "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"waitgroup\"",
+- "Doc": "check for misuses of sync.WaitGroup\n\nThis analyzer detects mistaken calls to the (*sync.WaitGroup).Add\nmethod from inside a new goroutine, causing Add to race with Wait:\n\n\t// WRONG\n\tvar wg sync.WaitGroup\n\tgo func() {\n\t wg.Add(1) // \"WaitGroup.Add called from inside new goroutine\"\n\t defer wg.Done()\n\t ...\n\t}()\n\twg.Wait() // (may return prematurely before new goroutine starts)\n\nThe correct code calls Add before starting the goroutine:\n\n\t// RIGHT\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\twg.Wait()",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"waitgroup\"",
+- "Doc": "replace wg.Add(1)/go/wg.Done() with wg.Go\n\nThe waitgroup analyzer simplifies goroutine management with `sync.WaitGroup`.\nIt replaces the common pattern\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\nwith a single call to\n\n\twg.Go(func(){ ... })\n\nwhich was added in Go 1.25.",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"yield\"",
+- "Doc": "report calls to yield where the result is ignored\n\nAfter a yield function returns false, the caller should not call\nthe yield function again; generally the iterator should return\npromptly.\n\nThis example fails to check the result of the call to yield,\ncausing this analyzer to report a diagnostic:\n\n\tyield(1) // yield may be called again (on L2) after returning false\n\tyield(2)\n\nThe corrected code is either this:\n\n\tif yield(1) { yield(2) }\n\nor simply:\n\n\t_ = yield(1) \u0026\u0026 yield(2)\n\nIt is not always a mistake to ignore the result of yield.\nFor example, this is a valid single-element iterator:\n\n\tyield(1) // ok to ignore result\n\treturn\n\nIt is only a mistake when the yield call that returned false may be\nfollowed by another call.",
+- "Default": "true",
+- "Status": ""
+- }
+- ]
+- },
+- "EnumValues": null,
+- "Default": "{}",
+- "Status": "",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "staticcheck",
+- "Type": "bool",
+- "Doc": "staticcheck configures the default set of analyses staticcheck.io.\nThese analyses are documented on\n[Staticcheck's website](https://staticcheck.io/docs/checks/).\n\nThe \"staticcheck\" option has three values:\n- false: disable all staticcheck analyzers\n- true: enable all staticcheck analyzers\n- unset: enable a subset of staticcheck analyzers\n selected by gopls maintainers for runtime efficiency\n and analytic precision.\n\nRegardless of this setting, individual analyzers can be\nselectively enabled or disabled using the `analyses` setting.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "experimental",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "staticcheckProvided",
+- "Type": "bool",
+- "Doc": "",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "experimental",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "annotations",
+- "Type": "map[enum]bool",
+- "Doc": "annotations specifies the various kinds of compiler\noptimization details that should be reported as diagnostics\nwhen enabled for a package by the \"Toggle compiler\noptimization details\" (`gopls.gc_details`) command.\n\n(Some users care only about one kind of annotation in their\nprofiling efforts. More importantly, in large packages, the\nnumber of annotations can sometimes overwhelm the user\ninterface and exceed the per-file diagnostic limit.)\n\nTODO(adonovan): rename this field to CompilerOptDetail.\n",
+- "EnumKeys": {
+- "ValueType": "bool",
+- "Keys": [
+- {
+- "Name": "\"bounds\"",
+- "Doc": "`\"bounds\"` controls bounds checking diagnostics.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"escape\"",
+- "Doc": "`\"escape\"` controls diagnostics about escape choices.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"inline\"",
+- "Doc": "`\"inline\"` controls diagnostics about inlining choices.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"nil\"",
+- "Doc": "`\"nil\"` controls nil checks.\n",
+- "Default": "true",
+- "Status": ""
+- }
+- ]
+- },
+- "EnumValues": null,
+- "Default": "{\"bounds\":true,\"escape\":true,\"inline\":true,\"nil\":true}",
+- "Status": "",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "vulncheck",
+- "Type": "enum",
+- "Doc": "vulncheck enables vulnerability scanning.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"Imports\"",
+- "Doc": "`\"Imports\"`: In Imports mode, `gopls` will report vulnerabilities that affect packages\ndirectly and indirectly used by the analyzed main module.\n",
+- "Status": ""
+- },
+- {
+- "Value": "\"Off\"",
+- "Doc": "`\"Off\"`: Disable vulnerability analysis.\n",
+- "Status": ""
+- }
+- ],
+- "Default": "\"Off\"",
+- "Status": "experimental",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "diagnosticsDelay",
+- "Type": "time.Duration",
+- "Doc": "diagnosticsDelay controls the amount of time that gopls waits\nafter the most recent file modification before computing deep diagnostics.\nSimple diagnostics (parsing and type-checking) are always run immediately\non recently modified packages.\n\nThis option must be set to a valid duration string, for example `\"250ms\"`.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "\"1s\"",
+- "Status": "advanced",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "diagnosticsTrigger",
+- "Type": "enum",
+- "Doc": "diagnosticsTrigger controls when to run diagnostics.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": [
+- {
+- "Value": "\"Edit\"",
+- "Doc": "`\"Edit\"`: Trigger diagnostics on file edit and save. (default)\n",
+- "Status": ""
+- },
+- {
+- "Value": "\"Save\"",
+- "Doc": "`\"Save\"`: Trigger diagnostics only on file save. Events like initial workspace load\nor configuration change will still trigger diagnostics.\n",
+- "Status": ""
+- }
+- ],
+- "Default": "\"Edit\"",
+- "Status": "experimental",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "analysisProgressReporting",
+- "Type": "bool",
+- "Doc": "analysisProgressReporting controls whether gopls sends progress\nnotifications when construction of its index of analysis facts is taking a\nlong time. Cancelling these notifications will cancel the indexing task,\nthough it will restart after the next change in the workspace.\n\nWhen a package is opened for the first time and heavyweight analyses such as\nstaticcheck are enabled, it can take a while to construct the index of\nanalysis facts for all its dependencies. The index is cached in the\nfilesystem, so subsequent analysis should be faster.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "true",
+- "Status": "",
+- "Hierarchy": "ui.diagnostic",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "hints",
+- "Type": "map[enum]bool",
+- "Doc": "hints specify inlay hints that users want to see. A full list of hints\nthat gopls uses can be found in\n[inlayHints.md](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).\n",
+- "EnumKeys": {
+- "ValueType": "bool",
+- "Keys": [
+- {
+- "Name": "\"assignVariableTypes\"",
+- "Doc": "`\"assignVariableTypes\"` controls inlay hints for variable types in assign statements:\n```go\n\ti/* int*/, j/* int*/ := 0, len(r)-1\n```\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"compositeLiteralFields\"",
+- "Doc": "`\"compositeLiteralFields\"` inlay hints for composite literal field names:\n```go\n\t{/*in: */\"Hello, world\", /*want: */\"dlrow ,olleH\"}\n```\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"compositeLiteralTypes\"",
+- "Doc": "`\"compositeLiteralTypes\"` controls inlay hints for composite literal types:\n```go\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}\n```\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"constantValues\"",
+- "Doc": "`\"constantValues\"` controls inlay hints for constant values:\n```go\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)\n```\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"functionTypeParameters\"",
+- "Doc": "`\"functionTypeParameters\"` inlay hints for implicit type parameters on generic functions:\n```go\n\tmyFoo/*[int, string]*/(1, \"hello\")\n```\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"ignoredError\"",
+- "Doc": "`\"ignoredError\"` inlay hints for implicitly discarded errors:\n```go\n\tf.Close() // ignore error\n```\nThis check inserts an `// ignore error` hint following any\nstatement that is a function call whose error result is\nimplicitly ignored.\n\nTo suppress the hint, write an actual comment containing\n\"ignore error\" following the call statement, or explictly\nassign the result to a blank variable. A handful of common\nfunctions such as `fmt.Println` are excluded from the\ncheck.\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"parameterNames\"",
+- "Doc": "`\"parameterNames\"` controls inlay hints for parameter names:\n```go\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)\n```\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"rangeVariableTypes\"",
+- "Doc": "`\"rangeVariableTypes\"` controls inlay hints for variable types in range statements:\n```go\n\tfor k/* int*/, v/* string*/ := range []string{} {\n\t\tfmt.Println(k, v)\n\t}\n```\n",
+- "Default": "false",
+- "Status": ""
+- }
+- ]
+- },
+- "EnumValues": null,
+- "Default": "{}",
+- "Status": "experimental",
+- "Hierarchy": "ui.inlayhint",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "codelenses",
+- "Type": "map[enum]bool",
+- "Doc": "codelenses overrides the enabled/disabled state of each of gopls'\nsources of [Code Lenses](codelenses.md).\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n \"codelenses\": {\n \"generate\": false, // Don't show the `go generate` lens.\n }\n...\n}\n```\n",
+- "EnumKeys": {
+- "ValueType": "bool",
+- "Keys": [
+- {
+- "Name": "\"generate\"",
+- "Doc": "`\"generate\"`: Run `go generate`\n\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"regenerate_cgo\"",
+- "Doc": "`\"regenerate_cgo\"`: Re-generate cgo declarations\n\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"run_govulncheck\"",
+- "Doc": "`\"run_govulncheck\"`: Run govulncheck (legacy)\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"test\"",
+- "Doc": "`\"test\"`: Run tests and benchmarks\n\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
+- "Default": "false",
+- "Status": ""
+- },
+- {
+- "Name": "\"tidy\"",
+- "Doc": "`\"tidy\"`: Tidy go.mod file\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"upgrade_dependency\"",
+- "Doc": "`\"upgrade_dependency\"`: Update dependencies\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"vendor\"",
+- "Doc": "`\"vendor\"`: Update vendor directory\n\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
+- "Default": "true",
+- "Status": ""
+- },
+- {
+- "Name": "\"vulncheck\"",
+- "Doc": "`\"vulncheck\"`: Run govulncheck\n\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+- "Default": "false",
+- "Status": "experimental"
+- }
+- ]
+- },
+- "EnumValues": null,
+- "Default": "{\"generate\":true,\"regenerate_cgo\":true,\"run_govulncheck\":true,\"tidy\":true,\"upgrade_dependency\":true,\"vendor\":true}",
+- "Status": "",
+- "Hierarchy": "ui",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "semanticTokens",
+- "Type": "bool",
+- "Doc": "semanticTokens controls whether the LSP server will send\nsemantic tokens to the client.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "experimental",
+- "Hierarchy": "ui",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "noSemanticString",
+- "Type": "bool",
+- "Doc": "noSemanticString turns off the sending of the semantic token 'string'\n\nDeprecated: Use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "experimental",
+- "Hierarchy": "ui",
+- "DeprecationMessage": "use SemanticTokenTypes[\"string\"] = false instead. See\ngolang/vscode-go#3632\n"
+- },
+- {
+- "Name": "noSemanticNumber",
+- "Type": "bool",
+- "Doc": "noSemanticNumber turns off the sending of the semantic token 'number'\n\nDeprecated: Use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "experimental",
+- "Hierarchy": "ui",
+- "DeprecationMessage": "use SemanticTokenTypes[\"number\"] = false instead. See\ngolang/vscode-go#3632.\n"
+- },
+- {
+- "Name": "semanticTokenTypes",
+- "Type": "map[string]bool",
+- "Doc": "semanticTokenTypes configures the semantic token types. It allows\ndisabling types by setting each value to false.\nBy default, all types are enabled.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "{}",
+- "Status": "experimental",
+- "Hierarchy": "ui",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "semanticTokenModifiers",
+- "Type": "map[string]bool",
+- "Doc": "semanticTokenModifiers configures the semantic token modifiers. It allows\ndisabling modifiers by setting each value to false.\nBy default, all modifiers are enabled.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "{}",
+- "Status": "experimental",
+- "Hierarchy": "ui",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "newGoFileHeader",
+- "Type": "bool",
+- "Doc": "newGoFileHeader enables automatic insertion of the copyright comment\nand package declaration in a newly created Go file.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "true",
+- "Status": "",
+- "Hierarchy": "ui",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "packageMove",
+- "Type": "bool",
+- "Doc": "packageMove enables PrepareRename to send the full package path\nand allows users to move a package via renaming.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "experimental",
+- "Hierarchy": "ui",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "local",
+- "Type": "string",
+- "Doc": "local is the equivalent of the `goimports -local` flag, which puts\nimports beginning with this string after third-party packages. It should\nbe the prefix of the import path whose imports should be grouped\nseparately.\n\nIt is used when tidying imports (during an LSP Organize\nImports request) or when inserting new ones (for example,\nduring completion); an LSP Formatting request merely sorts the\nexisting imports.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "\"\"",
+- "Status": "",
+- "Hierarchy": "formatting",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "gofumpt",
+- "Type": "bool",
+- "Doc": "gofumpt indicates if we should run gofumpt formatting.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "",
+- "Hierarchy": "formatting",
+- "DeprecationMessage": ""
+- },
+- {
+- "Name": "verboseOutput",
+- "Type": "bool",
+- "Doc": "verboseOutput enables additional debug logging.\n",
+- "EnumKeys": {
+- "ValueType": "",
+- "Keys": null
+- },
+- "EnumValues": null,
+- "Default": "false",
+- "Status": "debug",
+- "Hierarchy": "",
+- "DeprecationMessage": ""
+- }
+- ]
+- },
+- "Lenses": [
+- {
+- "FileType": "Go",
+- "Lens": "generate",
+- "Title": "Run `go generate`",
+- "Doc": "\nThis codelens source annotates any `//go:generate` comments\nwith commands to run `go generate` in this directory, on\nall directories recursively beneath this one.\n\nSee [Generating code](https://go.dev/blog/generate) for\nmore details.\n",
+- "Default": true,
+- "Status": ""
+- },
+- {
+- "FileType": "Go",
+- "Lens": "regenerate_cgo",
+- "Title": "Re-generate cgo declarations",
+- "Doc": "\nThis codelens source annotates an `import \"C\"` declaration\nwith a command to re-run the [cgo\ncommand](https://pkg.go.dev/cmd/cgo) to regenerate the\ncorresponding Go declarations.\n\nUse this after editing the C code in comments attached to\nthe import, or in C header files included by it.\n",
+- "Default": true,
+- "Status": ""
+- },
+- {
+- "FileType": "Go",
+- "Lens": "test",
+- "Title": "Run tests and benchmarks",
+- "Doc": "\nThis codelens source annotates each `Test` and `Benchmark`\nfunction in a `*_test.go` file with a command to run it.\n\nThis source is off by default because VS Code has\na client-side custom UI for testing, and because progress\nnotifications are not a great UX for streamed test output.\nSee:\n- golang/go#67400 for a discussion of this feature.\n- https://github.com/joaotavora/eglot/discussions/1402\n for an alternative approach.\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "FileType": "go.mod",
+- "Lens": "run_govulncheck",
+- "Title": "Run govulncheck (legacy)",
+- "Doc": "\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run Govulncheck asynchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+- "Default": true,
+- "Status": ""
+- },
+- {
+- "FileType": "go.mod",
+- "Lens": "tidy",
+- "Title": "Tidy go.mod file",
+- "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\ntidy`](https://go.dev/ref/mod#go-mod-tidy), which ensures\nthat the go.mod file matches the source code in the module.\n",
+- "Default": true,
+- "Status": ""
+- },
+- {
+- "FileType": "go.mod",
+- "Lens": "upgrade_dependency",
+- "Title": "Update dependencies",
+- "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with commands to:\n\n- check for available upgrades,\n- upgrade direct dependencies, and\n- upgrade all dependencies transitively.\n",
+- "Default": true,
+- "Status": ""
+- },
+- {
+- "FileType": "go.mod",
+- "Lens": "vendor",
+- "Title": "Update vendor directory",
+- "Doc": "\nThis codelens source annotates the `module` directive in a\ngo.mod file with a command to run [`go mod\nvendor`](https://go.dev/ref/mod#go-mod-vendor), which\ncreates or updates the directory named `vendor` in the\nmodule root so that it contains an up-to-date copy of all\nnecessary package dependencies.\n",
+- "Default": true,
+- "Status": ""
+- },
+- {
+- "FileType": "go.mod",
+- "Lens": "vulncheck",
+- "Title": "Run govulncheck",
+- "Doc": "\nThis codelens source annotates the `module` directive in a go.mod file\nwith a command to run govulncheck synchronously.\n\n[Govulncheck](https://go.dev/blog/vuln) is a static analysis tool that\ncomputes the set of functions reachable within your application, including\ndependencies; queries a database of known security vulnerabilities; and\nreports any potential problems it finds.\n",
+- "Default": false,
+- "Status": "experimental"
+- }
+- ],
+- "Analyzers": [
+- {
+- "Name": "QF1001",
+- "Doc": "Apply De Morgan's law\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1001",
+- "Default": false
+- },
+- {
+- "Name": "QF1002",
+- "Doc": "Convert untagged switch to tagged switch\n\nAn untagged switch that compares a single variable against a series of\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n switch {\n case x == 1 || x == 2, x == 3:\n ...\n case x == 4:\n ...\n default:\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2, 3:\n ...\n case 4:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1002",
+- "Default": true
+- },
+- {
+- "Name": "QF1003",
+- "Doc": "Convert if/else-if chain to tagged switch\n\nA series of if/else-if checks comparing the same variable against\nvalues can be replaced with a tagged switch.\n\nBefore:\n\n if x == 1 || x == 2 {\n ...\n } else if x == 3 {\n ...\n } else {\n ...\n }\n\nAfter:\n\n switch x {\n case 1, 2:\n ...\n case 3:\n ...\n default:\n ...\n }\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1003",
+- "Default": true
+- },
+- {
+- "Name": "QF1004",
+- "Doc": "Use strings.ReplaceAll instead of strings.Replace with n == -1\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1004",
+- "Default": true
+- },
+- {
+- "Name": "QF1005",
+- "Doc": "Expand call to math.Pow\n\nSome uses of math.Pow can be simplified to basic multiplication.\n\nBefore:\n\n math.Pow(x, 2)\n\nAfter:\n\n x * x\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1005",
+- "Default": false
+- },
+- {
+- "Name": "QF1006",
+- "Doc": "Lift if+break into loop condition\n\nBefore:\n\n for {\n if done {\n break\n }\n ...\n }\n\nAfter:\n\n for !done {\n ...\n }\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1006",
+- "Default": false
+- },
+- {
+- "Name": "QF1007",
+- "Doc": "Merge conditional assignment into variable declaration\n\nBefore:\n\n x := false\n if someCondition {\n x = true\n }\n\nAfter:\n\n x := someCondition\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1007",
+- "Default": false
+- },
+- {
+- "Name": "QF1008",
+- "Doc": "Omit embedded fields from selector expression\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1008",
+- "Default": false
+- },
+- {
+- "Name": "QF1009",
+- "Doc": "Use time.Time.Equal instead of == operator\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1009",
+- "Default": true
+- },
+- {
+- "Name": "QF1010",
+- "Doc": "Convert slice of bytes to string when printing it\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1010",
+- "Default": true
+- },
+- {
+- "Name": "QF1011",
+- "Doc": "Omit redundant type from variable declaration\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#",
+- "Default": false
+- },
+- {
+- "Name": "QF1012",
+- "Doc": "Use fmt.Fprintf(x, ...) instead of x.Write(fmt.Sprintf(...))\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#QF1012",
+- "Default": true
+- },
+- {
+- "Name": "S1000",
+- "Doc": "Use plain channel send or receive instead of single-case select\n\nSelect statements with a single case can be replaced with a simple\nsend or receive.\n\nBefore:\n\n select {\n case x := \u003c-ch:\n fmt.Println(x)\n }\n\nAfter:\n\n x := \u003c-ch\n fmt.Println(x)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1000",
+- "Default": true
+- },
+- {
+- "Name": "S1001",
+- "Doc": "Replace for loop with call to copy\n\nUse copy() for copying elements from one slice to another. For\narrays of identical size, you can use simple assignment.\n\nBefore:\n\n for i, x := range src {\n dst[i] = x\n }\n\nAfter:\n\n copy(dst, src)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1001",
+- "Default": true
+- },
+- {
+- "Name": "S1002",
+- "Doc": "Omit comparison with boolean constant\n\nBefore:\n\n if x == true {}\n\nAfter:\n\n if x {}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1002",
+- "Default": false
+- },
+- {
+- "Name": "S1003",
+- "Doc": "Replace call to strings.Index with strings.Contains\n\nBefore:\n\n if strings.Index(x, y) != -1 {}\n\nAfter:\n\n if strings.Contains(x, y) {}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1003",
+- "Default": true
+- },
+- {
+- "Name": "S1004",
+- "Doc": "Replace call to bytes.Compare with bytes.Equal\n\nBefore:\n\n if bytes.Compare(x, y) == 0 {}\n\nAfter:\n\n if bytes.Equal(x, y) {}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1004",
+- "Default": true
+- },
+- {
+- "Name": "S1005",
+- "Doc": "Drop unnecessary use of the blank identifier\n\nIn many cases, assigning to the blank identifier is unnecessary.\n\nBefore:\n\n for _ = range s {}\n x, _ = someMap[key]\n _ = \u003c-ch\n\nAfter:\n\n for range s{}\n x = someMap[key]\n \u003c-ch\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1005",
+- "Default": false
+- },
+- {
+- "Name": "S1006",
+- "Doc": "Use 'for { ... }' for infinite loops\n\nFor infinite loops, using for { ... } is the most idiomatic choice.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1006",
+- "Default": false
+- },
+- {
+- "Name": "S1007",
+- "Doc": "Simplify regular expression by using raw string literal\n\nRaw string literals use backticks instead of quotation marks and do not support\nany escape sequences. This means that the backslash can be used\nfreely, without the need of escaping.\n\nSince regular expressions have their own escape sequences, raw strings\ncan improve their readability.\n\nBefore:\n\n regexp.Compile(\"\\\\A(\\\\w+) profile: total \\\\d+\\\\n\\\\z\")\n\nAfter:\n\n regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1007",
+- "Default": true
+- },
+- {
+- "Name": "S1008",
+- "Doc": "Simplify returning boolean expression\n\nBefore:\n\n if \u003cexpr\u003e {\n return true\n }\n return false\n\nAfter:\n\n return \u003cexpr\u003e\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1008",
+- "Default": false
+- },
+- {
+- "Name": "S1009",
+- "Doc": "Omit redundant nil check on slices, maps, and channels\n\nThe len function is defined for all slices, maps, and\nchannels, even nil ones, which have a length of zero. It is not necessary to\ncheck for nil before checking that their length is not zero.\n\nBefore:\n\n if x != nil \u0026\u0026 len(x) != 0 {}\n\nAfter:\n\n if len(x) != 0 {}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1009",
+- "Default": true
+- },
+- {
+- "Name": "S1010",
+- "Doc": "Omit default slice index\n\nWhen slicing, the second index defaults to the length of the value,\nmaking s[n:len(s)] and s[n:] equivalent.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1010",
+- "Default": true
+- },
+- {
+- "Name": "S1011",
+- "Doc": "Use a single append to concatenate two slices\n\nBefore:\n\n for _, e := range y {\n x = append(x, e)\n }\n \n for i := range y {\n x = append(x, y[i])\n }\n \n for i := range y {\n v := y[i]\n x = append(x, v)\n }\n\nAfter:\n\n x = append(x, y...)\n x = append(x, y...)\n x = append(x, y...)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1011",
+- "Default": false
+- },
+- {
+- "Name": "S1012",
+- "Doc": "Replace time.Now().Sub(x) with time.Since(x)\n\nThe time.Since helper has the same effect as using time.Now().Sub(x)\nbut is easier to read.\n\nBefore:\n\n time.Now().Sub(x)\n\nAfter:\n\n time.Since(x)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1012",
+- "Default": true
+- },
+- {
+- "Name": "S1016",
+- "Doc": "Use a type conversion instead of manually copying struct fields\n\nTwo struct types with identical fields can be converted between each\nother. In older versions of Go, the fields had to have identical\nstruct tags. Since Go 1.8, however, struct tags are ignored during\nconversions. It is thus not necessary to manually copy every field\nindividually.\n\nBefore:\n\n var x T1\n y := T2{\n Field1: x.Field1,\n Field2: x.Field2,\n }\n\nAfter:\n\n var x T1\n y := T2(x)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1016",
+- "Default": false
+- },
+- {
+- "Name": "S1017",
+- "Doc": "Replace manual trimming with strings.TrimPrefix\n\nInstead of using strings.HasPrefix and manual slicing, use the\nstrings.TrimPrefix function. If the string doesn't start with the\nprefix, the original string will be returned. Using strings.TrimPrefix\nreduces complexity, and avoids common bugs, such as off-by-one\nmistakes.\n\nBefore:\n\n if strings.HasPrefix(str, prefix) {\n str = str[len(prefix):]\n }\n\nAfter:\n\n str = strings.TrimPrefix(str, prefix)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1017",
+- "Default": true
+- },
+- {
+- "Name": "S1018",
+- "Doc": "Use 'copy' for sliding elements\n\ncopy() permits using the same source and destination slice, even with\noverlapping ranges. This makes it ideal for sliding elements in a\nslice.\n\nBefore:\n\n for i := 0; i \u003c n; i++ {\n bs[i] = bs[offset+i]\n }\n\nAfter:\n\n copy(bs[:n], bs[offset:])\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1018",
+- "Default": true
+- },
+- {
+- "Name": "S1019",
+- "Doc": "Simplify 'make' call by omitting redundant arguments\n\nThe 'make' function has default values for the length and capacity\narguments. For channels, the length defaults to zero, and for slices,\nthe capacity defaults to the length.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1019",
+- "Default": true
+- },
+- {
+- "Name": "S1020",
+- "Doc": "Omit redundant nil check in type assertion\n\nBefore:\n\n if _, ok := i.(T); ok \u0026\u0026 i != nil {}\n\nAfter:\n\n if _, ok := i.(T); ok {}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1020",
+- "Default": true
+- },
+- {
+- "Name": "S1021",
+- "Doc": "Merge variable declaration and assignment\n\nBefore:\n\n var x uint\n x = 1\n\nAfter:\n\n var x uint = 1\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1021",
+- "Default": false
+- },
+- {
+- "Name": "S1023",
+- "Doc": "Omit redundant control flow\n\nFunctions that have no return value do not need a return statement as\nthe final statement of the function.\n\nSwitches in Go do not have automatic fallthrough, unlike languages\nlike C. It is not necessary to have a break statement as the final\nstatement in a case block.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1023",
+- "Default": true
+- },
+- {
+- "Name": "S1024",
+- "Doc": "Replace x.Sub(time.Now()) with time.Until(x)\n\nThe time.Until helper has the same effect as using x.Sub(time.Now())\nbut is easier to read.\n\nBefore:\n\n x.Sub(time.Now())\n\nAfter:\n\n time.Until(x)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1024",
+- "Default": true
+- },
+- {
+- "Name": "S1025",
+- "Doc": "Don't use fmt.Sprintf(\"%s\", x) unnecessarily\n\nIn many instances, there are easier and more efficient ways of getting\na value's string representation. Whenever a value's underlying type is\na string already, or the type has a String method, they should be used\ndirectly.\n\nGiven the following shared definitions\n\n type T1 string\n type T2 int\n\n func (T2) String() string { return \"Hello, world\" }\n\n var x string\n var y T1\n var z T2\n\nwe can simplify\n\n fmt.Sprintf(\"%s\", x)\n fmt.Sprintf(\"%s\", y)\n fmt.Sprintf(\"%s\", z)\n\nto\n\n x\n string(y)\n z.String()\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1025",
+- "Default": false
+- },
+- {
+- "Name": "S1028",
+- "Doc": "Simplify error construction with fmt.Errorf\n\nBefore:\n\n errors.New(fmt.Sprintf(...))\n\nAfter:\n\n fmt.Errorf(...)\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1028",
+- "Default": true
+- },
+- {
+- "Name": "S1029",
+- "Doc": "Range over the string directly\n\nRanging over a string will yield byte offsets and runes. If the offset\nisn't used, this is functionally equivalent to converting the string\nto a slice of runes and ranging over that. Ranging directly over the\nstring will be more performant, however, as it avoids allocating a new\nslice, the size of which depends on the length of the string.\n\nBefore:\n\n for _, r := range []rune(s) {}\n\nAfter:\n\n for _, r := range s {}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1029",
+- "Default": false
+- },
+- {
+- "Name": "S1030",
+- "Doc": "Use bytes.Buffer.String or bytes.Buffer.Bytes\n\nbytes.Buffer has both a String and a Bytes method. It is almost never\nnecessary to use string(buf.Bytes()) or []byte(buf.String()) – simply\nuse the other method.\n\nThe only exception to this are map lookups. Due to a compiler optimization,\nm[string(buf.Bytes())] is more efficient than m[buf.String()].\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1030",
+- "Default": true
+- },
+- {
+- "Name": "S1031",
+- "Doc": "Omit redundant nil check around loop\n\nYou can use range on nil slices and maps, the loop will simply never\nexecute. This makes an additional nil check around the loop\nunnecessary.\n\nBefore:\n\n if s != nil {\n for _, x := range s {\n ...\n }\n }\n\nAfter:\n\n for _, x := range s {\n ...\n }\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1031",
+- "Default": true
+- },
+- {
+- "Name": "S1032",
+- "Doc": "Use sort.Ints(x), sort.Float64s(x), and sort.Strings(x)\n\nThe sort.Ints, sort.Float64s and sort.Strings functions are easier to\nread than sort.Sort(sort.IntSlice(x)), sort.Sort(sort.Float64Slice(x))\nand sort.Sort(sort.StringSlice(x)).\n\nBefore:\n\n sort.Sort(sort.StringSlice(x))\n\nAfter:\n\n sort.Strings(x)\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1032",
+- "Default": true
+- },
+- {
+- "Name": "S1033",
+- "Doc": "Unnecessary guard around call to 'delete'\n\nCalling delete on a nil map is a no-op.\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1033",
+- "Default": true
+- },
+- {
+- "Name": "S1034",
+- "Doc": "Use result of type assertion to simplify cases\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1034",
+- "Default": true
+- },
+- {
+- "Name": "S1035",
+- "Doc": "Redundant call to net/http.CanonicalHeaderKey in method call on net/http.Header\n\nThe methods on net/http.Header, namely Add, Del, Get\nand Set, already canonicalize the given header name.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1035",
+- "Default": true
+- },
+- {
+- "Name": "S1036",
+- "Doc": "Unnecessary guard around map access\n\nWhen accessing a map key that doesn't exist yet, one receives a zero\nvalue. Often, the zero value is a suitable value, for example when\nusing append or doing integer math.\n\nThe following\n\n if _, ok := m[\"foo\"]; ok {\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n } else {\n m[\"foo\"] = []string{\"bar\"}\n }\n\ncan be simplified to\n\n m[\"foo\"] = append(m[\"foo\"], \"bar\")\n\nand\n\n if _, ok := m2[\"k\"]; ok {\n m2[\"k\"] += 4\n } else {\n m2[\"k\"] = 4\n }\n\ncan be simplified to\n\n m[\"k\"] += 4\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1036",
+- "Default": true
+- },
+- {
+- "Name": "S1037",
+- "Doc": "Elaborate way of sleeping\n\nUsing a select statement with a single case receiving\nfrom the result of time.After is a very elaborate way of sleeping that\ncan much simpler be expressed with a simple call to time.Sleep.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1037",
+- "Default": true
+- },
+- {
+- "Name": "S1038",
+- "Doc": "Unnecessarily complex way of printing formatted string\n\nInstead of using fmt.Print(fmt.Sprintf(...)), one can use fmt.Printf(...).\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1038",
+- "Default": true
+- },
+- {
+- "Name": "S1039",
+- "Doc": "Unnecessary use of fmt.Sprint\n\nCalling fmt.Sprint with a single string argument is unnecessary\nand identical to using the string directly.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1039",
+- "Default": true
+- },
+- {
+- "Name": "S1040",
+- "Doc": "Type assertion to current type\n\nThe type assertion x.(SomeInterface), when x already has type\nSomeInterface, can only fail if x is nil. Usually, this is\nleft-over code from when x had a different type and you can safely\ndelete the type assertion. If you want to check that x is not nil,\nconsider being explicit and using an actual if x == nil comparison\ninstead of relying on the type assertion panicking.\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#S1040",
+- "Default": true
+- },
+- {
+- "Name": "SA1000",
+- "Doc": "Invalid regular expression\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1000",
+- "Default": false
+- },
+- {
+- "Name": "SA1001",
+- "Doc": "Invalid template\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1001",
+- "Default": true
+- },
+- {
+- "Name": "SA1002",
+- "Doc": "Invalid format in time.Parse\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1002",
+- "Default": false
+- },
+- {
+- "Name": "SA1003",
+- "Doc": "Unsupported argument to functions in encoding/binary\n\nThe encoding/binary package can only serialize types with known sizes.\nThis precludes the use of the int and uint types, as their sizes\ndiffer on different architectures. Furthermore, it doesn't support\nserializing maps, channels, strings, or functions.\n\nBefore Go 1.8, bool wasn't supported, either.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1003",
+- "Default": false
+- },
+- {
+- "Name": "SA1004",
+- "Doc": "Suspiciously small untyped constant in time.Sleep\n\nThe time.Sleep function takes a time.Duration as its only argument.\nDurations are expressed in nanoseconds. Thus, calling time.Sleep(1)\nwill sleep for 1 nanosecond. This is a common source of bugs, as sleep\nfunctions in other languages often accept seconds or milliseconds.\n\nThe time package provides constants such as time.Second to express\nlarge durations. These can be combined with arithmetic to express\narbitrary durations, for example 5 * time.Second for 5 seconds.\n\nIf you truly meant to sleep for a tiny amount of time, use\nn * time.Nanosecond to signal to Staticcheck that you did mean to sleep\nfor some amount of nanoseconds.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1004",
+- "Default": true
+- },
+- {
+- "Name": "SA1005",
+- "Doc": "Invalid first argument to exec.Command\n\nos/exec runs programs directly (using variants of the fork and exec\nsystem calls on Unix systems). This shouldn't be confused with running\na command in a shell. The shell will allow for features such as input\nredirection, pipes, and general scripting. The shell is also\nresponsible for splitting the user's input into a program name and its\narguments. For example, the equivalent to\n\n ls / /tmp\n\nwould be\n\n exec.Command(\"ls\", \"/\", \"/tmp\")\n\nIf you want to run a command in a shell, consider using something like\nthe following – but be aware that not all systems, particularly\nWindows, will have a /bin/sh program:\n\n exec.Command(\"/bin/sh\", \"-c\", \"ls | grep Awesome\")\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1005",
+- "Default": true
+- },
+- {
+- "Name": "SA1007",
+- "Doc": "Invalid URL in net/url.Parse\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1007",
+- "Default": false
+- },
+- {
+- "Name": "SA1008",
+- "Doc": "Non-canonical key in http.Header map\n\nKeys in http.Header maps are canonical, meaning they follow a specific\ncombination of uppercase and lowercase letters. Methods such as\nhttp.Header.Add and http.Header.Del convert inputs into this canonical\nform before manipulating the map.\n\nWhen manipulating http.Header maps directly, as opposed to using the\nprovided methods, care should be taken to stick to canonical form in\norder to avoid inconsistencies. The following piece of code\ndemonstrates one such inconsistency:\n\n h := http.Header{}\n h[\"etag\"] = []string{\"1234\"}\n h.Add(\"etag\", \"5678\")\n fmt.Println(h)\n\n // Output:\n // map[Etag:[5678] etag:[1234]]\n\nThe easiest way of obtaining the canonical form of a key is to use\nhttp.CanonicalHeaderKey.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1008",
+- "Default": true
+- },
+- {
+- "Name": "SA1010",
+- "Doc": "(*regexp.Regexp).FindAll called with n == 0, which will always return zero results\n\nIf n \u003e= 0, the function returns at most n matches/submatches. To\nreturn all results, specify a negative number.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1010",
+- "Default": false
+- },
+- {
+- "Name": "SA1011",
+- "Doc": "Various methods in the 'strings' package expect valid UTF-8, but invalid input is provided\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1011",
+- "Default": false
+- },
+- {
+- "Name": "SA1012",
+- "Doc": "A nil context.Context is being passed to a function, consider using context.TODO instead\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1012",
+- "Default": true
+- },
+- {
+- "Name": "SA1013",
+- "Doc": "io.Seeker.Seek is being called with the whence constant as the first argument, but it should be the second\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1013",
+- "Default": true
+- },
+- {
+- "Name": "SA1014",
+- "Doc": "Non-pointer value passed to Unmarshal or Decode\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1014",
+- "Default": false
+- },
+- {
+- "Name": "SA1015",
+- "Doc": "Using time.Tick in a way that will leak. Consider using time.NewTicker, and only use time.Tick in tests, commands and endless functions\n\nBefore Go 1.23, time.Tickers had to be closed to be able to be garbage\ncollected. Since time.Tick doesn't make it possible to close the underlying\nticker, using it repeatedly would leak memory.\n\nGo 1.23 fixes this by allowing tickers to be collected even if they weren't closed.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1015",
+- "Default": false
+- },
+- {
+- "Name": "SA1016",
+- "Doc": "Trapping a signal that cannot be trapped\n\nNot all signals can be intercepted by a process. Specifically, on\nUNIX-like systems, the syscall.SIGKILL and syscall.SIGSTOP signals are\nnever passed to the process, but instead handled directly by the\nkernel. It is therefore pointless to try and handle these signals.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1016",
+- "Default": true
+- },
+- {
+- "Name": "SA1017",
+- "Doc": "Channels used with os/signal.Notify should be buffered\n\nThe os/signal package uses non-blocking channel sends when delivering\nsignals. If the receiving end of the channel isn't ready and the\nchannel is either unbuffered or full, the signal will be dropped. To\navoid missing signals, the channel should be buffered and of the\nappropriate size. For a channel used for notification of just one\nsignal value, a buffer of size 1 is sufficient.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1017",
+- "Default": false
+- },
+- {
+- "Name": "SA1018",
+- "Doc": "strings.Replace called with n == 0, which does nothing\n\nWith n == 0, zero instances will be replaced. To replace all\ninstances, use a negative number, or use strings.ReplaceAll.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1018",
+- "Default": false
+- },
+- {
+- "Name": "SA1020",
+- "Doc": "Using an invalid host:port pair with a net.Listen-related function\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1020",
+- "Default": false
+- },
+- {
+- "Name": "SA1021",
+- "Doc": "Using bytes.Equal to compare two net.IP\n\nA net.IP stores an IPv4 or IPv6 address as a slice of bytes. The\nlength of the slice for an IPv4 address, however, can be either 4 or\n16 bytes long, using different ways of representing IPv4 addresses. In\norder to correctly compare two net.IPs, the net.IP.Equal method should\nbe used, as it takes both representations into account.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1021",
+- "Default": false
+- },
+- {
+- "Name": "SA1023",
+- "Doc": "Modifying the buffer in an io.Writer implementation\n\nWrite must not modify the slice data, even temporarily.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1023",
+- "Default": false
+- },
+- {
+- "Name": "SA1024",
+- "Doc": "A string cutset contains duplicate characters\n\nThe strings.TrimLeft and strings.TrimRight functions take cutsets, not\nprefixes. A cutset is treated as a set of characters to remove from a\nstring. For example,\n\n strings.TrimLeft(\"42133word\", \"1234\")\n\nwill result in the string \"word\" – any characters that are 1, 2, 3 or\n4 are cut from the left of the string.\n\nIn order to remove one string from another, use strings.TrimPrefix instead.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1024",
+- "Default": false
+- },
+- {
+- "Name": "SA1025",
+- "Doc": "It is not possible to use (*time.Timer).Reset's return value correctly\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1025",
+- "Default": false
+- },
+- {
+- "Name": "SA1026",
+- "Doc": "Cannot marshal channels or functions\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1026",
+- "Default": false
+- },
+- {
+- "Name": "SA1027",
+- "Doc": "Atomic access to 64-bit variable must be 64-bit aligned\n\nOn ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to\narrange for 64-bit alignment of 64-bit words accessed atomically. The\nfirst word in a variable or in an allocated struct, array, or slice\ncan be relied upon to be 64-bit aligned.\n\nYou can use the structlayout tool to inspect the alignment of fields\nin a struct.\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1027",
+- "Default": false
+- },
+- {
+- "Name": "SA1028",
+- "Doc": "sort.Slice can only be used on slices\n\nThe first argument of sort.Slice must be a slice.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1028",
+- "Default": false
+- },
+- {
+- "Name": "SA1029",
+- "Doc": "Inappropriate key in call to context.WithValue\n\nThe provided key must be comparable and should not be\nof type string or any other built-in type to avoid collisions between\npackages using context. Users of WithValue should define their own\ntypes for keys.\n\nTo avoid allocating when assigning to an interface{},\ncontext keys often have concrete type struct{}. Alternatively,\nexported context key variables' static type should be a pointer or\ninterface.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1029",
+- "Default": false
+- },
+- {
+- "Name": "SA1030",
+- "Doc": "Invalid argument in call to a strconv function\n\nThis check validates the format, number base and bit size arguments of\nthe various parsing and formatting functions in strconv.\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1030",
+- "Default": false
+- },
+- {
+- "Name": "SA1031",
+- "Doc": "Overlapping byte slices passed to an encoder\n\nIn an encoding function of the form Encode(dst, src), dst and\nsrc were found to reference the same memory. This can result in\nsrc bytes being overwritten before they are read, when the encoder\nwrites more than one byte per src byte.\n\nAvailable since\n 2024.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1031",
+- "Default": false
+- },
+- {
+- "Name": "SA1032",
+- "Doc": "Wrong order of arguments to errors.Is\n\nThe first argument of the function errors.Is is the error\nthat we have and the second argument is the error we're trying to match against.\nFor example:\n\n\tif errors.Is(err, io.EOF) { ... }\n\nThis check detects some cases where the two arguments have been swapped. It\nflags any calls where the first argument is referring to a package-level error\nvariable, such as\n\n\tif errors.Is(io.EOF, err) { /* this is wrong */ }\n\nAvailable since\n 2024.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA1032",
+- "Default": false
+- },
+- {
+- "Name": "SA2001",
+- "Doc": "Empty critical section, did you mean to defer the unlock?\n\nEmpty critical sections of the kind\n\n mu.Lock()\n mu.Unlock()\n\nare very often a typo, and the following was intended instead:\n\n mu.Lock()\n defer mu.Unlock()\n\nDo note that sometimes empty critical sections can be useful, as a\nform of signaling to wait on another goroutine. Many times, there are\nsimpler ways of achieving the same effect. When that isn't the case,\nthe code should be amply commented to avoid confusion. Combining such\ncomments with a //lint:ignore directive can be used to suppress this\nrare false positive.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA2001",
+- "Default": true
+- },
+- {
+- "Name": "SA2002",
+- "Doc": "Called testing.T.FailNow or SkipNow in a goroutine, which isn't allowed\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA2002",
+- "Default": false
+- },
+- {
+- "Name": "SA2003",
+- "Doc": "Deferred Lock right after locking, likely meant to defer Unlock instead\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA2003",
+- "Default": false
+- },
+- {
+- "Name": "SA3000",
+- "Doc": "TestMain doesn't call os.Exit, hiding test failures\n\nTest executables (and in turn 'go test') exit with a non-zero status\ncode if any tests failed. When specifying your own TestMain function,\nit is your responsibility to arrange for this, by calling os.Exit with\nthe correct code. The correct code is returned by (*testing.M).Run, so\nthe usual way of implementing TestMain is to end it with\nos.Exit(m.Run()).\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA3000",
+- "Default": true
+- },
+- {
+- "Name": "SA3001",
+- "Doc": "Assigning to b.N in benchmarks distorts the results\n\nThe testing package dynamically sets b.N to improve the reliability of\nbenchmarks and uses it in computations to determine the duration of a\nsingle operation. Benchmark code must not alter b.N as this would\nfalsify results.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA3001",
+- "Default": true
+- },
+- {
+- "Name": "SA4000",
+- "Doc": "Binary operator has identical expressions on both sides\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4000",
+- "Default": true
+- },
+- {
+- "Name": "SA4001",
+- "Doc": "\u0026*x gets simplified to x, it does not copy x\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4001",
+- "Default": true
+- },
+- {
+- "Name": "SA4003",
+- "Doc": "Comparing unsigned values against negative values is pointless\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4003",
+- "Default": true
+- },
+- {
+- "Name": "SA4004",
+- "Doc": "The loop exits unconditionally after one iteration\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4004",
+- "Default": true
+- },
+- {
+- "Name": "SA4005",
+- "Doc": "Field assignment that will never be observed. Did you mean to use a pointer receiver?\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4005",
+- "Default": false
+- },
+- {
+- "Name": "SA4006",
+- "Doc": "A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4006",
+- "Default": false
+- },
+- {
+- "Name": "SA4008",
+- "Doc": "The variable in the loop condition never changes, are you incrementing the wrong variable?\n\nFor example:\n\n\tfor i := 0; i \u003c 10; j++ { ... }\n\nThis may also occur when a loop can only execute once because of unconditional\ncontrol flow that terminates the loop. For example, when a loop body contains an\nunconditional break, return, or panic:\n\n\tfunc f() {\n\t\tpanic(\"oops\")\n\t}\n\tfunc g() {\n\t\tfor i := 0; i \u003c 10; i++ {\n\t\t\t// f unconditionally calls panic, which means \"i\" is\n\t\t\t// never incremented.\n\t\t\tf()\n\t\t}\n\t}\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4008",
+- "Default": false
+- },
+- {
+- "Name": "SA4009",
+- "Doc": "A function argument is overwritten before its first use\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4009",
+- "Default": false
+- },
+- {
+- "Name": "SA4010",
+- "Doc": "The result of append will never be observed anywhere\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4010",
+- "Default": false
+- },
+- {
+- "Name": "SA4011",
+- "Doc": "Break statement with no effect. Did you mean to break out of an outer loop?\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4011",
+- "Default": true
+- },
+- {
+- "Name": "SA4012",
+- "Doc": "Comparing a value against NaN even though no value is equal to NaN\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4012",
+- "Default": false
+- },
+- {
+- "Name": "SA4013",
+- "Doc": "Negating a boolean twice (!!b) is the same as writing b. This is either redundant, or a typo.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4013",
+- "Default": true
+- },
+- {
+- "Name": "SA4014",
+- "Doc": "An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4014",
+- "Default": true
+- },
+- {
+- "Name": "SA4015",
+- "Doc": "Calling functions like math.Ceil on floats converted from integers doesn't do anything useful\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4015",
+- "Default": false
+- },
+- {
+- "Name": "SA4016",
+- "Doc": "Certain bitwise operations, such as x ^ 0, do not do anything useful\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4016",
+- "Default": true
+- },
+- {
+- "Name": "SA4017",
+- "Doc": "Discarding the return values of a function without side effects, making the call pointless\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4017",
+- "Default": false
+- },
+- {
+- "Name": "SA4018",
+- "Doc": "Self-assignment of variables\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4018",
+- "Default": false
+- },
+- {
+- "Name": "SA4019",
+- "Doc": "Multiple, identical build constraints in the same file\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4019",
+- "Default": true
+- },
+- {
+- "Name": "SA4020",
+- "Doc": "Unreachable case clause in a type switch\n\nIn a type switch like the following\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n\n var v any = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case T:\n // unreachable\n }\n\nthe second case clause can never be reached because T implements\nio.Reader and case clauses are evaluated in source order.\n\nAnother example:\n\n type T struct{}\n func (T) Read(b []byte) (int, error) { return 0, nil }\n func (T) Close() error { return nil }\n\n var v any = T{}\n\n switch v.(type) {\n case io.Reader:\n // ...\n case io.ReadCloser:\n // unreachable\n }\n\nEven though T has a Close method and thus implements io.ReadCloser,\nio.Reader will always match first. The method set of io.Reader is a\nsubset of io.ReadCloser. Thus it is impossible to match the second\ncase without matching the first case.\n\n\nStructurally equivalent interfaces\n\nA special case of the previous example are structurally identical\ninterfaces. Given these declarations\n\n type T error\n type V error\n\n func doSomething() error {\n err, ok := doAnotherThing()\n if ok {\n return T(err)\n }\n\n return U(err)\n }\n\nthe following type switch will have an unreachable case clause:\n\n switch doSomething().(type) {\n case T:\n // ...\n case V:\n // unreachable\n }\n\nT will always match before V because they are structurally equivalent\nand therefore doSomething()'s return value implements both.\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4020",
+- "Default": true
+- },
+- {
+- "Name": "SA4022",
+- "Doc": "Comparing the address of a variable against nil\n\nCode such as 'if \u0026x == nil' is meaningless, because taking the address of a variable always yields a non-nil pointer.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4022",
+- "Default": true
+- },
+- {
+- "Name": "SA4023",
+- "Doc": "Impossible comparison of interface value with untyped nil\n\nUnder the covers, interfaces are implemented as two elements, a\ntype T and a value V. V is a concrete value such as an int,\nstruct or pointer, never an interface itself, and has type T. For\ninstance, if we store the int value 3 in an interface, the\nresulting interface value has, schematically, (T=int, V=3). The\nvalue V is also known as the interface's dynamic value, since a\ngiven interface variable might hold different values V (and\ncorresponding types T) during the execution of the program.\n\nAn interface value is nil only if the V and T are both\nunset, (T=nil, V is not set), In particular, a nil interface will\nalways hold a nil type. If we store a nil pointer of type *int\ninside an interface value, the inner type will be *int regardless\nof the value of the pointer: (T=*int, V=nil). Such an interface\nvalue will therefore be non-nil even when the pointer value V\ninside is nil.\n\nThis situation can be confusing, and arises when a nil value is\nstored inside an interface value such as an error return:\n\n func returnsError() error {\n var p *MyError = nil\n if bad() {\n p = ErrBad\n }\n return p // Will always return a non-nil error.\n }\n\nIf all goes well, the function returns a nil p, so the return\nvalue is an error interface value holding (T=*MyError, V=nil).\nThis means that if the caller compares the returned error to nil,\nit will always look as if there was an error even if nothing bad\nhappened. To return a proper nil error to the caller, the\nfunction must return an explicit nil:\n\n func returnsError() error {\n if bad() {\n return ErrBad\n }\n return nil\n }\n\nIt's a good idea for functions that return errors always to use\nthe error type in their signature (as we did above) rather than a\nconcrete type such as *MyError, to help guarantee the error is\ncreated correctly. As an example, os.Open returns an error even\nthough, if not nil, it's always of concrete type *os.PathError.\n\nSimilar situations to those described here can arise whenever\ninterfaces are used. Just keep in mind that if any concrete value\nhas been stored in the interface, the interface will not be nil.\nFor more information, see The Laws of\nReflection at https://golang.org/doc/articles/laws_of_reflection.html.\n\nThis text has been copied from\nhttps://golang.org/doc/faq#nil_error, licensed under the Creative\nCommons Attribution 3.0 License.\n\nAvailable since\n 2020.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4023",
+- "Default": false
+- },
+- {
+- "Name": "SA4024",
+- "Doc": "Checking for impossible return value from a builtin function\n\nReturn values of the len and cap builtins cannot be negative.\n\nSee https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap.\n\nExample:\n\n if len(slice) \u003c 0 {\n fmt.Println(\"unreachable code\")\n }\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4024",
+- "Default": true
+- },
+- {
+- "Name": "SA4025",
+- "Doc": "Integer division of literals that results in zero\n\nWhen dividing two integer constants, the result will\nalso be an integer. Thus, a division such as 2 / 3 results in 0.\nThis is true for all of the following examples:\n\n\t_ = 2 / 3\n\tconst _ = 2 / 3\n\tconst _ float64 = 2 / 3\n\t_ = float64(2 / 3)\n\nStaticcheck will flag such divisions if both sides of the division are\ninteger literals, as it is highly unlikely that the division was\nintended to truncate to zero. Staticcheck will not flag integer\ndivision involving named constants, to avoid noisy positives.\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4025",
+- "Default": true
+- },
+- {
+- "Name": "SA4026",
+- "Doc": "Go constants cannot express negative zero\n\nIn IEEE 754 floating point math, zero has a sign and can be positive\nor negative. This can be useful in certain numerical code.\n\nGo constants, however, cannot express negative zero. This means that\nthe literals -0.0 and 0.0 have the same ideal value (zero) and\nwill both represent positive zero at runtime.\n\nTo explicitly and reliably create a negative zero, you can use the\nmath.Copysign function: math.Copysign(0, -1).\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4026",
+- "Default": true
+- },
+- {
+- "Name": "SA4027",
+- "Doc": "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL\n\n(*net/url.URL).Query parses the current value of net/url.URL.RawQuery\nand returns it as a map of type net/url.Values. Subsequent changes to\nthis map will not affect the URL unless the map gets encoded and\nassigned to the URL's RawQuery.\n\nAs a consequence, the following code pattern is an expensive no-op:\nu.Query().Add(key, value).\n\nAvailable since\n 2021.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4027",
+- "Default": true
+- },
+- {
+- "Name": "SA4028",
+- "Doc": "x % 1 is always zero\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4028",
+- "Default": true
+- },
+- {
+- "Name": "SA4029",
+- "Doc": "Ineffective attempt at sorting slice\n\nsort.Float64Slice, sort.IntSlice, and sort.StringSlice are\ntypes, not functions. Doing x = sort.StringSlice(x) does nothing,\nespecially not sort any values. The correct usage is\nsort.Sort(sort.StringSlice(x)) or sort.StringSlice(x).Sort(),\nbut there are more convenient helpers, namely sort.Float64s,\nsort.Ints, and sort.Strings.\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4029",
+- "Default": true
+- },
+- {
+- "Name": "SA4030",
+- "Doc": "Ineffective attempt at generating random number\n\nFunctions in the math/rand package that accept upper limits, such\nas Intn, generate random numbers in the half-open interval [0,n). In\nother words, the generated numbers will be \u003e= 0 and \u003c n – they\ndon't include n. rand.Intn(1) therefore doesn't generate 0\nor 1, it always generates 0.\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4030",
+- "Default": true
+- },
+- {
+- "Name": "SA4031",
+- "Doc": "Checking never-nil value against nil\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4031",
+- "Default": false
+- },
+- {
+- "Name": "SA4032",
+- "Doc": "Comparing runtime.GOOS or runtime.GOARCH against impossible value\n\nAvailable since\n 2024.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA4032",
+- "Default": true
+- },
+- {
+- "Name": "SA5000",
+- "Doc": "Assignment to nil map\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5000",
+- "Default": false
+- },
+- {
+- "Name": "SA5001",
+- "Doc": "Deferring Close before checking for a possible error\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5001",
+- "Default": true
+- },
+- {
+- "Name": "SA5002",
+- "Doc": "The empty for loop ('for {}') spins and can block the scheduler\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5002",
+- "Default": false
+- },
+- {
+- "Name": "SA5003",
+- "Doc": "Defers in infinite loops will never execute\n\nDefers are scoped to the surrounding function, not the surrounding\nblock. In a function that never returns, i.e. one containing an\ninfinite loop, defers will never execute.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5003",
+- "Default": true
+- },
+- {
+- "Name": "SA5004",
+- "Doc": "'for { select { ...' with an empty default branch spins\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5004",
+- "Default": true
+- },
+- {
+- "Name": "SA5005",
+- "Doc": "The finalizer references the finalized object, preventing garbage collection\n\nA finalizer is a function associated with an object that runs when the\ngarbage collector is ready to collect said object, that is when the\nobject is no longer referenced by anything.\n\nIf the finalizer references the object, however, it will always remain\nas the final reference to that object, preventing the garbage\ncollector from collecting the object. The finalizer will never run,\nand the object will never be collected, leading to a memory leak. That\nis why the finalizer should instead use its first argument to operate\non the object. That way, the number of references can temporarily go\nto zero before the object is being passed to the finalizer.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5005",
+- "Default": false
+- },
+- {
+- "Name": "SA5007",
+- "Doc": "Infinite recursive call\n\nA function that calls itself recursively needs to have an exit\ncondition. Otherwise it will recurse forever, until the system runs\nout of memory.\n\nThis issue can be caused by simple bugs such as forgetting to add an\nexit condition. It can also happen \"on purpose\". Some languages have\ntail call optimization which makes certain infinite recursive calls\nsafe to use. Go, however, does not implement TCO, and as such a loop\nshould be used instead.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5007",
+- "Default": false
+- },
+- {
+- "Name": "SA5008",
+- "Doc": "Invalid struct tag\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5008",
+- "Default": true
+- },
+- {
+- "Name": "SA5010",
+- "Doc": "Impossible type assertion\n\nSome type assertions can be statically proven to be\nimpossible. This is the case when the method sets of both\narguments of the type assertion conflict with each other, for\nexample by containing the same method with different\nsignatures.\n\nThe Go compiler already applies this check when asserting from an\ninterface value to a concrete type. If the concrete type misses\nmethods from the interface, or if function signatures don't match,\nthen the type assertion can never succeed.\n\nThis check applies the same logic when asserting from one interface to\nanother. If both interface types contain the same method but with\ndifferent signatures, then the type assertion can never succeed,\neither.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5010",
+- "Default": false
+- },
+- {
+- "Name": "SA5011",
+- "Doc": "Possible nil pointer dereference\n\nA pointer is being dereferenced unconditionally, while\nalso being checked against nil in another place. This suggests that\nthe pointer may be nil and dereferencing it may panic. This is\ncommonly a result of improperly ordered code or missing return\nstatements. Consider the following examples:\n\n func fn(x *int) {\n fmt.Println(*x)\n\n // This nil check is equally important for the previous dereference\n if x != nil {\n foo(*x)\n }\n }\n\n func TestFoo(t *testing.T) {\n x := compute()\n if x == nil {\n t.Errorf(\"nil pointer received\")\n }\n\n // t.Errorf does not abort the test, so if x is nil, the next line will panic.\n foo(*x)\n }\n\nStaticcheck tries to deduce which functions abort control flow.\nFor example, it is aware that a function will not continue\nexecution after a call to panic or log.Fatal. However, sometimes\nthis detection fails, in particular in the presence of\nconditionals. Consider the following example:\n\n func Log(msg string, level int) {\n fmt.Println(msg)\n if level == levelFatal {\n os.Exit(1)\n }\n }\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n }\n\n func fn(x *int) {\n if x == nil {\n Fatal(\"unexpected nil pointer\")\n }\n fmt.Println(*x)\n }\n\nStaticcheck will flag the dereference of x, even though it is perfectly\nsafe. Staticcheck is not able to deduce that a call to\nFatal will exit the program. For the time being, the easiest\nworkaround is to modify the definition of Fatal like so:\n\n func Fatal(msg string) {\n Log(msg, levelFatal)\n panic(\"unreachable\")\n }\n\nWe also hard-code functions from common logging packages such as\nlogrus. Please file an issue if we're missing support for a\npopular package.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5011",
+- "Default": false
+- },
+- {
+- "Name": "SA5012",
+- "Doc": "Passing odd-sized slice to function expecting even size\n\nSome functions that take slices as parameters expect the slices to have an even number of elements. \nOften, these functions treat elements in a slice as pairs. \nFor example, strings.NewReplacer takes pairs of old and new strings, \nand calling it with an odd number of elements would be an error.\n\nAvailable since\n 2020.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA5012",
+- "Default": false
+- },
+- {
+- "Name": "SA6000",
+- "Doc": "Using regexp.Match or related in a loop, should use regexp.Compile\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA6000",
+- "Default": false
+- },
+- {
+- "Name": "SA6001",
+- "Doc": "Missing an optimization opportunity when indexing maps by byte slices\n\nMap keys must be comparable, which precludes the use of byte slices.\nThis usually leads to using string keys and converting byte slices to\nstrings.\n\nNormally, a conversion of a byte slice to a string needs to copy the data and\ncauses allocations. The compiler, however, recognizes m[string(b)] and\nuses the data of b directly, without copying it, because it knows that\nthe data can't change during the map lookup. This leads to the\ncounter-intuitive situation that\n\n k := string(b)\n println(m[k])\n println(m[k])\n\nwill be less efficient than\n\n println(m[string(b)])\n println(m[string(b)])\n\nbecause the first version needs to copy and allocate, while the second\none does not.\n\nFor some history on this optimization, check out commit\nf5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA6001",
+- "Default": false
+- },
+- {
+- "Name": "SA6002",
+- "Doc": "Storing non-pointer values in sync.Pool allocates memory\n\nA sync.Pool is used to avoid unnecessary allocations and reduce the\namount of work the garbage collector has to do.\n\nWhen passing a value that is not a pointer to a function that accepts\nan interface, the value needs to be placed on the heap, which means an\nadditional allocation. Slices are a common thing to put in sync.Pools,\nand they're structs with 3 fields (length, capacity, and a pointer to\nan array). In order to avoid the extra allocation, one should store a\npointer to the slice instead.\n\nSee the comments on https://go-review.googlesource.com/c/go/+/24371\nthat discuss this problem.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA6002",
+- "Default": false
+- },
+- {
+- "Name": "SA6003",
+- "Doc": "Converting a string to a slice of runes before ranging over it\n\nYou may want to loop over the runes in a string. Instead of converting\nthe string to a slice of runes and looping over that, you can loop\nover the string itself. That is,\n\n for _, r := range s {}\n\nand\n\n for _, r := range []rune(s) {}\n\nwill yield the same values. The first version, however, will be faster\nand avoid unnecessary memory allocations.\n\nDo note that if you are interested in the indices, ranging over a\nstring and over a slice of runes will yield different indices. The\nfirst one yields byte offsets, while the second one yields indices in\nthe slice of runes.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA6003",
+- "Default": false
+- },
+- {
+- "Name": "SA6005",
+- "Doc": "Inefficient string comparison with strings.ToLower or strings.ToUpper\n\nConverting two strings to the same case and comparing them like so\n\n if strings.ToLower(s1) == strings.ToLower(s2) {\n ...\n }\n\nis significantly more expensive than comparing them with\nstrings.EqualFold(s1, s2). This is due to memory usage as well as\ncomputational complexity.\n\nstrings.ToLower will have to allocate memory for the new strings, as\nwell as convert both strings fully, even if they differ on the very\nfirst byte. strings.EqualFold, on the other hand, compares the strings\none character at a time. It doesn't need to create two intermediate\nstrings and can return as soon as the first non-matching character has\nbeen found.\n\nFor a more in-depth explanation of this issue, see\nhttps://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA6005",
+- "Default": true
+- },
+- {
+- "Name": "SA6006",
+- "Doc": "Using io.WriteString to write []byte\n\nUsing io.WriteString to write a slice of bytes, as in\n\n io.WriteString(w, string(b))\n\nis both unnecessary and inefficient. Converting from []byte to string\nhas to allocate and copy the data, and we could simply use w.Write(b)\ninstead.\n\nAvailable since\n 2024.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA6006",
+- "Default": true
+- },
+- {
+- "Name": "SA9001",
+- "Doc": "Defers in range loops may not run when you expect them to\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9001",
+- "Default": false
+- },
+- {
+- "Name": "SA9002",
+- "Doc": "Using a non-octal os.FileMode that looks like it was meant to be in octal.\n\nAvailable since\n 2017.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9002",
+- "Default": true
+- },
+- {
+- "Name": "SA9003",
+- "Doc": "Empty body in an if or else branch\n\nAvailable since\n 2017.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9003",
+- "Default": false
+- },
+- {
+- "Name": "SA9004",
+- "Doc": "Only the first constant has an explicit type\n\nIn a constant declaration such as the following:\n\n const (\n First byte = 1\n Second = 2\n )\n\nthe constant Second does not have the same type as the constant First.\nThis construct shouldn't be confused with\n\n const (\n First byte = iota\n Second\n )\n\nwhere First and Second do indeed have the same type. The type is only\npassed on when no explicit value is assigned to the constant.\n\nWhen declaring enumerations with explicit values it is therefore\nimportant not to write\n\n const (\n EnumFirst EnumType = 1\n EnumSecond = 2\n EnumThird = 3\n )\n\nThis discrepancy in types can cause various confusing behaviors and\nbugs.\n\n\nWrong type in variable declarations\n\nThe most obvious issue with such incorrect enumerations expresses\nitself as a compile error:\n\n package pkg\n\n const (\n EnumFirst uint8 = 1\n EnumSecond = 2\n )\n\n func fn(useFirst bool) {\n x := EnumSecond\n if useFirst {\n x = EnumFirst\n }\n }\n\nfails to compile with\n\n ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment\n\n\nLosing method sets\n\nA more subtle issue occurs with types that have methods and optional\ninterfaces. Consider the following:\n\n package main\n\n import \"fmt\"\n\n type Enum int\n\n func (e Enum) String() string {\n return \"an enum\"\n }\n\n const (\n EnumFirst Enum = 1\n EnumSecond = 2\n )\n\n func main() {\n fmt.Println(EnumFirst)\n fmt.Println(EnumSecond)\n }\n\nThis code will output\n\n an enum\n 2\n\nas EnumSecond has no explicit type, and thus defaults to int.\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9004",
+- "Default": true
+- },
+- {
+- "Name": "SA9005",
+- "Doc": "Trying to marshal a struct with no public fields nor custom marshaling\n\nThe encoding/json and encoding/xml packages only operate on exported\nfields in structs, not unexported ones. It is usually an error to try\nto (un)marshal structs that only consist of unexported fields.\n\nThis check will not flag calls involving types that define custom\nmarshaling behavior, e.g. via MarshalJSON methods. It will also not\nflag empty structs.\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9005",
+- "Default": false
+- },
+- {
+- "Name": "SA9006",
+- "Doc": "Dubious bit shifting of a fixed size integer value\n\nBit shifting a value past its size will always clear the value.\n\nFor instance:\n\n v := int8(42)\n v \u003e\u003e= 8\n\nwill always result in 0.\n\nThis check flags bit shifting operations on fixed size integer values only.\nThat is, int, uint and uintptr are never flagged to avoid potential false\npositives in somewhat exotic but valid bit twiddling tricks:\n\n // Clear any value above 32 bits if integers are more than 32 bits.\n func f(i int) int {\n v := i \u003e\u003e 32\n v = v \u003c\u003c 32\n return i-v\n }\n\nAvailable since\n 2020.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9006",
+- "Default": true
+- },
+- {
+- "Name": "SA9007",
+- "Doc": "Deleting a directory that shouldn't be deleted\n\nIt is virtually never correct to delete system directories such as\n/tmp or the user's home directory. However, it can be fairly easy to\ndo by mistake, for example by mistakenly using os.TempDir instead\nof ioutil.TempDir, or by forgetting to add a suffix to the result\nof os.UserHomeDir.\n\nWriting\n\n d := os.TempDir()\n defer os.RemoveAll(d)\n\nin your unit tests will have a devastating effect on the stability of your system.\n\nThis check flags attempts at deleting the following directories:\n\n- os.TempDir\n- os.UserCacheDir\n- os.UserConfigDir\n- os.UserHomeDir\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9007",
+- "Default": false
+- },
+- {
+- "Name": "SA9008",
+- "Doc": "else branch of a type assertion is probably not reading the right value\n\nWhen declaring variables as part of an if statement (like in 'if\nfoo := ...; foo {'), the same variables will also be in the scope of\nthe else branch. This means that in the following example\n\n if x, ok := x.(int); ok {\n // ...\n } else {\n fmt.Printf(\"unexpected type %T\", x)\n }\n\nx in the else branch will refer to the x from x, ok\n:=; it will not refer to the x that is being type-asserted. The\nresult of a failed type assertion is the zero value of the type that\nis being asserted to, so x in the else branch will always have the\nvalue 0 and the type int.\n\nAvailable since\n 2022.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9008",
+- "Default": false
+- },
+- {
+- "Name": "SA9009",
+- "Doc": "Ineffectual Go compiler directive\n\nA potential Go compiler directive was found, but is ineffectual as it begins\nwith whitespace.\n\nAvailable since\n 2024.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#SA9009",
+- "Default": true
+- },
+- {
+- "Name": "ST1000",
+- "Doc": "Incorrect or missing package comment\n\nPackages must have a package comment that is formatted according to\nthe guidelines laid out in\nhttps://go.dev/wiki/CodeReviewComments#package-comments.\n\nAvailable since\n 2019.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1000",
+- "Default": false
+- },
+- {
+- "Name": "ST1001",
+- "Doc": "Dot imports are discouraged\n\nDot imports that aren't in external test packages are discouraged.\n\nThe dot_import_whitelist option can be used to whitelist certain\nimports.\n\nQuoting Go Code Review Comments:\n\n\u003e The import . form can be useful in tests that, due to circular\n\u003e dependencies, cannot be made part of the package being tested:\n\u003e \n\u003e package foo_test\n\u003e \n\u003e import (\n\u003e \"bar/testutil\" // also imports \"foo\"\n\u003e . \"foo\"\n\u003e )\n\u003e \n\u003e In this case, the test file cannot be in package foo because it\n\u003e uses bar/testutil, which imports foo. So we use the import .\n\u003e form to let the file pretend to be part of package foo even though\n\u003e it is not. Except for this one case, do not use import . in your\n\u003e programs. It makes the programs much harder to read because it is\n\u003e unclear whether a name like Quux is a top-level identifier in the\n\u003e current package or in an imported package.\n\nAvailable since\n 2019.1\n\nOptions\n dot_import_whitelist\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1001",
+- "Default": false
+- },
+- {
+- "Name": "ST1003",
+- "Doc": "Poorly chosen identifier\n\nIdentifiers, such as variable and package names, follow certain rules.\n\nSee the following links for details:\n\n- https://go.dev/doc/effective_go#package-names\n- https://go.dev/doc/effective_go#mixed-caps\n- https://go.dev/wiki/CodeReviewComments#initialisms\n- https://go.dev/wiki/CodeReviewComments#variable-names\n\nAvailable since\n 2019.1, non-default\n\nOptions\n initialisms\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1003",
+- "Default": false
+- },
+- {
+- "Name": "ST1005",
+- "Doc": "Incorrectly formatted error string\n\nError strings follow a set of guidelines to ensure uniformity and good\ncomposability.\n\nQuoting Go Code Review Comments:\n\n\u003e Error strings should not be capitalized (unless beginning with\n\u003e proper nouns or acronyms) or end with punctuation, since they are\n\u003e usually printed following other context. That is, use\n\u003e fmt.Errorf(\"something bad\") not fmt.Errorf(\"Something bad\"), so\n\u003e that log.Printf(\"Reading %s: %v\", filename, err) formats without a\n\u003e spurious capital letter mid-message.\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1005",
+- "Default": false
+- },
+- {
+- "Name": "ST1006",
+- "Doc": "Poorly chosen receiver name\n\nQuoting Go Code Review Comments:\n\n\u003e The name of a method's receiver should be a reflection of its\n\u003e identity; often a one or two letter abbreviation of its type\n\u003e suffices (such as \"c\" or \"cl\" for \"Client\"). Don't use generic\n\u003e names such as \"me\", \"this\" or \"self\", identifiers typical of\n\u003e object-oriented languages that place more emphasis on methods as\n\u003e opposed to functions. The name need not be as descriptive as that\n\u003e of a method argument, as its role is obvious and serves no\n\u003e documentary purpose. It can be very short as it will appear on\n\u003e almost every line of every method of the type; familiarity admits\n\u003e brevity. Be consistent, too: if you call the receiver \"c\" in one\n\u003e method, don't call it \"cl\" in another.\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1006",
+- "Default": false
+- },
+- {
+- "Name": "ST1008",
+- "Doc": "A function's error value should be its last return value\n\nA function's error value should be its last return value.\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1008",
+- "Default": false
+- },
+- {
+- "Name": "ST1011",
+- "Doc": "Poorly chosen name for variable of type time.Duration\n\ntime.Duration values represent an amount of time, which is represented\nas a count of nanoseconds. An expression like 5 * time.Microsecond\nyields the value 5000. It is therefore not appropriate to suffix a\nvariable of type time.Duration with any time unit, such as Msec or\nMilli.\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1011",
+- "Default": false
+- },
+- {
+- "Name": "ST1012",
+- "Doc": "Poorly chosen name for error variable\n\nError variables that are part of an API should be called errFoo or\nErrFoo.\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1012",
+- "Default": false
+- },
+- {
+- "Name": "ST1013",
+- "Doc": "Should use constants for HTTP error codes, not magic numbers\n\nHTTP has a tremendous number of status codes. While some of those are\nwell known (200, 400, 404, 500), most of them are not. The net/http\npackage provides constants for all status codes that are part of the\nvarious specifications. It is recommended to use these constants\ninstead of hard-coding magic numbers, to vastly improve the\nreadability of your code.\n\nAvailable since\n 2019.1\n\nOptions\n http_status_code_whitelist\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1013",
+- "Default": false
+- },
+- {
+- "Name": "ST1015",
+- "Doc": "A switch's default case should be the first or last case\n\nAvailable since\n 2019.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1015",
+- "Default": false
+- },
+- {
+- "Name": "ST1016",
+- "Doc": "Use consistent method receiver names\n\nAvailable since\n 2019.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1016",
+- "Default": false
+- },
+- {
+- "Name": "ST1017",
+- "Doc": "Don't use Yoda conditions\n\nYoda conditions are conditions of the kind 'if 42 == x', where the\nliteral is on the left side of the comparison. These are a common\nidiom in languages in which assignment is an expression, to avoid bugs\nof the kind 'if (x = 42)'. In Go, which doesn't allow for this kind of\nbug, we prefer the more idiomatic 'if x == 42'.\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1017",
+- "Default": false
+- },
+- {
+- "Name": "ST1018",
+- "Doc": "Avoid zero-width and control characters in string literals\n\nAvailable since\n 2019.2\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1018",
+- "Default": false
+- },
+- {
+- "Name": "ST1019",
+- "Doc": "Importing the same package multiple times\n\nGo allows importing the same package multiple times, as long as\ndifferent import aliases are being used. That is, the following\nbit of code is valid:\n\n import (\n \"fmt\"\n fumpt \"fmt\"\n format \"fmt\"\n _ \"fmt\"\n )\n\nHowever, this is very rarely done on purpose. Usually, it is a\nsign of code that got refactored, accidentally adding duplicate\nimport statements. It is also a rarely known feature, which may\ncontribute to confusion.\n\nDo note that sometimes, this feature may be used\nintentionally (see for example\nhttps://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)\n– if you want to allow this pattern in your code base, you're\nadvised to disable this check.\n\nAvailable since\n 2020.1\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1019",
+- "Default": false
+- },
+- {
+- "Name": "ST1020",
+- "Doc": "The documentation of an exported function should start with the function's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1020",
+- "Default": false
+- },
+- {
+- "Name": "ST1021",
+- "Doc": "The documentation of an exported type should start with type's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1021",
+- "Default": false
+- },
+- {
+- "Name": "ST1022",
+- "Doc": "The documentation of an exported variable or constant should start with variable's name\n\nDoc comments work best as complete sentences, which\nallow a wide variety of automated presentations. The first sentence\nshould be a one-sentence summary that starts with the name being\ndeclared.\n\nIf every doc comment begins with the name of the item it describes,\nyou can use the doc subcommand of the go tool and run the output\nthrough grep.\n\nSee https://go.dev/doc/effective_go#commentary for more\ninformation on how to write good documentation.\n\nAvailable since\n 2020.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#ST1022",
+- "Default": false
+- },
+- {
+- "Name": "ST1023",
+- "Doc": "Redundant type in variable declaration\n\nAvailable since\n 2021.1, non-default\n",
+- "URL": "https://staticcheck.dev/docs/checks/#",
+- "Default": false
+- },
+- {
+- "Name": "any",
+- "Doc": "replace interface{} with any\n\nThe any analyzer suggests replacing uses of the empty interface type,\n`interface{}`, with the `any` alias, which was introduced in Go 1.18.\nThis is a purely stylistic change that makes code more readable.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any",
+- "Default": true
+- },
+- {
+- "Name": "appendclipped",
+- "Doc": "simplify append chains using slices.Concat\n\nThe appendclipped analyzer suggests replacing chains of append calls with a\nsingle call to slices.Concat, which was added in Go 1.21. For example,\nappend(append(s, s1...), s2...) would be simplified to slices.Concat(s, s1, s2).\n\nIn the simple case of appending to a newly allocated slice, such as\nappend([]T(nil), s...), the analyzer suggests the more concise slices.Clone(s).\nFor byte slices, it will prefer bytes.Clone if the \"bytes\" package is\nalready imported.\n\nThis fix is only applied when the base of the append tower is a\n\"clipped\" slice, meaning its length and capacity are equal (e.g.\nx[:0:0] or []T{}). This is to avoid changing program behavior by\neliminating intended side effects on the base slice's underlying\narray.\n\nThis analyzer is currently disabled by default as the\ntransformation does not preserve the nilness of the base slice in\nall cases; see https://go.dev/issue/73557.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped",
+- "Default": false
+- },
+- {
+- "Name": "appends",
+- "Doc": "check for missing values after append\n\nThis checker reports calls to append that pass\nno values to be appended to the slice.\n\n\ts := []string{\"a\", \"b\", \"c\"}\n\t_ = append(s)\n\nSuch calls are always no-ops and often indicate an\nunderlying mistake.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends",
+- "Default": true
+- },
+- {
+- "Name": "asmdecl",
+- "Doc": "report mismatches between assembly files and Go declarations",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/asmdecl",
+- "Default": true
+- },
+- {
+- "Name": "assign",
+- "Doc": "check for useless assignments\n\nThis checker reports assignments of the form x = x or a[i] = a[i].\nThese are almost always useless, and even when they aren't they are\nusually a mistake.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
+- "Default": true
+- },
+- {
+- "Name": "atomic",
+- "Doc": "check for common mistakes using the sync/atomic package\n\nThe atomic checker looks for assignment statements of the form:\n\n\tx = atomic.AddUint64(\u0026x, 1)\n\nwhich are not atomic.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic",
+- "Default": true
+- },
+- {
+- "Name": "atomicalign",
+- "Doc": "check for non-64-bits-aligned arguments to sync/atomic functions",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomicalign",
+- "Default": true
+- },
+- {
+- "Name": "bloop",
+- "Doc": "replace for-range over b.N with b.Loop\n\nThe bloop analyzer suggests replacing benchmark loops of the form\n`for i := 0; i \u003c b.N; i++` or `for range b.N` with the more modern\n`for b.Loop()`, which was added in Go 1.24.\n\nThis change makes benchmark code more readable and also removes the need for\nmanual timer control, so any preceding calls to b.StartTimer, b.StopTimer,\nor b.ResetTimer within the same function will also be removed.\n\nCaveats: The b.Loop() method is designed to prevent the compiler from\noptimizing away the benchmark loop, which can occasionally result in\nslower execution due to increased allocations in some specific cases.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#bloop",
+- "Default": true
+- },
+- {
+- "Name": "bools",
+- "Doc": "check for common mistakes involving boolean operators",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/bools",
+- "Default": true
+- },
+- {
+- "Name": "buildtag",
+- "Doc": "check //go:build and // +build directives",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildtag",
+- "Default": true
+- },
+- {
+- "Name": "cgocall",
+- "Doc": "detect some violations of the cgo pointer passing rules\n\nCheck for invalid cgo pointer passing.\nThis looks for code that uses cgo to call C code passing values\nwhose types are almost always invalid according to the cgo pointer\nsharing rules.\nSpecifically, it warns about attempts to pass a Go chan, map, func,\nor slice to C, either directly, or via a pointer, array, or struct.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/cgocall",
+- "Default": true
+- },
+- {
+- "Name": "composites",
+- "Doc": "check for unkeyed composite literals\n\nThis analyzer reports a diagnostic for composite literals of struct\ntypes imported from another package that do not use the field-keyed\nsyntax. Such literals are fragile because the addition of a new field\n(even if unexported) to the struct will cause compilation to fail.\n\nAs an example,\n\n\terr = \u0026net.DNSConfigError{err}\n\nshould be replaced by:\n\n\terr = \u0026net.DNSConfigError{Err: err}\n",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/composite",
+- "Default": true
+- },
+- {
+- "Name": "copylocks",
+- "Doc": "check for locks erroneously passed by value\n\nInadvertently copying a value containing a lock, such as sync.Mutex or\nsync.WaitGroup, may cause both copies to malfunction. Generally such\nvalues should be referred to through a pointer.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/copylock",
+- "Default": true
+- },
+- {
+- "Name": "deepequalerrors",
+- "Doc": "check for calls of reflect.DeepEqual on error values\n\nThe deepequalerrors checker looks for calls of the form:\n\n reflect.DeepEqual(err1, err2)\n\nwhere err1 and err2 are errors. Using reflect.DeepEqual to compare\nerrors is discouraged.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/deepequalerrors",
+- "Default": true
+- },
+- {
+- "Name": "defers",
+- "Doc": "report common mistakes in defer statements\n\nThe defers analyzer reports a diagnostic when a defer statement would\nresult in a non-deferred call to time.Since, as experience has shown\nthat this is nearly always a mistake.\n\nFor example:\n\n\tstart := time.Now()\n\t...\n\tdefer recordLatency(time.Since(start)) // error: call to time.Since is not deferred\n\nThe correct code is:\n\n\tdefer func() { recordLatency(time.Since(start)) }()",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers",
+- "Default": true
+- },
+- {
+- "Name": "deprecated",
+- "Doc": "check for use of deprecated identifiers\n\nThe deprecated analyzer looks for deprecated symbols and package\nimports.\n\nSee https://go.dev/wiki/Deprecated to learn about Go's convention\nfor documenting and signaling deprecated identifiers.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/deprecated",
+- "Default": true
+- },
+- {
+- "Name": "directive",
+- "Doc": "check Go toolchain directives such as //go:debug\n\nThis analyzer checks for problems with known Go toolchain directives\nin all Go source files in a package directory, even those excluded by\n//go:build constraints, and all non-Go source files too.\n\nFor //go:debug (see https://go.dev/doc/godebug), the analyzer checks\nthat the directives are placed only in Go source files, only above the\npackage comment, and only in package main or *_test.go files.\n\nSupport for other known directives may be added in the future.\n\nThis analyzer does not check //go:build, which is handled by the\nbuildtag analyzer.\n",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/directive",
+- "Default": true
+- },
+- {
+- "Name": "embed",
+- "Doc": "check //go:embed directive usage\n\nThis analyzer checks that the embed package is imported if //go:embed\ndirectives are present, providing a suggested fix to add the import if\nit is missing.\n\nThis analyzer also checks that //go:embed directives precede the\ndeclaration of a single variable.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/embeddirective",
+- "Default": true
+- },
+- {
+- "Name": "errorsas",
+- "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analyzer reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
+- "Default": true
+- },
+- {
+- "Name": "errorsastype",
+- "Doc": "replace errors.As with errors.AsType[T]\n\nThis analyzer suggests fixes to simplify uses of [errors.As] of\nthis form:\n\n\tvar myerr *MyErr\n\tif errors.As(err, \u0026myerr) {\n\t\thandle(myerr)\n\t}\n\nby using the less error-prone generic [errors.AsType] function,\nintroduced in Go 1.26:\n\n\tif myerr, ok := errors.AsType[*MyErr](err); ok {\n\t\thandle(myerr)\n\t}\n\nThe fix is only offered if the var declaration has the form shown and\nthere are no uses of myerr outside the if statement.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
+- "Default": true
+- },
+- {
+- "Name": "fillreturns",
+- "Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns",
+- "Default": true
+- },
+- {
+- "Name": "fmtappendf",
+- "Doc": "replace []byte(fmt.Sprintf) with fmt.Appendf\n\nThe fmtappendf analyzer suggests replacing `[]byte(fmt.Sprintf(...))` with\n`fmt.Appendf(nil, ...)`. This avoids the intermediate allocation of a string\nby Sprintf, making the code more efficient. The suggestion also applies to\nfmt.Sprint and fmt.Sprintln.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#fmtappendf",
+- "Default": true
+- },
+- {
+- "Name": "forvar",
+- "Doc": "remove redundant re-declaration of loop variables\n\nThe forvar analyzer removes unnecessary shadowing of loop variables.\nBefore Go 1.22, it was common to write `for _, x := range s { x := x ... }`\nto create a fresh variable for each iteration. Go 1.22 changed the semantics\nof `for` loops, making this pattern redundant. This analyzer removes the\nunnecessary `x := x` statement.\n\nThis fix only applies to `range` loops.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
+- "Default": true
+- },
+- {
+- "Name": "framepointer",
+- "Doc": "report assembly that clobbers the frame pointer before saving it",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/framepointer",
+- "Default": true
+- },
+- {
+- "Name": "hostport",
+- "Doc": "check format of addresses passed to net.Dial\n\nThis analyzer flags code that produce network address strings using\nfmt.Sprintf, as in this example:\n\n addr := fmt.Sprintf(\"%s:%d\", host, 12345) // \"will not work with IPv6\"\n ...\n conn, err := net.Dial(\"tcp\", addr) // \"when passed to dial here\"\n\nThe analyzer suggests a fix to use the correct approach, a call to\nnet.JoinHostPort:\n\n addr := net.JoinHostPort(host, \"12345\")\n ...\n conn, err := net.Dial(\"tcp\", addr)\n\nA similar diagnostic and fix are produced for a format string of \"%s:%s\".\n",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/hostport",
+- "Default": true
+- },
+- {
+- "Name": "httpresponse",
+- "Doc": "check for mistakes using HTTP responses\n\nA common mistake when using the net/http package is to defer a function\ncall to close the http.Response Body before checking the error that\ndetermines whether the response is valid:\n\n\tresp, err := http.Head(url)\n\tdefer resp.Body.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t// (defer statement belongs here)\n\nThis checker helps uncover latent nil dereference bugs by reporting a\ndiagnostic for such mistakes.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/httpresponse",
+- "Default": true
+- },
+- {
+- "Name": "ifaceassert",
+- "Doc": "detect impossible interface-to-interface type assertions\n\nThis checker flags type assertions v.(T) and corresponding type-switch cases\nin which the static type V of v is an interface that cannot possibly implement\nthe target interface T. This occurs when V and T contain methods with the same\nname but different signatures. Example:\n\n\tvar v interface {\n\t\tRead()\n\t}\n\t_ = v.(io.Reader)\n\nThe Read method in v has a different signature than the Read method in\nio.Reader, so this assertion cannot succeed.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
+- "Default": true
+- },
+- {
+- "Name": "infertypeargs",
+- "Doc": "check for unnecessary type arguments in call expressions\n\nExplicit type arguments may be omitted from call expressions if they can be\ninferred from function arguments, or from other type arguments:\n\n\tfunc f[T any](T) {}\n\t\n\tfunc _() {\n\t\tf[string](\"foo\") // string could be inferred\n\t}\n",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/infertypeargs",
+- "Default": true
+- },
+- {
+- "Name": "inline",
+- "Doc": "apply fixes based on 'go:fix inline' comment directives\n\nThe inline analyzer inlines functions and constants that are marked for inlining.\n\n## Functions\n\nGiven a function that is marked for inlining, like this one:\n\n\t//go:fix inline\n\tfunc Square(x int) int { return Pow(x, 2) }\n\nthis analyzer will recommend that calls to the function elsewhere, in the same\nor other packages, should be inlined.\n\nInlining can be used to move off of a deprecated function:\n\n\t// Deprecated: prefer Pow(x, 2).\n\t//go:fix inline\n\tfunc Square(x int) int { return Pow(x, 2) }\n\nIt can also be used to move off of an obsolete package,\nas when the import path has changed or a higher major version is available:\n\n\tpackage pkg\n\n\timport pkg2 \"pkg/v2\"\n\n\t//go:fix inline\n\tfunc F() { pkg2.F(nil) }\n\nReplacing a call pkg.F() by pkg2.F(nil) can have no effect on the program,\nso this mechanism provides a low-risk way to update large numbers of calls.\nWe recommend, where possible, expressing the old API in terms of the new one\nto enable automatic migration.\n\nThe inliner takes care to avoid behavior changes, even subtle ones,\nsuch as changes to the order in which argument expressions are\nevaluated. When it cannot safely eliminate all parameter variables,\nit may introduce a \"binding declaration\" of the form\n\n\tvar params = args\n\nto evaluate argument expressions in the correct order and bind them to\nparameter variables. Since the resulting code transformation may be\nstylistically suboptimal, such inlinings may be disabled by specifying\nthe -inline.allow_binding_decl=false flag to the analyzer driver.\n\n(In cases where it is not safe to \"reduce\" a call—that is, to replace\na call f(x) by the body of function f, suitably substituted—the\ninliner machinery is capable of replacing f by a function literal,\nfunc(){...}(). However, the inline analyzer discards all such\n\"literalizations\" unconditionally, again on grounds of style.)\n\n## Constants\n\nGiven a constant that is marked for inlining, like this one:\n\n\t//go:fix inline\n\tconst Ptr = Pointer\n\nthis analyzer will recommend that uses of Ptr should be replaced with Pointer.\n\nAs with functions, inlining can be used to replace deprecated constants and\nconstants in obsolete packages.\n\nA constant definition can be marked for inlining only if it refers to another\nnamed constant.\n\nThe \"//go:fix inline\" comment must appear before a single const declaration on its own,\nas above; before a const declaration that is part of a group, as in this case:\n\n\tconst (\n\t C = 1\n\t //go:fix inline\n\t Ptr = Pointer\n\t)\n\nor before a group, applying to every constant in the group:\n\n\t//go:fix inline\n\tconst (\n\t\tPtr = Pointer\n\t Val = Value\n\t)\n\nThe proposal https://go.dev/issue/32816 introduces the \"//go:fix inline\" directives.\n\nYou can use this command to apply inline fixes en masse:\n\n\t$ go run golang.org/x/tools/go/analysis/passes/inline/cmd/inline@latest -fix ./...",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline",
+- "Default": true
+- },
+- {
+- "Name": "loopclosure",
+- "Doc": "check references to loop variables from within nested functions\n\nThis analyzer reports places where a function literal references the\niteration variable of an enclosing loop, and the loop calls the function\nin such a way (e.g. with go or defer) that it may outlive the loop\niteration and possibly observe the wrong value of the variable.\n\nNote: An iteration variable can only outlive a loop iteration in Go versions \u003c=1.21.\nIn Go 1.22 and later, the loop variable lifetimes changed to create a new\niteration variable per loop iteration. (See go.dev/issue/60078.)\n\nIn this example, all the deferred functions run after the loop has\ncompleted, so all observe the final value of v [\u003cgo1.22].\n\n\tfor _, v := range list {\n\t defer func() {\n\t use(v) // incorrect\n\t }()\n\t}\n\nOne fix is to create a new variable for each iteration of the loop:\n\n\tfor _, v := range list {\n\t v := v // new var per iteration\n\t defer func() {\n\t use(v) // ok\n\t }()\n\t}\n\nAfter Go version 1.22, the previous two for loops are equivalent\nand both are correct.\n\nThe next example uses a go statement and has a similar problem [\u003cgo1.22].\nIn addition, it has a data race because the loop updates v\nconcurrent with the goroutines accessing it.\n\n\tfor _, v := range elem {\n\t go func() {\n\t use(v) // incorrect, and a data race\n\t }()\n\t}\n\nA fix is the same as before. The checker also reports problems\nin goroutines started by golang.org/x/sync/errgroup.Group.\nA hard-to-spot variant of this form is common in parallel tests:\n\n\tfunc Test(t *testing.T) {\n\t for _, test := range tests {\n\t t.Run(test.name, func(t *testing.T) {\n\t t.Parallel()\n\t use(test) // incorrect, and a data race\n\t })\n\t }\n\t}\n\nThe t.Parallel() call causes the rest of the function to execute\nconcurrent with the loop [\u003cgo1.22].\n\nThe analyzer reports references only in the last statement,\nas it is not deep enough to understand the effects of subsequent\nstatements that might render the reference benign.\n(\"Last statement\" is defined recursively in compound\nstatements such as if, switch, and select.)\n\nSee: https://golang.org/doc/go_faq.html#closures_and_goroutines",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/loopclosure",
+- "Default": true
+- },
+- {
+- "Name": "lostcancel",
+- "Doc": "check cancel func returned by context.WithCancel is called\n\nThe cancellation function returned by context.WithCancel, WithTimeout,\nWithDeadline and variants such as WithCancelCause must be called,\nor the new context will remain live until its parent context is cancelled.\n(The background context is never cancelled.)",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel",
+- "Default": true
+- },
+- {
+- "Name": "maprange",
+- "Doc": "checks for unnecessary calls to maps.Keys and maps.Values in range statements\n\nConsider a loop written like this:\n\n\tfor val := range maps.Values(m) {\n\t\tfmt.Println(val)\n\t}\n\nThis should instead be written without the call to maps.Values:\n\n\tfor _, val := range m {\n\t\tfmt.Println(val)\n\t}\n\ngolang.org/x/exp/maps returns slices for Keys/Values instead of iterators,\nbut unnecessary calls should similarly be removed:\n\n\tfor _, key := range maps.Keys(m) {\n\t\tfmt.Println(key)\n\t}\n\nshould be rewritten as:\n\n\tfor key := range m {\n\t\tfmt.Println(key)\n\t}",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/maprange",
+- "Default": true
+- },
+- {
+- "Name": "mapsloop",
+- "Doc": "replace explicit loops over maps with calls to maps package\n\nThe mapsloop analyzer replaces loops of the form\n\n\tfor k, v := range x { m[k] = v }\n\nwith a single call to a function from the `maps` package, added in Go 1.23.\nDepending on the context, this could be `maps.Copy`, `maps.Insert`,\n`maps.Clone`, or `maps.Collect`.\n\nThe transformation to `maps.Clone` is applied conservatively, as it\npreserves the nilness of the source map, which may be a subtle change in\nbehavior if the original code did not handle a nil map in the same way.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
+- "Default": true
+- },
+- {
+- "Name": "minmax",
+- "Doc": "replace if/else statements with calls to min or max\n\nThe minmax analyzer simplifies conditional assignments by suggesting the use\nof the built-in `min` and `max` functions, introduced in Go 1.21. For example,\n\n\tif a \u003c b { x = a } else { x = b }\n\nis replaced by\n\n\tx = min(a, b).\n\nThis analyzer avoids making suggestions for floating-point types,\nas the behavior of `min` and `max` with NaN values can differ from\nthe original if/else statement.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#minmax",
+- "Default": true
+- },
+- {
+- "Name": "newexpr",
+- "Doc": "simplify code by using go1.26's new(expr)\n\nThis analyzer finds declarations of functions of this form:\n\n\tfunc varOf(x int) *int { return \u0026x }\n\nand suggests a fix to turn them into inlinable wrappers around\ngo1.26's built-in new(expr) function:\n\n\tfunc varOf(x int) *int { return new(x) }\n\nIn addition, this analyzer suggests a fix for each call\nto one of the functions before it is transformed, so that\n\n\tuse(varOf(123))\n\nis replaced by:\n\n\tuse(new(123))\n\n(Wrapper functions such as varOf are common when working with Go\nserialization packages such as for JSON or protobuf, where pointers\nare often used to express optionality.)",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr",
+- "Default": true
+- },
+- {
+- "Name": "nilfunc",
+- "Doc": "check for useless comparisons between functions and nil\n\nA useless comparison is one like f == nil as opposed to f() == nil.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc",
+- "Default": true
+- },
+- {
+- "Name": "nilness",
+- "Doc": "check for redundant or impossible nil comparisons\n\nThe nilness checker inspects the control-flow graph of each function in\na package and reports nil pointer dereferences, degenerate nil\npointers, and panics with nil values. A degenerate comparison is of the form\nx==nil or x!=nil where x is statically known to be nil or non-nil. These are\noften a mistake, especially in control flow related to errors. Panics with nil\nvalues are checked because they are not detectable by\n\n\tif r := recover(); r != nil {\n\nThis check reports conditions such as:\n\n\tif f == nil { // impossible condition (f is a function)\n\t}\n\nand:\n\n\tp := \u0026v\n\t...\n\tif p != nil { // tautological condition\n\t}\n\nand:\n\n\tif p == nil {\n\t\tprint(*p) // nil dereference\n\t}\n\nand:\n\n\tif p == nil {\n\t\tpanic(p)\n\t}\n\nSometimes the control flow may be quite complex, making bugs hard\nto spot. In the example below, the err.Error expression is\nguaranteed to panic because, after the first return, err must be\nnil. The intervening loop is just a distraction.\n\n\t...\n\terr := g.Wait()\n\tif err != nil {\n\t\treturn err\n\t}\n\tpartialSuccess := false\n\tfor _, err := range errs {\n\t\tif err == nil {\n\t\t\tpartialSuccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif partialSuccess {\n\t\treportStatus(StatusMessage{\n\t\t\tCode: code.ERROR,\n\t\t\tDetail: err.Error(), // \"nil dereference in dynamic method call\"\n\t\t})\n\t\treturn nil\n\t}\n\n...",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilness",
+- "Default": true
+- },
+- {
+- "Name": "nonewvars",
+- "Doc": "suggested fixes for \"no new vars on left side of :=\"\n\nThis checker provides suggested fixes for type errors of the\ntype \"no new vars on left side of :=\". For example:\n\n\tz := 1\n\tz := 2\n\nwill turn into\n\n\tz := 1\n\tz = 2",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/nonewvars",
+- "Default": true
+- },
+- {
+- "Name": "noresultvalues",
+- "Doc": "suggested fixes for unexpected return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"no result values expected\" or \"too many return values\".\nFor example:\n\n\tfunc z() { return nil }\n\nwill turn into\n\n\tfunc z() { return }",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/noresultvalues",
+- "Default": true
+- },
+- {
+- "Name": "omitzero",
+- "Doc": "suggest replacing omitempty with omitzero for struct fields\n\nThe omitzero analyzer identifies uses of the `omitempty` JSON struct tag on\nfields that are themselves structs. The `omitempty` tag has no effect on\nstruct-typed fields. The analyzer offers two suggestions: either remove the\ntag, or replace it with `omitzero` (added in Go 1.24), which correctly\nomits the field if the struct value is zero.\n\nReplacing `omitempty` with `omitzero` is a change in behavior. The\noriginal code would always encode the struct field, whereas the\nmodified code will omit it if it is a zero-value.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
+- "Default": true
+- },
+- {
+- "Name": "printf",
+- "Doc": "check consistency of Printf format strings and arguments\n\nThe check applies to calls of the formatting functions such as\n[fmt.Printf] and [fmt.Sprintf], as well as any detected wrappers of\nthose functions such as [log.Printf]. It reports a variety of\nmistakes such as syntax errors in the format string and mismatches\n(of number and type) between the verbs and their arguments.\n\nSee the documentation of the fmt package for the complete set of\nformat operators and their operand types.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
+- "Default": true
+- },
+- {
+- "Name": "rangeint",
+- "Doc": "replace 3-clause for loops with for-range over integers\n\nThe rangeint analyzer suggests replacing traditional for loops such\nas\n\n\tfor i := 0; i \u003c n; i++ { ... }\n\nwith the more idiomatic Go 1.22 style:\n\n\tfor i := range n { ... }\n\nThis transformation is applied only if (a) the loop variable is not\nmodified within the loop body and (b) the loop's limit expression\nis not modified within the loop, as `for range` evaluates its\noperand only once.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#rangeint",
+- "Default": true
+- },
+- {
+- "Name": "recursiveiter",
+- "Doc": "check for inefficient recursive iterators\n\nThis analyzer reports when a function that returns an iterator\n(iter.Seq or iter.Seq2) calls itself as the operand of a range\nstatement, as this is inefficient.\n\nWhen implementing an iterator (e.g. iter.Seq[T]) for a recursive\ndata type such as a tree or linked list, it is tempting to\nrecursively range over the iterator for each child element.\n\nHere's an example of a naive iterator over a binary tree:\n\n\ttype tree struct {\n\t\tvalue int\n\t\tleft, right *tree\n\t}\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\tif t != nil {\n\t\t\t\tfor elem := range t.left.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !yield(t.value) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tfor elem := range t.right.All() { // \"inefficient recursive iterator\"\n\t\t\t\t\tif !yield(elem) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\nThough it correctly enumerates the elements of the tree, it hides a\nsignificant performance problem--two, in fact. Consider a balanced\ntree of N nodes. Iterating the root node will cause All to be\ncalled once on every node of the tree. This results in a chain of\nnested active range-over-func statements when yield(t.value) is\ncalled on a leaf node.\n\nThe first performance problem is that each range-over-func\nstatement must typically heap-allocate a variable, so iteration of\nthe tree allocates as many variables as there are elements in the\ntree, for a total of O(N) allocations, all unnecessary.\n\nThe second problem is that each call to yield for a leaf of the\ntree causes each of the enclosing range loops to receive a value,\nwhich they then immediately pass on to their respective yield\nfunction. This results in a chain of log(N) dynamic yield calls per\nelement, a total of O(N*log N) dynamic calls overall, when only\nO(N) are necessary.\n\nA better implementation strategy for recursive iterators is to\nfirst define the \"every\" operator for your recursive data type,\nwhere every(f) reports whether an arbitrary predicate f(x) is true\nfor every element x in the data type. For our tree, the every\nfunction would be:\n\n\tfunc (t *tree) every(f func(int) bool) bool {\n\t\treturn t == nil ||\n\t\t\tt.left.every(f) \u0026\u0026 f(t.value) \u0026\u0026 t.right.every(f)\n\t}\n\nFor example, this use of the every operator prints whether every\nelement in the tree is an even number:\n\n\teven := func(x int) bool { return x\u00261 == 0 }\n\tprintln(t.every(even))\n\nThen the iterator can be simply expressed as a trivial wrapper\naround the every operator:\n\n\tfunc (t *tree) All() iter.Seq[int] {\n\t\treturn func(yield func(int) bool) {\n\t\t\t_ = t.every(yield)\n\t\t}\n\t}\n\nIn effect, tree.All computes whether yield returns true for each\nelement, short-circuiting if it ever returns false, then discards\nthe final boolean result.\n\nThis has much better performance characteristics: it makes one\ndynamic call per element of the tree, and it doesn't heap-allocate\nanything. It is also clearer.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/recursiveiter",
+- "Default": true
+- },
+- {
+- "Name": "reflecttypefor",
+- "Doc": "replace reflect.TypeOf(x) with TypeFor[T]()\n\nThis analyzer suggests fixes to replace uses of reflect.TypeOf(x) with\nreflect.TypeFor, introduced in go1.22, when the desired runtime type\nis known at compile time, for example:\n\n\treflect.TypeOf(uint32(0)) -\u003e reflect.TypeFor[uint32]()\n\treflect.TypeOf((*ast.File)(nil)) -\u003e reflect.TypeFor[*ast.File]()\n\nIt also offers a fix to simplify the construction below, which uses\nreflect.TypeOf to return the runtime type for an interface type,\n\n\treflect.TypeOf((*io.Reader)(nil)).Elem()\n\nto:\n\n\treflect.TypeFor[io.Reader]()\n\nNo fix is offered in cases when the runtime type is dynamic, such as:\n\n\tvar r io.Reader = ...\n\treflect.TypeOf(r)\n\nor when the operand has potential side effects.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#reflecttypefor",
+- "Default": true
+- },
+- {
+- "Name": "shadow",
+- "Doc": "check for possible unintended shadowing of variables\n\nThis analyzer check for shadowed variables.\nA shadowed variable is a variable declared in an inner scope\nwith the same name and type as a variable in an outer scope,\nand where the outer variable is mentioned after the inner one\nis declared.\n\n(This definition can be refined; the module generates too many\nfalse positives and is not yet enabled by default.)\n\nFor example:\n\n\tfunc BadRead(f *os.File, buf []byte) error {\n\t\tvar err error\n\t\tfor {\n\t\t\tn, err := f.Read(buf) // shadows the function variable 'err'\n\t\t\tif err != nil {\n\t\t\t\tbreak // causes return of wrong value\n\t\t\t}\n\t\t\tfoo(buf)\n\t\t}\n\t\treturn err\n\t}",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shadow",
+- "Default": false
+- },
+- {
+- "Name": "shift",
+- "Doc": "check for shifts that equal or exceed the width of the integer",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shift",
+- "Default": true
+- },
+- {
+- "Name": "sigchanyzer",
+- "Doc": "check for unbuffered channel of os.Signal\n\nThis checker reports call expression of the form\n\n\tsignal.Notify(c \u003c-chan os.Signal, sig ...os.Signal),\n\nwhere c is an unbuffered channel, which can be at risk of missing the signal.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer",
+- "Default": true
+- },
+- {
+- "Name": "simplifycompositelit",
+- "Doc": "check for composite literal simplifications\n\nAn array, slice, or map composite literal of the form:\n\n\t[]T{T{}, T{}}\n\nwill be simplified to:\n\n\t[]T{{}, {}}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifycompositelit",
+- "Default": true
+- },
+- {
+- "Name": "simplifyrange",
+- "Doc": "check for range statement simplifications\n\nA range of the form:\n\n\tfor x, _ = range v {...}\n\nwill be simplified to:\n\n\tfor x = range v {...}\n\nA range of the form:\n\n\tfor _ = range v {...}\n\nwill be simplified to:\n\n\tfor range v {...}\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyrange",
+- "Default": true
+- },
+- {
+- "Name": "simplifyslice",
+- "Doc": "check for slice simplifications\n\nA slice expression of the form:\n\n\ts[a:len(s)]\n\nwill be simplified to:\n\n\ts[a:]\n\nThis is one of the simplifications that \"gofmt -s\" applies.\n\nThis analyzer ignores generated code.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/simplifyslice",
+- "Default": true
+- },
+- {
+- "Name": "slicescontains",
+- "Doc": "replace loops with slices.Contains or slices.ContainsFunc\n\nThe slicescontains analyzer simplifies loops that check for the existence of\nan element in a slice. It replaces them with calls to `slices.Contains` or\n`slices.ContainsFunc`, which were added in Go 1.21.\n\nIf the expression for the target element has side effects, this\ntransformation will cause those effects to occur only once, not\nonce per tested slice element.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicescontains",
+- "Default": true
+- },
+- {
+- "Name": "slicesdelete",
+- "Doc": "replace append-based slice deletion with slices.Delete\n\nThe slicesdelete analyzer suggests replacing the idiom\n\n\ts = append(s[:i], s[j:]...)\n\nwith the more explicit\n\n\ts = slices.Delete(s, i, j)\n\nintroduced in Go 1.21.\n\nThis analyzer is disabled by default. The `slices.Delete` function\nzeros the elements between the new length and the old length of the\nslice to prevent memory leaks, which is a subtle difference in\nbehavior compared to the append-based idiom; see https://go.dev/issue/73686.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
+- "Default": false
+- },
+- {
+- "Name": "slicessort",
+- "Doc": "replace sort.Slice with slices.Sort for basic types\n\nThe slicessort analyzer simplifies sorting slices of basic ordered\ntypes. It replaces\n\n\tsort.Slice(s, func(i, j int) bool { return s[i] \u003c s[j] })\n\nwith the simpler `slices.Sort(s)`, which was added in Go 1.21.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicessort",
+- "Default": true
+- },
+- {
+- "Name": "slog",
+- "Doc": "check for invalid structured logging calls\n\nThe slog checker looks for calls to functions from the log/slog\npackage that take alternating key-value pairs. It reports calls\nwhere an argument in a key position is neither a string nor a\nslog.Attr, and where a final key is missing its value.\nFor example,it would report\n\n\tslog.Warn(\"message\", 11, \"k\") // slog.Warn arg \"11\" should be a string or a slog.Attr\n\nand\n\n\tslog.Info(\"message\", \"k1\", v1, \"k2\") // call to slog.Info missing a final value",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog",
+- "Default": true
+- },
+- {
+- "Name": "sortslice",
+- "Doc": "check the argument type of sort.Slice\n\nsort.Slice requires an argument of a slice type. Check that\nthe interface{} value passed to sort.Slice is actually a slice.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice",
+- "Default": true
+- },
+- {
+- "Name": "stditerators",
+- "Doc": "use iterators instead of Len/At-style APIs\n\nThis analyzer suggests a fix to replace each loop of the form:\n\n\tfor i := 0; i \u003c x.Len(); i++ {\n\t\tuse(x.At(i))\n\t}\n\nor its \"for elem := range x.Len()\" equivalent by a range loop over an\niterator offered by the same data type:\n\n\tfor elem := range x.All() {\n\t\tuse(x.At(i)\n\t}\n\nwhere x is one of various well-known types in the standard library.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stditerators",
+- "Default": true
+- },
+- {
+- "Name": "stdmethods",
+- "Doc": "check signature of methods of well-known interfaces\n\nSometimes a type may be intended to satisfy an interface but may fail to\ndo so because of a mistake in its method signature.\nFor example, the result of this WriteTo method should be (int64, error),\nnot error, to satisfy io.WriterTo:\n\n\ttype myWriterTo struct{...}\n\tfunc (myWriterTo) WriteTo(w io.Writer) error { ... }\n\nThis check ensures that each method whose name matches one of several\nwell-known interface methods from the standard library has the correct\nsignature for that interface.\n\nChecked method names include:\n\n\tFormat GobEncode GobDecode MarshalJSON MarshalXML\n\tPeek ReadByte ReadFrom ReadRune Scan Seek\n\tUnmarshalJSON UnreadByte UnreadRune WriteByte\n\tWriteTo",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods",
+- "Default": true
+- },
+- {
+- "Name": "stdversion",
+- "Doc": "report uses of too-new standard library symbols\n\nThe stdversion analyzer reports references to symbols in the standard\nlibrary that were introduced by a Go release higher than the one in\nforce in the referring file. (Recall that the file's Go version is\ndefined by the 'go' directive its module's go.mod file, or by a\n\"//go:build go1.X\" build tag at the top of the file.)\n\nThe analyzer does not report a diagnostic for a reference to a \"too\nnew\" field or method of a type that is itself \"too new\", as this may\nhave false positives, for example if fields or methods are accessed\nthrough a type alias that is guarded by a Go version constraint.\n",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
+- "Default": true
+- },
+- {
+- "Name": "stringintconv",
+- "Doc": "check for string(int) conversions\n\nThis checker flags conversions of the form string(x) where x is an integer\n(but not byte or rune) type. Such conversions are discouraged because they\nreturn the UTF-8 representation of the Unicode code point x, and not a decimal\nstring representation of x as one might expect. Furthermore, if x denotes an\ninvalid code point, the conversion cannot be statically rejected.\n\nFor conversions that intend on using the code point, consider replacing them\nwith string(rune(x)). Otherwise, strconv.Itoa and its equivalents return the\nstring representation of the value in the desired base.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
+- "Default": true
+- },
+- {
+- "Name": "stringsbuilder",
+- "Doc": "replace += with strings.Builder\n\nThis analyzer replaces repeated string += string concatenation\noperations with calls to Go 1.10's strings.Builder.\n\nFor example:\n\n\tvar s = \"[\"\n\tfor x := range seq {\n\t\ts += x\n\t\ts += \".\"\n\t}\n\ts += \"]\"\n\tuse(s)\n\nis replaced by:\n\n\tvar s strings.Builder\n\ts.WriteString(\"[\")\n\tfor x := range seq {\n\t\ts.WriteString(x)\n\t\ts.WriteString(\".\")\n\t}\n\ts.WriteString(\"]\")\n\tuse(s.String())\n\nThis avoids quadratic memory allocation and improves performance.\n\nThe analyzer requires that all references to s except the final one\nare += operations. To avoid warning about trivial cases, at least one\nmust appear within a loop. The variable s must be a local\nvariable, not a global or parameter.\n\nThe sole use of the finished string must be the last reference to the\nvariable s. (It may appear within an intervening loop or function literal,\nsince even s.String() is called repeatedly, it does not allocate memory.)",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringbuilder",
+- "Default": true
+- },
+- {
+- "Name": "stringscutprefix",
+- "Doc": "replace HasPrefix/TrimPrefix with CutPrefix\n\nThe stringscutprefix analyzer simplifies a common pattern where code first\nchecks for a prefix with `strings.HasPrefix` and then removes it with\n`strings.TrimPrefix`. It replaces this two-step process with a single call\nto `strings.CutPrefix`, introduced in Go 1.20. The analyzer also handles\nthe equivalent functions in the `bytes` package.\n\nFor example, this input:\n\n\tif strings.HasPrefix(s, prefix) {\n\t use(strings.TrimPrefix(s, prefix))\n\t}\n\nis fixed to:\n\n\tif after, ok := strings.CutPrefix(s, prefix); ok {\n\t use(after)\n\t}\n\nThe analyzer also offers fixes to use CutSuffix in a similar way.\nThis input:\n\n\tif strings.HasSuffix(s, suffix) {\n\t use(strings.TrimSuffix(s, suffix))\n\t}\n\nis fixed to:\n\n\tif before, ok := strings.CutSuffix(s, suffix); ok {\n\t use(before)\n\t}",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringscutprefix",
+- "Default": true
+- },
+- {
+- "Name": "stringsseq",
+- "Doc": "replace ranging over Split/Fields with SplitSeq/FieldsSeq\n\nThe stringsseq analyzer improves the efficiency of iterating over substrings.\nIt replaces\n\n\tfor range strings.Split(...)\n\nwith the more efficient\n\n\tfor range strings.SplitSeq(...)\n\nwhich was added in Go 1.24 and avoids allocating a slice for the\nsubstrings. The analyzer also handles strings.Fields and the\nequivalent functions in the bytes package.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#stringsseq",
+- "Default": true
+- },
+- {
+- "Name": "structtag",
+- "Doc": "check that struct field tags conform to reflect.StructTag.Get\n\nAlso report certain struct tags (json, xml) used with unexported fields.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/structtag",
+- "Default": true
+- },
+- {
+- "Name": "testingcontext",
+- "Doc": "replace context.WithCancel with t.Context in tests\n\nThe testingcontext analyzer simplifies context management in tests. It\nreplaces the manual creation of a cancellable context,\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\nwith a single call to t.Context(), which was added in Go 1.24.\n\nThis change is only suggested if the `cancel` function is not used\nfor any other purpose.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#testingcontext",
+- "Default": true
+- },
+- {
+- "Name": "testinggoroutine",
+- "Doc": "report calls to (*testing.T).Fatal from goroutines started by a test\n\nFunctions that abruptly terminate a test, such as the Fatal, Fatalf, FailNow, and\nSkip{,f,Now} methods of *testing.T, must be called from the test goroutine itself.\nThis checker detects calls to these functions that occur within a goroutine\nstarted by the test. For example:\n\n\tfunc TestFoo(t *testing.T) {\n\t go func() {\n\t t.Fatal(\"oops\") // error: (*T).Fatal called from non-test goroutine\n\t }()\n\t}",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine",
+- "Default": true
+- },
+- {
+- "Name": "tests",
+- "Doc": "check for common mistaken usages of tests and examples\n\nThe tests checker walks Test, Benchmark, Fuzzing and Example functions checking\nmalformed names, wrong signatures and examples documenting non-existent\nidentifiers.\n\nPlease see the documentation for package testing in golang.org/pkg/testing\nfor the conventions that are enforced for Tests, Benchmarks, and Examples.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests",
+- "Default": true
+- },
+- {
+- "Name": "timeformat",
+- "Doc": "check for calls of (time.Time).Format or time.Parse with 2006-02-01\n\nThe timeformat checker looks for time formats with the 2006-02-01 (yyyy-dd-mm)\nformat. Internationally, \"yyyy-dd-mm\" does not occur in common calendar date\nstandards, and so it is more likely that 2006-01-02 (yyyy-mm-dd) was intended.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat",
+- "Default": true
+- },
+- {
+- "Name": "unmarshal",
+- "Doc": "report passing non-pointer or non-interface values to unmarshal\n\nThe unmarshal analysis reports calls to functions such as json.Unmarshal\nin which the argument type is not a pointer or an interface.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal",
+- "Default": true
+- },
+- {
+- "Name": "unreachable",
+- "Doc": "check for unreachable code\n\nThe unreachable analyzer finds statements that execution can never reach\nbecause they are preceded by a return statement, a call to panic, an\ninfinite loop, or similar constructs.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable",
+- "Default": true
+- },
+- {
+- "Name": "unsafeptr",
+- "Doc": "check for invalid conversions of uintptr to unsafe.Pointer\n\nThe unsafeptr analyzer reports likely incorrect uses of unsafe.Pointer\nto convert integers to pointers. A conversion from uintptr to\nunsafe.Pointer is invalid if it implies that there is a uintptr-typed\nword in memory that holds a pointer value, because that word will be\ninvisible to stack copying and to the garbage collector.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr",
+- "Default": true
+- },
+- {
+- "Name": "unusedfunc",
+- "Doc": "check for unused functions, methods, etc\n\nThe unusedfunc analyzer reports functions and methods that are\nnever referenced outside of their own declaration.\n\nA function is considered unused if it is unexported and not\nreferenced (except within its own declaration).\n\nA method is considered unused if it is unexported, not referenced\n(except within its own declaration), and its name does not match\nthat of any method of an interface type declared within the same\npackage.\n\nThe tool may report false positives in some situations, for\nexample:\n\n - for a declaration of an unexported function that is referenced\n from another package using the go:linkname mechanism, if the\n declaration's doc comment does not also have a go:linkname\n comment.\n\n (Such code is in any case strongly discouraged: linkname\n annotations, if they must be used at all, should be used on both\n the declaration and the alias.)\n\n - for compiler intrinsics in the \"runtime\" package that, though\n never referenced, are known to the compiler and are called\n indirectly by compiled object code.\n\n - for functions called only from assembly.\n\n - for functions called only from files whose build tags are not\n selected in the current build configuration.\n\nSince these situations are relatively common in the low-level parts\nof the runtime, this analyzer ignores the standard library.\nSee https://go.dev/issue/71686 and https://go.dev/issue/74130 for\nfurther discussion of these limitations.\n\nThe unusedfunc algorithm is not as precise as the\ngolang.org/x/tools/cmd/deadcode tool, but it has the advantage that\nit runs within the modular analysis framework, enabling near\nreal-time feedback within gopls.\n\nThe unusedfunc analyzer also reports unused types, vars, and\nconstants. Enums--constants defined with iota--are ignored since\neven the unused values must remain present to preserve the logical\nordering.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedfunc",
+- "Default": true
+- },
+- {
+- "Name": "unusedparams",
+- "Doc": "check for unused parameters of functions\n\nThe unusedparams analyzer checks functions to see if there are\nany parameters that are not being used.\n\nTo ensure soundness, it ignores:\n - \"address-taken\" functions, that is, functions that are used as\n a value rather than being called directly; their signatures may\n be required to conform to a func type.\n - exported functions or methods, since they may be address-taken\n in another package.\n - unexported methods whose name matches an interface method\n declared in the same package, since the method's signature\n may be required to conform to the interface type.\n - functions with empty bodies, or containing just a call to panic.\n - parameters that are unnamed, or named \"_\", the blank identifier.\n\nThe analyzer suggests a fix of replacing the parameter name by \"_\",\nbut in such cases a deeper fix can be obtained by invoking the\n\"Refactor: remove unused parameter\" code action, which will\neliminate the parameter entirely, along with all corresponding\narguments at call sites, while taking care to preserve any side\neffects in the argument expressions; see\nhttps://github.com/golang/tools/releases/tag/gopls%2Fv0.14.\n\nThis analyzer ignores generated code.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedparams",
+- "Default": true
+- },
+- {
+- "Name": "unusedresult",
+- "Doc": "check for unused results of calls to some functions\n\nSome functions like fmt.Errorf return a result and have no side\neffects, so it is always a mistake to discard the result. Other\nfunctions may return an error that must not be ignored, or a cleanup\noperation that must be called. This analyzer reports calls to\nfunctions like these when the result of the call is ignored.\n\nThe set of functions may be controlled using flags.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
+- "Default": true
+- },
+- {
+- "Name": "unusedvariable",
+- "Doc": "check for unused variables and suggest fixes",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/unusedvariable",
+- "Default": true
+- },
+- {
+- "Name": "unusedwrite",
+- "Doc": "checks for unused writes\n\nThe analyzer reports instances of writes to struct fields and\narrays that are never read. Specifically, when a struct object\nor an array is copied, its elements are copied implicitly by\nthe compiler, and any element write to this copy does nothing\nwith the original object.\n\nFor example:\n\n\ttype T struct { x int }\n\n\tfunc f(input []T) {\n\t\tfor i, v := range input { // v is a copy\n\t\t\tv.x = i // unused write to field x\n\t\t}\n\t}\n\nAnother example is about non-pointer receiver:\n\n\ttype T struct { x int }\n\n\tfunc (t T) f() { // t is a copy\n\t\tt.x = i // unused write to field x\n\t}",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedwrite",
+- "Default": true
+- },
+- {
+- "Name": "waitgroup",
+- "Doc": "check for misuses of sync.WaitGroup\n\nThis analyzer detects mistaken calls to the (*sync.WaitGroup).Add\nmethod from inside a new goroutine, causing Add to race with Wait:\n\n\t// WRONG\n\tvar wg sync.WaitGroup\n\tgo func() {\n\t wg.Add(1) // \"WaitGroup.Add called from inside new goroutine\"\n\t defer wg.Done()\n\t ...\n\t}()\n\twg.Wait() // (may return prematurely before new goroutine starts)\n\nThe correct code calls Add before starting the goroutine:\n\n\t// RIGHT\n\tvar wg sync.WaitGroup\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\twg.Wait()",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
+- "Default": true
+- },
+- {
+- "Name": "waitgroup",
+- "Doc": "replace wg.Add(1)/go/wg.Done() with wg.Go\n\nThe waitgroup analyzer simplifies goroutine management with `sync.WaitGroup`.\nIt replaces the common pattern\n\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t...\n\t}()\n\nwith a single call to\n\n\twg.Go(func(){ ... })\n\nwhich was added in Go 1.25.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#waitgroup",
+- "Default": true
+- },
+- {
+- "Name": "yield",
+- "Doc": "report calls to yield where the result is ignored\n\nAfter a yield function returns false, the caller should not call\nthe yield function again; generally the iterator should return\npromptly.\n\nThis example fails to check the result of the call to yield,\ncausing this analyzer to report a diagnostic:\n\n\tyield(1) // yield may be called again (on L2) after returning false\n\tyield(2)\n\nThe corrected code is either this:\n\n\tif yield(1) { yield(2) }\n\nor simply:\n\n\t_ = yield(1) \u0026\u0026 yield(2)\n\nIt is not always a mistake to ignore the result of yield.\nFor example, this is a valid single-element iterator:\n\n\tyield(1) // ok to ignore result\n\treturn\n\nIt is only a mistake when the yield call that returned false may be\nfollowed by another call.",
+- "URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/yield",
+- "Default": true
+- }
+- ],
+- "Hints": [
+- {
+- "Name": "assignVariableTypes",
+- "Doc": "`\"assignVariableTypes\"` controls inlay hints for variable types in assign statements:\n```go\n\ti/* int*/, j/* int*/ := 0, len(r)-1\n```\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "compositeLiteralFields",
+- "Doc": "`\"compositeLiteralFields\"` inlay hints for composite literal field names:\n```go\n\t{/*in: */\"Hello, world\", /*want: */\"dlrow ,olleH\"}\n```\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "compositeLiteralTypes",
+- "Doc": "`\"compositeLiteralTypes\"` controls inlay hints for composite literal types:\n```go\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}\n```\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "constantValues",
+- "Doc": "`\"constantValues\"` controls inlay hints for constant values:\n```go\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)\n```\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "functionTypeParameters",
+- "Doc": "`\"functionTypeParameters\"` inlay hints for implicit type parameters on generic functions:\n```go\n\tmyFoo/*[int, string]*/(1, \"hello\")\n```\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "ignoredError",
+- "Doc": "`\"ignoredError\"` inlay hints for implicitly discarded errors:\n```go\n\tf.Close() // ignore error\n```\nThis check inserts an `// ignore error` hint following any\nstatement that is a function call whose error result is\nimplicitly ignored.\n\nTo suppress the hint, write an actual comment containing\n\"ignore error\" following the call statement, or explictly\nassign the result to a blank variable. A handful of common\nfunctions such as `fmt.Println` are excluded from the\ncheck.\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "parameterNames",
+- "Doc": "`\"parameterNames\"` controls inlay hints for parameter names:\n```go\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)\n```\n",
+- "Default": false,
+- "Status": ""
+- },
+- {
+- "Name": "rangeVariableTypes",
+- "Doc": "`\"rangeVariableTypes\"` controls inlay hints for variable types in range statements:\n```go\n\tfor k/* int*/, v/* string*/ := range []string{} {\n\t\tfmt.Println(k, v)\n\t}\n```\n",
+- "Default": false,
+- "Status": ""
+- }
+- ]
+-}
+\ No newline at end of file
+diff -urN a/gopls/internal/doc/generate/generate.go b/gopls/internal/doc/generate/generate.go
+--- a/gopls/internal/doc/generate/generate.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/doc/generate/generate.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,838 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// The generate command updates the following files of documentation:
+-//
+-// gopls/doc/settings.md -- from linking gopls/internal/settings.DefaultOptions
+-// gopls/doc/analyzers.md -- from linking gopls/internal/settings.DefaultAnalyzers
+-// gopls/doc/inlayHints.md -- from loading gopls/internal/settings.InlayHint
+-// gopls/internal/doc/api.json -- all of the above in a single value, for 'gopls api-json'
+-//
+-// Run it with this command:
+-//
+-// $ cd gopls/internal/doc/generate && go generate
+-package main
+-
+-import (
+- "bytes"
+- "encoding/json"
+- "errors"
+- "fmt"
+- "go/ast"
+- "go/doc/comment"
+- "go/token"
+- "go/types"
+- "maps"
+- "os"
+- "os/exec"
+- "path/filepath"
+- "reflect"
+- "regexp"
+- "slices"
+- "sort"
+- "strconv"
+- "strings"
+- "time"
+- "unicode"
+-
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/go/packages"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/doc"
+- "golang.org/x/tools/gopls/internal/golang"
+- "golang.org/x/tools/gopls/internal/mod"
+- "golang.org/x/tools/gopls/internal/settings"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- internalastutil "golang.org/x/tools/internal/astutil"
+-)
+-
+-func main() {
+- if _, err := doMain(true); err != nil {
+- fmt.Fprintf(os.Stderr, "Generation failed: %v\n", err)
+- os.Exit(1)
+- }
+-}
+-
+-// doMain regenerates the output files. On success:
+-// - if write, it updates them;
+-// - if !write, it reports whether they would change.
+-func doMain(write bool) (bool, error) {
+- api, err := loadAPI()
+- if err != nil {
+- return false, err
+- }
+-
+- goplsDir, err := pkgDir("golang.org/x/tools/gopls")
+- if err != nil {
+- return false, err
+- }
+-
+- // TODO(adonovan): consider using HTML, not Markdown, for the
+- // generated reference documents. It's not more difficult, the
+- // layout is easier to read, and we can use go/doc-comment
+- // rendering logic.
+-
+- for _, f := range []struct {
+- name string // relative to gopls
+- rewrite rewriter
+- }{
+- {"internal/doc/api.json", rewriteAPI},
+- {"doc/settings.md", rewriteSettings},
+- {"doc/codelenses.md", rewriteCodeLenses},
+- {"doc/analyzers.md", rewriteAnalyzers},
+- {"doc/inlayHints.md", rewriteInlayHints},
+- } {
+- file := filepath.Join(goplsDir, f.name)
+- old, err := os.ReadFile(file)
+- if err != nil {
+- return false, err
+- }
+-
+- new, err := f.rewrite(old, api)
+- if err != nil {
+- return false, fmt.Errorf("rewriting %q: %v", file, err)
+- }
+-
+- if write {
+- if err := os.WriteFile(file, new, 0); err != nil {
+- return false, err
+- }
+- } else if !bytes.Equal(old, new) {
+- return false, nil // files would change
+- }
+- }
+- return true, nil
+-}
+-
+-// A rewriter is a function that transforms the content of a file.
+-type rewriter = func([]byte, *doc.API) ([]byte, error)
+-
+-// pkgDir returns the directory corresponding to the import path pkgPath.
+-func pkgDir(pkgPath string) (string, error) {
+- cmd := exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath)
+- out, err := cmd.Output()
+- if err != nil {
+- if ee, _ := err.(*exec.ExitError); ee != nil && len(ee.Stderr) > 0 {
+- return "", fmt.Errorf("%v: %w\n%s", cmd, err, ee.Stderr)
+- }
+- return "", fmt.Errorf("%v: %w", cmd, err)
+- }
+- return strings.TrimSpace(string(out)), nil
+-}
+-
+-// loadAPI computes the JSON-encodable value that describes gopls'
+-// interfaces, by a combination of static and dynamic analysis.
+-func loadAPI() (*doc.API, error) {
+- pkgs, err := packages.Load(
+- &packages.Config{
+- Mode: packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax | packages.NeedDeps,
+- },
+- "golang.org/x/tools/gopls/internal/settings",
+- )
+- if err != nil {
+- return nil, err
+- }
+- settingsPkg := pkgs[0]
+-
+- defaults := settings.DefaultOptions()
+- api := &doc.API{
+- Options: map[string][]*doc.Option{},
+- Analyzers: loadAnalyzers(settings.AllAnalyzers, defaults),
+- }
+-
+- api.Lenses, err = loadLenses(settingsPkg, defaults.Codelenses)
+- if err != nil {
+- return nil, err
+- }
+- api.Hints, err = loadHints(settingsPkg)
+- if err != nil {
+- return nil, err
+- }
+-
+- for _, category := range []reflect.Value{
+- reflect.ValueOf(defaults.UserOptions),
+- } {
+- // Find the type information and ast.File corresponding to the category.
+- optsType := settingsPkg.Types.Scope().Lookup(category.Type().Name())
+- if optsType == nil {
+- return nil, fmt.Errorf("could not find %v in scope %v", category.Type().Name(), settingsPkg.Types.Scope())
+- }
+- opts, err := loadOptions(category, optsType, settingsPkg, "")
+- if err != nil {
+- return nil, err
+- }
+-
+- // Edge case for "analyses": populate its enum keys from
+- // the analyzer list, since its map keys are strings, not enums.
+- // Also, set its EnumKeys.ValueType for historical reasons.
+- for _, opt := range opts {
+- if opt.Name == "analyses" {
+- opt.EnumKeys.ValueType = "bool"
+- for _, a := range api.Analyzers {
+- opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, doc.EnumKey{
+- Name: fmt.Sprintf("%q", a.Name),
+- Doc: a.Doc,
+- Default: strconv.FormatBool(a.Default),
+- })
+- }
+- }
+- }
+-
+- catName := strings.TrimSuffix(category.Type().Name(), "Options")
+- api.Options[catName] = opts
+- }
+- return api, nil
+-}
+-
+-// loadOptions computes a single category of settings by a combination
+-// of static analysis and reflection over gopls internal types.
+-func loadOptions(category reflect.Value, optsType types.Object, pkg *packages.Package, hierarchy string) ([]*doc.Option, error) {
+- file, err := fileForPos(pkg, optsType.Pos())
+- if err != nil {
+- return nil, err
+- }
+-
+- enums, err := loadEnums(pkg) // TODO(adonovan): do this only once at toplevel.
+- if err != nil {
+- return nil, err
+- }
+-
+- var opts []*doc.Option
+- optsStruct := optsType.Type().Underlying().(*types.Struct)
+- for i := 0; i < optsStruct.NumFields(); i++ {
+- // The types field gives us the type.
+- typesField := optsStruct.Field(i)
+-
+- // If the field name ends with "Options", assume it is a struct with
+- // additional options and process it recursively.
+- if h := strings.TrimSuffix(typesField.Name(), "Options"); h != typesField.Name() {
+- // Keep track of the parent structs.
+- if hierarchy != "" {
+- h = hierarchy + "." + h
+- }
+- options, err := loadOptions(category, typesField, pkg, strings.ToLower(h))
+- if err != nil {
+- return nil, err
+- }
+- opts = append(opts, options...)
+- continue
+- }
+- path, _ := astutil.PathEnclosingInterval(file, typesField.Pos(), typesField.Pos())
+- if len(path) < 2 {
+- return nil, fmt.Errorf("could not find AST node for field %v", typesField)
+- }
+-
+- // The AST field gives us the doc.
+- astField, ok := path[1].(*ast.Field)
+- if !ok {
+- return nil, fmt.Errorf("unexpected AST path %v", path)
+- }
+- description, deprecation := astField.Doc.Text(), internalastutil.Deprecation(astField.Doc)
+-
+- // The reflect field gives us the default value.
+- reflectField := category.FieldByName(typesField.Name())
+- if !reflectField.IsValid() {
+- return nil, fmt.Errorf("could not find reflect field for %v", typesField.Name())
+- }
+-
+- def, err := formatDefault(reflectField)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Derive the doc-and-api.json type from the Go field type.
+- //
+- // In principle, we should use JSON nomenclature here
+- // (number, array, object, etc; see #68057), but in
+- // practice we use the Go type string ([]T, map[K]V,
+- // etc) with only one tweak: enumeration types are
+- // replaced by "enum", including when they appear as
+- // map keys.
+- //
+- // Notable edge cases:
+- // - any (e.g. in linksInHover) is really a sum of false | true | "internal".
+- // - time.Duration is really a string with a particular syntax.
+- typ := typesField.Type().String()
+- if _, ok := enums[typesField.Type()]; ok {
+- typ = "enum"
+- }
+- name := lowerFirst(typesField.Name())
+-
+- // enum-keyed maps
+- var enumKeys doc.EnumKeys
+- if m, ok := typesField.Type().Underlying().(*types.Map); ok {
+- if values, ok := enums[m.Key()]; ok {
+- // Update type name: "map[CodeLensSource]T" -> "map[enum]T"
+- // hack: assumes key substring is unique!
+- typ = strings.Replace(typ, m.Key().String(), "enum", 1)
+-
+- enumKeys.ValueType = m.Elem().String() // e.g. bool
+-
+- // For map[enum]T fields, gather the set of valid
+- // EnumKeys (from type information). If T=bool, also
+- // record the default value (from reflection).
+- keys, err := collectEnumKeys(m, reflectField, values)
+- if err != nil {
+- return nil, err
+- }
+- enumKeys.Keys = keys
+- }
+- }
+-
+- // Get the status of the field by checking its struct tags.
+- reflectStructField, ok := category.Type().FieldByName(typesField.Name())
+- if !ok {
+- return nil, fmt.Errorf("no struct field for %s", typesField.Name())
+- }
+- status := reflectStructField.Tag.Get("status")
+-
+- opts = append(opts, &doc.Option{
+- Name: name,
+- Type: typ,
+- Doc: lowerFirst(description),
+- Default: def,
+- EnumKeys: enumKeys,
+- EnumValues: enums[typesField.Type()],
+- Status: status,
+- Hierarchy: hierarchy,
+- DeprecationMessage: lowerFirst(strings.TrimPrefix(deprecation, "Deprecated: ")),
+- })
+- }
+- return opts, nil
+-}
+-
+-// loadEnums returns a description of gopls' settings enum types based on static analysis.
+-func loadEnums(pkg *packages.Package) (map[types.Type][]doc.EnumValue, error) {
+- enums := make(map[types.Type][]doc.EnumValue)
+- for _, name := range pkg.Types.Scope().Names() {
+- obj := pkg.Types.Scope().Lookup(name)
+- cnst, ok := obj.(*types.Const)
+- if !ok {
+- continue
+- }
+- f, err := fileForPos(pkg, cnst.Pos())
+- if err != nil {
+- return nil, fmt.Errorf("finding file for %q: %v", cnst.Name(), err)
+- }
+- path, _ := astutil.PathEnclosingInterval(f, cnst.Pos(), cnst.Pos())
+- spec := path[1].(*ast.ValueSpec)
+- value := cnst.Val().ExactString()
+- docstring := valueDoc(cnst.Name(), value, spec.Doc.Text())
+- var status string
+- for _, d := range internalastutil.Directives(spec.Doc) {
+- if d.Tool == "gopls" && d.Name == "status" {
+- status = d.Args
+- break
+- }
+- }
+- v := doc.EnumValue{
+- Value: value,
+- Doc: docstring,
+- Status: status,
+- }
+- enums[obj.Type()] = append(enums[obj.Type()], v)
+- }
+-
+- // linksInHover is a one-off edge case (true | false | "gopls")
+- // that doesn't warrant a general solution (e.g. struct tag).
+- enums[pkg.Types.Scope().Lookup("LinksInHoverEnum").Type()] = []doc.EnumValue{
+- {Value: "false", Doc: "false: do not show links"},
+- {Value: "true", Doc: "true: show links to the `linkTarget` domain"},
+- {Value: `"gopls"`, Doc: "`\"gopls\"`: show links to gopls' internal documentation viewer"},
+- }
+-
+- return enums, nil
+-}
+-
+-func collectEnumKeys(m *types.Map, reflectField reflect.Value, enumValues []doc.EnumValue) ([]doc.EnumKey, error) {
+- // We can get default values for enum -> bool maps.
+- var isEnumBoolMap bool
+- if basic, ok := m.Elem().Underlying().(*types.Basic); ok && basic.Kind() == types.Bool {
+- isEnumBoolMap = true
+- }
+- var keys []doc.EnumKey
+- for _, v := range enumValues {
+- var def string
+- if isEnumBoolMap {
+- var err error
+- def, err = formatDefaultFromEnumBoolMap(reflectField, v.Value)
+- if err != nil {
+- return nil, err
+- }
+- }
+- keys = append(keys, doc.EnumKey{
+- Name: v.Value,
+- Doc: v.Doc,
+- Status: v.Status,
+- Default: def,
+- })
+- }
+- return keys, nil
+-}
+-
+-func formatDefaultFromEnumBoolMap(reflectMap reflect.Value, enumKey string) (string, error) {
+- if reflectMap.Kind() != reflect.Map {
+- return "", nil
+- }
+- name := enumKey
+- if unquoted, err := strconv.Unquote(name); err == nil {
+- name = unquoted
+- }
+- for _, e := range reflectMap.MapKeys() {
+- if e.String() == name {
+- value := reflectMap.MapIndex(e)
+- if value.Type().Kind() == reflect.Bool {
+- return formatDefault(value)
+- }
+- }
+- }
+- // Assume that if the value isn't mentioned in the map, it defaults to
+- // the default value, false.
+- return formatDefault(reflect.ValueOf(false))
+-}
+-
+-// formatDefault formats the default value into a JSON-like string.
+-// VS Code exposes settings as JSON, so showing them as JSON is reasonable.
+-// TODO(rstambler): Reconsider this approach, as the VS Code Go generator now
+-// marshals to JSON.
+-func formatDefault(reflectField reflect.Value) (string, error) {
+- def := reflectField.Interface()
+-
+- // Durations marshal as nanoseconds, but we want the stringy versions,
+- // e.g. "100ms".
+- if t, ok := def.(time.Duration); ok {
+- def = t.String()
+- }
+- defBytes, err := json.Marshal(def)
+- if err != nil {
+- return "", err
+- }
+-
+- // Nil values format as "null" so print them as hardcoded empty values.
+- switch reflectField.Type().Kind() {
+- case reflect.Map:
+- if reflectField.IsNil() {
+- defBytes = []byte("{}")
+- }
+- case reflect.Slice:
+- if reflectField.IsNil() {
+- defBytes = []byte("[]")
+- }
+- }
+- return string(defBytes), err
+-}
+-
+-// valueDoc transforms a docstring documenting a constant identifier to a
+-// docstring documenting its value.
+-//
+-// If doc is of the form "Foo is a bar", it returns '`"fooValue"` is a bar'. If
+-// doc is non-standard ("this value is a bar"), it returns '`"fooValue"`: this
+-// value is a bar'.
+-func valueDoc(name, value, doc string) string {
+- if doc == "" {
+- return ""
+- }
+- if strings.HasPrefix(doc, name) {
+- // docstring in standard form. Replace the subject with value.
+- return fmt.Sprintf("`%s`%s", value, doc[len(name):])
+- }
+- return fmt.Sprintf("`%s`: %s", value, doc)
+-}
+-
+-// loadLenses combines the syntactic comments from the settings
+-// package with the default values from settings.DefaultOptions(), and
+-// returns a list of Code Lens descriptors.
+-func loadLenses(settingsPkg *packages.Package, defaults map[settings.CodeLensSource]bool) ([]*doc.Lens, error) {
+- // Find the CodeLensSource enums among the files of the protocol package.
+- // Map each enum value to its doc comment.
+- enumDoc := make(map[string]string)
+- enumStatus := make(map[string]string)
+- for _, f := range settingsPkg.Syntax {
+- for _, decl := range f.Decls {
+- if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.CONST {
+- for _, spec := range decl.Specs {
+- spec := spec.(*ast.ValueSpec)
+- posn := safetoken.StartPosition(settingsPkg.Fset, spec.Pos())
+- if id, ok := spec.Type.(*ast.Ident); ok && id.Name == "CodeLensSource" {
+- if len(spec.Names) != 1 || len(spec.Values) != 1 {
+- return nil, fmt.Errorf("%s: declare one CodeLensSource per line", posn)
+- }
+- lit, ok := spec.Values[0].(*ast.BasicLit)
+- if !ok || lit.Kind != token.STRING {
+- return nil, fmt.Errorf("%s: CodeLensSource value is not a string literal", posn)
+- }
+- value, _ := strconv.Unquote(lit.Value) // ignore error: AST is well-formed
+- if spec.Doc == nil {
+- return nil, fmt.Errorf("%s: %s lacks doc comment", posn, spec.Names[0].Name)
+- }
+- enumDoc[value] = spec.Doc.Text()
+- for _, d := range internalastutil.Directives(spec.Doc) {
+- if d.Tool == "gopls" && d.Name == "status" {
+- enumStatus[value] = d.Args
+- break
+- }
+- }
+- }
+- }
+- }
+- }
+- }
+- if len(enumDoc) == 0 {
+- return nil, fmt.Errorf("failed to extract any CodeLensSource declarations")
+- }
+-
+- // Build list of Lens descriptors.
+- var lenses []*doc.Lens
+- addAll := func(sources map[settings.CodeLensSource]cache.CodeLensSourceFunc, fileType string) error {
+- for _, source := range slices.Sorted(maps.Keys(sources)) {
+- docText, ok := enumDoc[string(source)]
+- if !ok {
+- return fmt.Errorf("missing CodeLensSource declaration for %s", source)
+- }
+- title, docText, _ := strings.Cut(docText, "\n") // first line is title
+- lenses = append(lenses, &doc.Lens{
+- FileType: fileType,
+- Lens: string(source),
+- Title: title,
+- Doc: docText,
+- Default: defaults[source],
+- Status: enumStatus[string(source)],
+- })
+- }
+- return nil
+- }
+- err := errors.Join(
+- addAll(golang.CodeLensSources(), "Go"),
+- addAll(mod.CodeLensSources(), "go.mod"))
+- return lenses, err
+-}
+-
+-func loadAnalyzers(analyzers []*settings.Analyzer, defaults *settings.Options) []*doc.Analyzer {
+- slices.SortFunc(analyzers, func(x, y *settings.Analyzer) int {
+- return strings.Compare(x.Analyzer().Name, y.Analyzer().Name)
+- })
+- var json []*doc.Analyzer
+- for _, a := range analyzers {
+- json = append(json, &doc.Analyzer{
+- Name: a.Analyzer().Name,
+- Doc: a.Analyzer().Doc,
+- URL: a.Analyzer().URL,
+- Default: a.Enabled(defaults),
+- })
+- }
+- return json
+-}
+-
+-// loadHints derives and returns the inlay hints metadata from the settings.InlayHint type.
+-func loadHints(settingsPkg *packages.Package) ([]*doc.Hint, error) {
+- enums, err := loadEnums(settingsPkg) // TODO(adonovan): call loadEnums exactly once
+- if err != nil {
+- return nil, err
+- }
+- inlayHint := settingsPkg.Types.Scope().Lookup("InlayHint").Type()
+- var hints []*doc.Hint
+- for _, enumVal := range enums[inlayHint] {
+- name, _ := strconv.Unquote(enumVal.Value)
+- hints = append(hints, &doc.Hint{
+- Name: name,
+- Doc: enumVal.Doc,
+- Status: enumVal.Status,
+- })
+- }
+- return hints, nil
+-}
+-
+-func lowerFirst(x string) string {
+- if x == "" {
+- return x
+- }
+- return strings.ToLower(x[:1]) + x[1:]
+-}
+-
+-func fileForPos(pkg *packages.Package, pos token.Pos) (*ast.File, error) {
+- fset := pkg.Fset
+- for _, f := range pkg.Syntax {
+- if safetoken.StartPosition(fset, f.FileStart).Filename == safetoken.StartPosition(fset, pos).Filename {
+- return f, nil
+- }
+- }
+- return nil, fmt.Errorf("no file for pos %v", pos)
+-}
+-
+-func rewriteAPI(_ []byte, api *doc.API) ([]byte, error) {
+- return json.MarshalIndent(api, "", "\t")
+-}
+-
+-type optionsGroup struct {
+- title string // dotted path (e.g. "ui.documentation")
+- final string // final segment of title (e.g. "documentation")
+- level int
+- options []*doc.Option
+-}
+-
+-func rewriteSettings(prevContent []byte, api *doc.API) ([]byte, error) {
+- content := prevContent
+- for category, opts := range api.Options {
+- groups := collectGroups(opts)
+-
+- var buf bytes.Buffer
+-
+- // First, print a table of contents (ToC).
+- fmt.Fprintln(&buf)
+- for _, h := range groups {
+- title := h.final
+- if title != "" {
+- fmt.Fprintf(&buf, "%s* [%s](#%s)\n",
+- strings.Repeat(" ", h.level),
+- capitalize(title),
+- strings.ToLower(title))
+- }
+- }
+-
+- // Section titles are h2, options are h3.
+- // This is independent of the option hierarchy.
+- // (Nested options should not be smaller!)
+- fmt.Fprintln(&buf)
+- for _, h := range groups {
+- title := h.final
+- if title != "" {
+- // Emit HTML anchor as GitHub markdown doesn't support
+- // "# Heading {#anchor}" syntax.
+- fmt.Fprintf(&buf, "\n", strings.ToLower(title))
+-
+- fmt.Fprintf(&buf, "## %s\n\n", capitalize(title))
+- }
+- for _, opt := range h.options {
+- // Emit HTML anchor as GitHub markdown doesn't support
+- // "# Heading {#anchor}" syntax.
+- //
+- // (Each option name is the camelCased name of a field of
+- // settings.UserOptions or one of its FooOptions subfields.)
+- fmt.Fprintf(&buf, "\n", opt.Name)
+-
+- // heading
+- //
+- // We do not display the undocumented dotted-path alias
+- // (h.title + "." + opt.Name) used by VS Code only.
+- fmt.Fprintf(&buf, "### `%s %s`\n\n", opt.Name, opt.Type)
+-
+- // status
+- writeStatus(&buf, opt.Status)
+-
+- // doc comment
+- buf.WriteString(opt.Doc)
+-
+- // enums
+- write := func(name, doc string) {
+- if doc != "" {
+- unbroken := parBreakRE.ReplaceAllString(doc, "\\\n")
+- fmt.Fprintf(&buf, "* %s\n", strings.TrimSpace(unbroken))
+- } else {
+- fmt.Fprintf(&buf, "* `%s`\n", name)
+- }
+- }
+- if len(opt.EnumValues) > 0 && opt.Type == "enum" {
+- // enum as top-level type constructor
+- buf.WriteString("\nMust be one of:\n\n")
+- for _, val := range opt.EnumValues {
+- write(val.Value, val.Doc)
+- }
+- } else if len(opt.EnumKeys.Keys) > 0 && shouldShowEnumKeysInSettings(opt.Name) {
+- // enum as map key (currently just "annotations")
+- buf.WriteString("\nEach enum must be one of:\n\n")
+- for _, val := range opt.EnumKeys.Keys {
+- write(val.Name, val.Doc)
+- }
+- }
+-
+- // default value
+- fmt.Fprintf(&buf, "\nDefault: `%v`.\n\n", opt.Default)
+- }
+- }
+- newContent, err := replaceSection(content, category, buf.Bytes())
+- if err != nil {
+- return nil, err
+- }
+- content = newContent
+- }
+- return content, nil
+-}
+-
+-// writeStatus emits a Markdown paragraph to buf about the status of a feature,
+-// if nonempty.
+-func writeStatus(buf *bytes.Buffer, status string) {
+- switch status {
+- case "":
+- case "advanced":
+- fmt.Fprint(buf, "**This is an advanced setting and should not be configured by most `gopls` users.**\n\n")
+- case "debug":
+- fmt.Fprint(buf, "**This setting is for debugging purposes only.**\n\n")
+- case "experimental":
+- fmt.Fprint(buf, "**This setting is experimental and may be deleted.**\n\n")
+- default:
+- fmt.Fprintf(buf, "**Status: %s.**\n\n", status)
+- }
+-}
+-
+-var parBreakRE = regexp.MustCompile("\n{2,}")
+-
+-func shouldShowEnumKeysInSettings(name string) bool {
+- // These fields have too many possible options,
+- // or too voluminous documentation, to render as enums.
+- // Instead they each get their own page in the manual.
+- return !(name == "analyses" || name == "codelenses" || name == "hints")
+-}
+-
+-func collectGroups(opts []*doc.Option) []optionsGroup {
+- optsByHierarchy := map[string][]*doc.Option{}
+- for _, opt := range opts {
+- optsByHierarchy[opt.Hierarchy] = append(optsByHierarchy[opt.Hierarchy], opt)
+- }
+-
+- // As a hack, assume that uncategorized items are less important to
+- // users and force the empty string to the end of the list.
+- var containsEmpty bool
+- var sorted []string
+- for h := range optsByHierarchy {
+- if h == "" {
+- containsEmpty = true
+- continue
+- }
+- sorted = append(sorted, h)
+- }
+- sort.Strings(sorted)
+- if containsEmpty {
+- sorted = append(sorted, "")
+- }
+- var groups []optionsGroup
+- baseLevel := 0
+- for _, h := range sorted {
+- split := strings.SplitAfter(h, ".")
+- last := split[len(split)-1]
+- // Hack to capitalize all of UI.
+- if last == "ui" {
+- last = "UI"
+- }
+- // A hierarchy may look like "ui.formatting". If "ui" has no
+- // options of its own, it may not be added to the map, but it
+- // still needs a heading.
+- components := strings.Split(h, ".")
+- for i := 1; i < len(components); i++ {
+- parent := strings.Join(components[0:i], ".")
+- if _, ok := optsByHierarchy[parent]; !ok {
+- groups = append(groups, optionsGroup{
+- title: parent,
+- final: last,
+- level: baseLevel + i,
+- })
+- }
+- }
+- groups = append(groups, optionsGroup{
+- title: h,
+- final: last,
+- level: baseLevel + strings.Count(h, "."),
+- options: optsByHierarchy[h],
+- })
+- }
+- return groups
+-}
+-
+-func capitalize(s string) string {
+- return string(unicode.ToUpper(rune(s[0]))) + s[1:]
+-}
+-
+-func rewriteCodeLenses(prevContent []byte, api *doc.API) ([]byte, error) {
+- var buf bytes.Buffer
+- for _, lens := range api.Lenses {
+- fmt.Fprintf(&buf, "## `%s`: %s\n\n", lens.Lens, lens.Title)
+- writeStatus(&buf, lens.Status)
+- fmt.Fprintf(&buf, "%s\n\n", lens.Doc)
+- fmt.Fprintf(&buf, "Default: %v\n\n", onOff(lens.Default))
+- fmt.Fprintf(&buf, "File type: %s\n\n", lens.FileType)
+- }
+- return replaceSection(prevContent, "Lenses", buf.Bytes())
+-}
+-
+-func rewriteAnalyzers(prevContent []byte, api *doc.API) ([]byte, error) {
+- var buf bytes.Buffer
+- for _, analyzer := range api.Analyzers {
+- fmt.Fprintf(&buf, "\n", analyzer.Name)
+- title, doc, _ := strings.Cut(analyzer.Doc, "\n")
+- title = strings.TrimPrefix(title, analyzer.Name+": ")
+- fmt.Fprintf(&buf, "## `%s`: %s\n\n", analyzer.Name, title)
+-
+- // Convert Analyzer.Doc from go/doc/comment form to Markdown.
+- // Headings in doc comments are converted to ### (HeadingLevel=3).
+- //
+- // Some Analyzers (e.g. go/analysis/passes/inline) use ## to indicate subheadings
+- // Although this is valid Markdown, it is not valid go/doc/comment,
+- // nor is it rendered as a subheading by pkg.go.dev or gopls's doc viewer.
+- // Perhaps it will be supported in future; see
+- // https://github.com/golang/go/issues/51082#issuecomment-1033116430 et seq.
+- //
+- // In the meantime, the go/doc/comment processing will escape them so
+- // that the ## appears literally in analyzers.md just as it does in
+- // the two viewers mentioned above. The meaning is clear enough.
+- doctree := new(comment.Parser).Parse(doc)
+- buf.Write((&comment.Printer{HeadingLevel: 3}).Markdown(doctree))
+- buf.WriteString("\n\n")
+-
+- fmt.Fprintf(&buf, "Default: %s.", onOff(analyzer.Default))
+- if !analyzer.Default {
+- fmt.Fprintf(&buf, " Enable by setting `\"analyses\": {\"%s\": true}`.", analyzer.Name)
+- }
+- fmt.Fprintf(&buf, "\n\n")
+- if analyzer.URL != "" {
+- // TODO(adonovan): currently the URL provides the same information
+- // as 'doc' above, though that may change due to
+- // https://github.com/golang/go/issues/61315#issuecomment-1841350181.
+- // In that case, update this to something like "Complete documentation".
+- fmt.Fprintf(&buf, "Package documentation: [%s](%s)\n\n",
+- analyzer.Name, analyzer.URL)
+- }
+-
+- }
+- return replaceSection(prevContent, "Analyzers", buf.Bytes())
+-}
+-
+-func rewriteInlayHints(prevContent []byte, api *doc.API) ([]byte, error) {
+- var buf bytes.Buffer
+- for _, hint := range api.Hints {
+- fmt.Fprintf(&buf, "## **%v**\n\n", hint.Name)
+- fmt.Fprintf(&buf, "%s\n\n", hint.Doc)
+- switch hint.Default {
+- case true:
+- fmt.Fprintf(&buf, "**Enabled by default.**\n\n")
+- case false:
+- fmt.Fprintf(&buf, "**Disabled by default. Enable it by setting `\"hints\": {\"%s\": true}`.**\n\n", hint.Name)
+- }
+- }
+- return replaceSection(prevContent, "Hints", buf.Bytes())
+-}
+-
+-// replaceSection replaces the portion of a file delimited by comments of the form:
+-//
+-//
+-//
+-func replaceSection(content []byte, sectionName string, replacement []byte) ([]byte, error) {
+- re := regexp.MustCompile(fmt.Sprintf(`(?s)\n(.*?)`, sectionName, sectionName))
+- idx := re.FindSubmatchIndex(content)
+- if idx == nil {
+- return nil, fmt.Errorf("could not find section %q", sectionName)
+- }
+- result := slices.Clone(content[:idx[2]])
+- result = append(result, replacement...)
+- result = append(result, content[idx[3]:]...)
+- return result, nil
+-}
+-
+-type onOff bool
+-
+-func (o onOff) String() string {
+- if o {
+- return "on"
+- } else {
+- return "off"
+- }
+-}
+diff -urN a/gopls/internal/doc/generate/generate_test.go b/gopls/internal/doc/generate/generate_test.go
+--- a/gopls/internal/doc/generate/generate_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/doc/generate/generate_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,24 +0,0 @@
+-// Copyright 2020 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package main
+-
+-import (
+- "testing"
+-
+- "golang.org/x/tools/internal/testenv"
+-)
+-
+-func TestGenerated(t *testing.T) {
+- testenv.NeedsGoPackages(t)
+- testenv.NeedsLocalXTools(t)
+-
+- ok, err := doMain(false)
+- if err != nil {
+- t.Fatal(err)
+- }
+- if !ok {
+- t.Error("documentation needs updating. Run: cd gopls && go generate ./...")
+- }
+-}
+diff -urN a/gopls/internal/file/file.go b/gopls/internal/file/file.go
+--- a/gopls/internal/file/file.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/file/file.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,68 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// The file package defines types used for working with LSP files.
+-package file
+-
+-import (
+- "context"
+- "fmt"
+- "time"
+-
+- "golang.org/x/tools/gopls/internal/protocol"
+-)
+-
+-// An Identity identifies the name and contents of a file.
+-//
+-// TODO(rfindley): Identity may not carry its weight. Consider instead just
+-// exposing Handle.Hash, and using an ad-hoc key type where necessary.
+-// Or perhaps if mod/work parsing is moved outside of the memoize cache,
+-// a notion of Identity simply isn't needed.
+-type Identity struct {
+- URI protocol.DocumentURI
+- Hash Hash // digest of file contents
+-}
+-
+-func (id Identity) String() string {
+- return fmt.Sprintf("%s%s", id.URI, id.Hash)
+-}
+-
+-// A FileHandle represents the URI, content, hash, and optional
+-// version of a file tracked by the LSP session.
+-//
+-// File content may be provided by the file system (for Saved files)
+-// or from an overlay, for open files with unsaved edits.
+-// A FileHandle may record an attempt to read a non-existent file,
+-// in which case Content returns an error.
+-type Handle interface {
+- // URI is the URI for this file handle.
+- URI() protocol.DocumentURI
+- // Identity returns an Identity for the file, even if there was an error
+- // reading it.
+- Identity() Identity
+- // SameContentsOnDisk reports whether the file has the same content on disk:
+- // it is false for files open on an editor with unsaved edits.
+- SameContentsOnDisk() bool
+- // Version returns the file version, as defined by the LSP client.
+- // For on-disk file handles, Version returns 0.
+- Version() int32
+- // Content returns the contents of a file.
+- // If the file is not available, returns a nil slice and an error.
+- Content() ([]byte, error)
+- // ModTime reports the modification time of a file.
+- // If the file is not available, returns the zero time and an error.
+- ModTime() (time.Time, error)
+- // String returns the file's path.
+- String() string
+-}
+-
+-// A Source maps URIs to Handles.
+-type Source interface {
+- // ReadFile returns the Handle for a given URI, either by reading the content
+- // of the file or by obtaining it from a cache.
+- //
+- // Invariant: ReadFile must only return an error in the case of context
+- // cancellation. If ctx.Err() is nil, the resulting error must also be nil.
+- ReadFile(ctx context.Context, uri protocol.DocumentURI) (Handle, error)
+-}
+diff -urN a/gopls/internal/file/hash.go b/gopls/internal/file/hash.go
+--- a/gopls/internal/file/hash.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/file/hash.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,33 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package file
+-
+-import (
+- "crypto/sha256"
+- "fmt"
+-)
+-
+-// A Hash is a cryptographic digest of the contents of a file.
+-// (Although at 32B it is larger than a 16B string header, it is smaller
+-// and has better locality than the string header + 64B of hex digits.)
+-type Hash [sha256.Size]byte
+-
+-// HashOf returns the hash of some data.
+-func HashOf(data []byte) Hash {
+- return Hash(sha256.Sum256(data))
+-}
+-
+-// String returns the digest as a string of hex digits.
+-func (h Hash) String() string {
+- return fmt.Sprintf("%64x", [sha256.Size]byte(h))
+-}
+-
+-// XORWith updates *h to *h XOR h2.
+-func (h *Hash) XORWith(h2 Hash) {
+- // Small enough that we don't need crypto/subtle.XORBytes.
+- for i := range h {
+- h[i] ^= h2[i]
+- }
+-}
+diff -urN a/gopls/internal/file/kind.go b/gopls/internal/file/kind.go
+--- a/gopls/internal/file/kind.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/file/kind.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,74 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package file
+-
+-import (
+- "fmt"
+-
+- "golang.org/x/tools/gopls/internal/protocol"
+-)
+-
+-// Kind describes the kind of the file in question.
+-// It can be one of Go,mod, Sum, or Tmpl.
+-type Kind int
+-
+-const (
+- // UnknownKind is a file type we don't know about.
+- UnknownKind = Kind(iota)
+-
+- // Go is a Go source file.
+- Go
+- // Mod is a go.mod file.
+- Mod
+- // Sum is a go.sum file.
+- Sum
+- // Tmpl is a template file.
+- Tmpl
+- // Work is a go.work file.
+- Work
+- // Asm is a Go assembly (.s) file.
+- Asm
+-)
+-
+-func (k Kind) String() string {
+- switch k {
+- case Go:
+- return "go"
+- case Mod:
+- return "go.mod"
+- case Sum:
+- return "go.sum"
+- case Tmpl:
+- return "tmpl"
+- case Work:
+- return "go.work"
+- case Asm:
+- return "Go assembly"
+- default:
+- return fmt.Sprintf("internal error: unknown file kind %d", k)
+- }
+-}
+-
+-// KindForLang returns the gopls file [Kind] associated with the given LSP
+-// LanguageKind string from the LanguageID field of [protocol.TextDocumentItem],
+-// or UnknownKind if the language is not one recognized by gopls.
+-func KindForLang(langID protocol.LanguageKind) Kind {
+- switch langID {
+- case "go":
+- return Go
+- case "go.mod":
+- return Mod
+- case "go.sum":
+- return Sum
+- case "tmpl", "gotmpl":
+- return Tmpl
+- case "go.work":
+- return Work
+- case "go.s":
+- return Asm
+- default:
+- return UnknownKind
+- }
+-}
+diff -urN a/gopls/internal/file/modification.go b/gopls/internal/file/modification.go
+--- a/gopls/internal/file/modification.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/file/modification.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,57 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package file
+-
+-import "golang.org/x/tools/gopls/internal/protocol"
+-
+-// Modification represents a modification to a file.
+-type Modification struct {
+- URI protocol.DocumentURI
+- Action Action
+-
+- // OnDisk is true if a watched file is changed on disk.
+- // If true, Version will be -1 and Text will be nil.
+- OnDisk bool
+-
+- // Version will be -1 and Text will be nil when they are not supplied,
+- // specifically on textDocument/didClose and for on-disk changes.
+- Version int32
+- Text []byte
+-
+- // LanguageID is only sent from the language client on textDocument/didOpen.
+- LanguageID protocol.LanguageKind
+-}
+-
+-// An Action is a type of file state change.
+-type Action int
+-
+-const (
+- UnknownAction = Action(iota)
+- Open
+- Change
+- Close
+- Save
+- Create
+- Delete
+-)
+-
+-func (a Action) String() string {
+- switch a {
+- case Open:
+- return "Open"
+- case Change:
+- return "Change"
+- case Close:
+- return "Close"
+- case Save:
+- return "Save"
+- case Create:
+- return "Create"
+- case Delete:
+- return "Delete"
+- default:
+- return "Unknown"
+- }
+-}
+diff -urN a/gopls/internal/filecache/filecache.go b/gopls/internal/filecache/filecache.go
+--- a/gopls/internal/filecache/filecache.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/filecache/filecache.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,620 +0,0 @@
+-// Copyright 2022 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// The filecache package provides a file-based shared durable blob cache.
+-//
+-// The cache is a machine-global mapping from (kind string, key
+-// [32]byte) to []byte, where kind is an identifier describing the
+-// namespace or purpose (e.g. "analysis"), and key is a SHA-256 digest
+-// of the recipe of the value. (It need not be the digest of the value
+-// itself, so you can query the cache without knowing what value the
+-// recipe would produce.)
+-//
+-// The space budget of the cache can be controlled by [SetBudget].
+-// Cache entries may be evicted at any time or in any order.
+-// Note that "du -sh $GOPLSCACHE" may report a disk usage
+-// figure that is rather larger (e.g. 50%) than the budget because
+-// it rounds up partial disk blocks.
+-//
+-// The Get and Set operations are concurrency-safe.
+-package filecache
+-
+-import (
+- "bytes"
+- "crypto/sha256"
+- "encoding/hex"
+- "encoding/json"
+- "errors"
+- "fmt"
+- "io"
+- "io/fs"
+- "log"
+- "os"
+- "path/filepath"
+- "sort"
+- "strings"
+- "sync"
+- "sync/atomic"
+- "time"
+-
+- "golang.org/x/tools/gopls/internal/util/bug"
+- "golang.org/x/tools/gopls/internal/util/lru"
+-)
+-
+-// Start causes the filecache to initialize and start garbage gollection.
+-//
+-// Start is automatically called by the first call to Get, but may be called
+-// explicitly to pre-initialize the cache.
+-func Start() {
+- go getCacheDir()
+-}
+-
+-// As an optimization, use a 100MB in-memory LRU cache in front of filecache
+-// operations. This reduces I/O for operations such as diagnostics or
+-// implementations that repeatedly access the same cache entries.
+-var memCache = lru.New[memKey, []byte](100 * 1e6)
+-
+-type memKey struct {
+- kind string
+- key [32]byte
+-}
+-
+-// Get retrieves from the cache and returns the value most recently
+-// supplied to Set(kind, key), possibly by another process.
+-//
+-// Get returns ErrNotFound if the value was not found. The first call
+-// to Get may fail due to ENOSPC or deletion of the process's
+-// executable. Other causes of failure include deletion or corruption
+-// of the cache (by external meddling) while gopls is running, or
+-// faulty hardware; see issue #67433.
+-//
+-// Callers should not modify the returned array.
+-func Get(kind string, key [32]byte) ([]byte, error) {
+- // First consult the read-through memory cache.
+- // Note that memory cache hits do not update the times
+- // used for LRU eviction of the file-based cache.
+- if value, ok := memCache.Get(memKey{kind, key}); ok {
+- return value, nil
+- }
+-
+- iolimit <- struct{}{} // acquire a token
+- defer func() { <-iolimit }() // release a token
+-
+- // Read the index file, which provides the name of the CAS file.
+- indexName, err := filename(kind, key)
+- if err != nil {
+- // e.g. ENOSPC, deletion of executable (first time only);
+- // deletion of cache (at any time).
+- return nil, err
+- }
+- indexData, err := os.ReadFile(indexName)
+- if err != nil {
+- if errors.Is(err, os.ErrNotExist) {
+- return nil, ErrNotFound
+- }
+- return nil, err
+- }
+- var valueHash [32]byte
+- if copy(valueHash[:], indexData) != len(valueHash) {
+- return nil, ErrNotFound // index entry has wrong length
+- }
+-
+- // Read the CAS file and check its contents match.
+- //
+- // This ensures integrity in all cases (corrupt or truncated
+- // file, short read, I/O error, wrong length, etc) except an
+- // engineered hash collision, which is infeasible.
+- casName, err := filename(casKind, valueHash)
+- if err != nil {
+- return nil, err // see above for possible causes
+- }
+- value, _ := os.ReadFile(casName) // ignore error
+- if sha256.Sum256(value) != valueHash {
+- return nil, ErrNotFound // CAS file is missing or has wrong contents
+- }
+-
+- // Update file times used by LRU eviction.
+- //
+- // Because this turns a read into a write operation,
+- // we follow the approach used in the go command's
+- // cache and update the access time only if the
+- // existing timestamp is older than one hour.
+- //
+- // (Traditionally the access time would be updated
+- // automatically, but for efficiency most POSIX systems have
+- // for many years set the noatime mount option to avoid every
+- // open or read operation entailing a metadata write.)
+- now := time.Now()
+- touch := func(filename string) {
+- st, err := os.Stat(filename)
+- if err == nil && now.Sub(st.ModTime()) > time.Hour {
+- os.Chtimes(filename, now, now) // ignore error
+- }
+- }
+- touch(indexName)
+- touch(casName)
+-
+- memCache.Set(memKey{kind, key}, value, len(value))
+-
+- return value, nil
+-}
+-
+-// ErrNotFound is the distinguished error
+-// returned by Get when the key is not found.
+-var ErrNotFound = fmt.Errorf("not found")
+-
+-// Set updates the value in the cache.
+-//
+-// Set may fail due to:
+-// - failure to access/create the cache (first call only);
+-// - out of space (ENOSPC);
+-// - deletion of the cache concurrent with a call to Set;
+-// - faulty hardware.
+-// See issue #67433.
+-func Set(kind string, key [32]byte, value []byte) error {
+- memCache.Set(memKey{kind, key}, value, len(value))
+-
+- // Set the active event to wake up the GC.
+- select {
+- case active <- struct{}{}:
+- default:
+- }
+-
+- iolimit <- struct{}{} // acquire a token
+- defer func() { <-iolimit }() // release a token
+-
+- // First, add the value to the content-
+- // addressable store (CAS), if not present.
+- hash := sha256.Sum256(value)
+- casName, err := filename(casKind, hash)
+- if err != nil {
+- return err
+- }
+- // Does CAS file exist and have correct (complete) content?
+- // TODO(adonovan): opt: use mmap for this check.
+- if prev, _ := os.ReadFile(casName); !bytes.Equal(prev, value) {
+- if err := os.MkdirAll(filepath.Dir(casName), 0700); err != nil {
+- return err
+- }
+- // Avoiding O_TRUNC here is merely an optimization to avoid
+- // cache misses when two threads race to write the same file.
+- if err := writeFileNoTrunc(casName, value, 0600); err != nil {
+- os.Remove(casName) // ignore error
+- return err // e.g. disk full
+- }
+- }
+-
+- // Now write an index entry that refers to the CAS file.
+- indexName, err := filename(kind, key)
+- if err != nil {
+- return err
+- }
+- if err := os.MkdirAll(filepath.Dir(indexName), 0700); err != nil {
+- return err
+- }
+- if err := writeFileNoTrunc(indexName, hash[:], 0600); err != nil {
+- os.Remove(indexName) // ignore error
+- return err // e.g. disk full
+- }
+-
+- return nil
+-}
+-
+-// The active 1-channel is a selectable resettable event
+-// indicating recent cache activity.
+-var active = make(chan struct{}, 1)
+-
+-// writeFileNoTrunc is like os.WriteFile but doesn't truncate until
+-// after the write, so that racing writes of the same data are idempotent.
+-func writeFileNoTrunc(filename string, data []byte, perm os.FileMode) error {
+- f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, perm)
+- if err != nil {
+- return err
+- }
+- _, err = f.Write(data)
+- if err == nil {
+- err = f.Truncate(int64(len(data)))
+- }
+- if closeErr := f.Close(); err == nil {
+- err = closeErr
+- }
+- return err
+-}
+-
+-// reserved kind strings
+-const (
+- casKind = "cas" // content-addressable store files
+- bugKind = "bug" // gopls bug reports
+-)
+-
+-var iolimit = make(chan struct{}, 128) // counting semaphore to limit I/O concurrency in Set.
+-
+-var budget int64 = 1e9 // 1GB
+-
+-// SetBudget sets a soft limit on disk usage of regular files in the
+-// cache (in bytes) and returns the previous value. Supplying a
+-// negative value queries the current value without changing it.
+-//
+-// If two gopls processes have different budgets, the one with the
+-// lower budget will collect garbage more actively, but both will
+-// observe the effect.
+-//
+-// Even in the steady state, the storage usage reported by the 'du'
+-// command may exceed the budget by as much as a factor of 3 due to
+-// the overheads of directories and the effects of block quantization,
+-// which are especially pronounced for the small index files.
+-func SetBudget(new int64) (old int64) {
+- if new < 0 {
+- return atomic.LoadInt64(&budget)
+- }
+- return atomic.SwapInt64(&budget, new)
+-}
+-
+-// --- implementation ----
+-
+-// filename returns the name of the cache file of the specified kind and key.
+-//
+-// A typical cache file has a name such as:
+-//
+-// $HOME/Library/Caches / gopls / VVVVVVVV / KK / KKKK...KKKK - kind
+-//
+-// The portions separated by spaces are as follows:
+-// - The user's preferred cache directory; the default value varies by OS.
+-// - The constant "gopls".
+-// - The "version", 32 bits of the digest of the gopls executable.
+-// - The first 8 bits of the key, to avoid huge directories.
+-// - The full 256 bits of the key.
+-// - The kind or purpose of this cache file (e.g. "analysis").
+-//
+-// The kind establishes a namespace for the keys. It is represented as
+-// a suffix, not a segment, as this significantly reduces the number
+-// of directories created, and thus the storage overhead.
+-//
+-// Previous iterations of the design aimed for the invariant that once
+-// a file is written, its contents are never modified, though it may
+-// be atomically replaced or removed. However, not all platforms have
+-// an atomic rename operation (our first approach), and file locking
+-// (our second) is a notoriously fickle mechanism.
+-//
+-// The current design instead exploits a trick from the cache
+-// implementation used by the go command: writes of small files are in
+-// practice atomic (all or nothing) on all platforms.
+-// (See GOROOT/src/cmd/go/internal/cache/cache.go.)
+-//
+-// Russ Cox notes: "all file systems use an rwlock around every file
+-// system block, including data blocks, so any writes or reads within
+-// the same block are going to be handled atomically by the FS
+-// implementation without any need to request file locking explicitly.
+-// And since the files are so small, there's only one block. (A block
+-// is at minimum 512 bytes, usually much more.)" And: "all modern file
+-// systems protect against [partial writes due to power loss] with
+-// journals."
+-//
+-// We use a two-level scheme consisting of an index and a
+-// content-addressable store (CAS). A single cache entry consists of
+-// two files. The value of a cache entry is written into the file at
+-// filename("cas", sha256(value)). Since the value may be arbitrarily
+-// large, this write is not atomic. That means we must check the
+-// integrity of the contents read back from the CAS to make sure they
+-// hash to the expected key. If the CAS file is incomplete or
+-// inconsistent, we proceed as if it were missing.
+-//
+-// Once the CAS file has been written, we write a small fixed-size
+-// index file at filename(kind, key), using the values supplied by the
+-// caller. The index file contains the hash that identifies the value
+-// file in the CAS. (We could add extra metadata to this file, up to
+-// 512B, the minimum size of a disk block, if later desired, so long
+-// as the total size remains fixed.) Because the index file is small,
+-// concurrent writes to it are atomic in practice, even though this is
+-// not guaranteed by any OS. The fixed size ensures that readers can't
+-// see a palimpsest when a short new file overwrites a longer old one.
+-//
+-// New versions of gopls are free to reorganize the contents of the
+-// version directory as needs evolve. But all versions of gopls must
+-// in perpetuity treat the "gopls" directory in a common fashion.
+-//
+-// In particular, each gopls process attempts to garbage collect
+-// the entire gopls directory so that newer binaries can clean up
+-// after older ones: in the development cycle especially, new
+-// versions may be created frequently.
+-func filename(kind string, key [32]byte) (string, error) {
+- base := fmt.Sprintf("%x-%s", key, kind)
+- dir, err := getCacheDir()
+- if err != nil {
+- return "", err
+- }
+- // Keep the BugReports function consistent with this one.
+- return filepath.Join(dir, base[:2], base), nil
+-}
+-
+-// getCacheDir returns the persistent cache directory of all processes
+-// running this version of the gopls executable.
+-//
+-// It must incorporate the hash of the executable so that we needn't
+-// worry about incompatible changes to the file format or changes to
+-// the algorithm that produced the index.
+-func getCacheDir() (string, error) {
+- cacheDirOnce.Do(func() {
+- // Use user's preferred cache directory.
+- userDir := os.Getenv("GOPLSCACHE")
+- if userDir == "" {
+- var err error
+- userDir, err = os.UserCacheDir()
+- if err != nil {
+- userDir = os.TempDir()
+- }
+- }
+- goplsDir := filepath.Join(userDir, "gopls")
+-
+- // UserCacheDir may return a nonexistent directory
+- // (in which case we must create it, which may fail),
+- // or it may return a non-writable directory, in
+- // which case we should ideally respect the user's express
+- // wishes (e.g. XDG_CACHE_HOME) and not write somewhere else.
+- // Sadly UserCacheDir doesn't currently let us distinguish
+- // such intent from accidental misconfiguraton such as HOME=/
+- // in a CI builder. So, we check whether the gopls subdirectory
+- // can be created (or already exists) and not fall back to /tmp.
+- // See also https://github.com/golang/go/issues/57638.
+- if os.MkdirAll(goplsDir, 0700) != nil {
+- goplsDir = filepath.Join(os.TempDir(), "gopls")
+- }
+-
+- // Start the garbage collector.
+- go gc(goplsDir)
+-
+- // Compute the hash of this executable (~20ms) and create a subdirectory.
+- hash, err := hashExecutable()
+- if err != nil {
+- cacheDirErr = fmt.Errorf("can't hash gopls executable: %w", err)
+- }
+- // Use only 32 bits of the digest to avoid unwieldy filenames.
+- // It's not an adversarial situation.
+- cacheDir = filepath.Join(goplsDir, fmt.Sprintf("%x", hash[:4]))
+- if err := os.MkdirAll(cacheDir, 0700); err != nil {
+- cacheDirErr = fmt.Errorf("can't create cache: %w", err)
+- }
+- })
+- return cacheDir, cacheDirErr
+-}
+-
+-var (
+- cacheDirOnce sync.Once
+- cacheDir string
+- cacheDirErr error
+-)
+-
+-func hashExecutable() (hash [32]byte, err error) {
+- exe, err := os.Executable()
+- if err != nil {
+- return hash, err
+- }
+- f, err := os.Open(exe)
+- if err != nil {
+- return hash, err
+- }
+- defer f.Close()
+- h := sha256.New()
+- if _, err := io.Copy(h, f); err != nil {
+- return hash, fmt.Errorf("can't read executable: %w", err)
+- }
+- h.Sum(hash[:0])
+- return hash, nil
+-}
+-
+-// gc runs forever, periodically deleting files from the gopls
+-// directory until the space budget is no longer exceeded, and also
+-// deleting files older than the maximum age, regardless of budget.
+-//
+-// One gopls process may delete garbage created by a different gopls
+-// process, possibly running a different version of gopls, possibly
+-// running concurrently.
+-func gc(goplsDir string) {
+- // period between collections
+- //
+- // Originally the period was always 1 minute, but this
+- // consumed 15% of a CPU core when idle (#61049).
+- //
+- // The reason for running collections even when idle is so
+- // that long lived gopls sessions eventually clean up the
+- // caches created by defunct executables.
+- const (
+- minPeriod = 5 * time.Minute // when active
+- maxPeriod = 6 * time.Hour // when idle
+- )
+-
+- // Sleep statDelay*batchSize between stats to smooth out I/O.
+- //
+- // The constants below were chosen using the following heuristics:
+- // - 1GB of filecache is on the order of ~100-200k files, in which case
+- // 100μs delay per file introduces 10-20s of additional walk time,
+- // less than the minPeriod.
+- // - Processing batches of stats at once is much more efficient than
+- // sleeping after every stat (due to OS optimizations).
+- const statDelay = 100 * time.Microsecond // average delay between stats, to smooth out I/O
+- const batchSize = 1000 // # of stats to process before sleeping
+- const maxAge = 5 * 24 * time.Hour // max time since last access before file is deleted
+-
+- // The macOS filesystem is strikingly slow, at least on some machines.
+- // /usr/bin/find achieves only about 25,000 stats per second
+- // at full speed (no pause between items), meaning a large
+- // cache may take several minutes to scan.
+- //
+- // (gopls' caches should never actually get this big in
+- // practice: the example mentioned above resulted from a bug
+- // that caused filecache to fail to delete any files.)
+-
+- const debug = false
+-
+- // Names of all directories found in first pass; nil thereafter.
+- dirs := make(map[string]bool)
+-
+- for {
+- // Wait unconditionally for the minimum period.
+- // We do this even on the first run so that tests
+- // don't (all) run the GC.
+- time.Sleep(minPeriod)
+-
+- // Enumerate all files in the cache.
+- type item struct {
+- path string
+- mtime time.Time
+- size int64
+- }
+- var files []item
+- start := time.Now()
+- var total int64 // bytes
+- _ = filepath.Walk(goplsDir, func(path string, stat os.FileInfo, err error) error {
+- if err != nil {
+- return nil // ignore errors
+- }
+- if stat.IsDir() {
+- // Collect (potentially empty) directories.
+- if dirs != nil {
+- dirs[path] = true
+- }
+- } else {
+- // Unconditionally delete files we haven't used in ages.
+- age := time.Since(stat.ModTime())
+- if age > maxAge {
+- if debug {
+- log.Printf("age: deleting stale file %s (%dB, age %v)",
+- path, stat.Size(), age)
+- }
+- os.Remove(path) // ignore error
+- } else {
+- files = append(files, item{path, stat.ModTime(), stat.Size()})
+- total += stat.Size()
+- if debug && len(files)%1000 == 0 {
+- log.Printf("filecache: checked %d files in %v", len(files), time.Since(start))
+- }
+- if len(files)%batchSize == 0 {
+- time.Sleep(batchSize * statDelay)
+- }
+- }
+- }
+- return nil
+- })
+-
+- // Sort oldest files first.
+- sort.Slice(files, func(i, j int) bool {
+- return files[i].mtime.Before(files[j].mtime)
+- })
+-
+- // Delete oldest files until we're under budget.
+- budget := atomic.LoadInt64(&budget)
+- for _, file := range files {
+- if total < budget {
+- break
+- }
+- if debug {
+- age := time.Since(file.mtime)
+- log.Printf("budget: deleting stale file %s (%dB, age %v)",
+- file.path, file.size, age)
+- }
+- os.Remove(file.path) // ignore error
+- total -= file.size
+- }
+- files = nil // release memory before sleep
+-
+- // Once only, delete all directories.
+- // This will succeed only for the empty ones,
+- // and ensures that stale directories (whose
+- // files have been deleted) are removed eventually.
+- // They don't take up much space but they do slow
+- // down the traversal.
+- //
+- // We do this after the sleep to minimize the
+- // race against Set, which may create a directory
+- // that is momentarily empty.
+- //
+- // (Test processes don't live that long, so
+- // this may not be reached on the CI builders.)
+- if dirs != nil {
+- dirnames := make([]string, 0, len(dirs))
+- for dir := range dirs {
+- dirnames = append(dirnames, dir)
+- }
+- dirs = nil
+-
+- // Descending length order => children before parents.
+- sort.Slice(dirnames, func(i, j int) bool {
+- return len(dirnames[i]) > len(dirnames[j])
+- })
+- var deleted int
+- for _, dir := range dirnames {
+- if os.Remove(dir) == nil { // ignore error
+- deleted++
+- }
+- }
+- if debug {
+- log.Printf("deleted %d empty directories", deleted)
+- }
+- }
+-
+- // Wait up to the max period,
+- // or for Set activity in this process.
+- select {
+- case <-active:
+- case <-time.After(maxPeriod):
+- }
+- }
+-}
+-
+-func init() {
+- // Register a handler to durably record this process's first
+- // assertion failure in the cache so that we can ask users to
+- // share this information via the stats command.
+- bug.Handle(func(bug bug.Bug) {
+- // Wait for cache init (bugs in tests happen early).
+- _, _ = getCacheDir()
+-
+- data, err := json.Marshal(bug)
+- if err != nil {
+- panic(fmt.Sprintf("error marshalling bug %+v: %v", bug, err))
+- }
+-
+- key := sha256.Sum256(data)
+- _ = Set(bugKind, key, data)
+- })
+-}
+-
+-// BugReports returns a new unordered array of the contents
+-// of all cached bug reports produced by this executable.
+-// It also returns the location of the cache directory
+-// used by this process (or "" on initialization error).
+-func BugReports() (string, []bug.Bug) {
+- // To test this logic, run:
+- // $ TEST_GOPLS_BUG=oops gopls bug # trigger a bug
+- // $ gopls stats # list the bugs
+-
+- dir, err := getCacheDir()
+- if err != nil {
+- return "", nil // ignore initialization errors
+- }
+- var result []bug.Bug
+- _ = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
+- if err != nil {
+- return nil // ignore readdir/stat errors
+- }
+- // Parse the key from each "XXXX-bug" cache file name.
+- if !info.IsDir() && strings.HasSuffix(path, bugKind) {
+- var key [32]byte
+- n, err := hex.Decode(key[:], []byte(filepath.Base(path)[:len(key)*2]))
+- if err != nil || n != len(key) {
+- return nil // ignore malformed file names
+- }
+- content, err := Get(bugKind, key)
+- if err == nil { // ignore read errors
+- var b bug.Bug
+- if err := json.Unmarshal(content, &b); err != nil {
+- log.Printf("error marshalling bug %q: %v", string(content), err)
+- }
+- result = append(result, b)
+- }
+- }
+- return nil
+- })
+- return dir, result
+-}
+diff -urN a/gopls/internal/filecache/filecache_test.go b/gopls/internal/filecache/filecache_test.go
+--- a/gopls/internal/filecache/filecache_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/filecache/filecache_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,264 +0,0 @@
+-// Copyright 2022 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package filecache_test
+-
+-// This file defines tests of the API of the filecache package.
+-//
+-// Some properties (e.g. garbage collection) cannot be exercised
+-// through the API, so this test does not attempt to do so.
+-
+-import (
+- "bytes"
+- cryptorand "crypto/rand"
+- "fmt"
+- "log"
+- mathrand "math/rand"
+- "os"
+- "os/exec"
+- "strconv"
+- "strings"
+- "testing"
+-
+- "golang.org/x/sync/errgroup"
+- "golang.org/x/tools/gopls/internal/filecache"
+- "golang.org/x/tools/internal/testenv"
+-)
+-
+-func TestBasics(t *testing.T) {
+- const kind = "TestBasics"
+- key := uniqueKey() // never used before
+- value := []byte("hello")
+-
+- // Get of a never-seen key returns not found.
+- if _, err := filecache.Get(kind, key); err != filecache.ErrNotFound {
+- if strings.Contains(err.Error(), "operation not supported") ||
+- strings.Contains(err.Error(), "not implemented") {
+- t.Skipf("skipping: %v", err)
+- }
+- t.Errorf("Get of random key returned err=%q, want not found", err)
+- }
+-
+- // Set of a never-seen key and a small value succeeds.
+- if err := filecache.Set(kind, key, value); err != nil {
+- t.Errorf("Set failed: %v", err)
+- }
+-
+- // Get of the key returns a copy of the value.
+- if got, err := filecache.Get(kind, key); err != nil {
+- t.Errorf("Get after Set failed: %v", err)
+- } else if string(got) != string(value) {
+- t.Errorf("Get after Set returned different value: got %q, want %q", got, value)
+- }
+-
+- // The kind is effectively part of the key.
+- if _, err := filecache.Get("different-kind", key); err != filecache.ErrNotFound {
+- t.Errorf("Get with wrong kind returned err=%q, want not found", err)
+- }
+-}
+-
+-// TestConcurrency exercises concurrent access to the same entry.
+-func TestConcurrency(t *testing.T) {
+- if os.Getenv("GO_BUILDER_NAME") == "plan9-arm" {
+- t.Skip(`skipping on plan9-arm builder due to golang/go#58748: failing with 'mount rpc error'`)
+- }
+- const kind = "TestConcurrency"
+- key := uniqueKey()
+- const N = 100 // concurrency level
+-
+- // Construct N distinct values, each larger
+- // than a typical 4KB OS file buffer page.
+- var values [N][8192]byte
+- for i := range values {
+- if _, err := mathrand.Read(values[i][:]); err != nil {
+- t.Fatalf("rand: %v", err)
+- }
+- }
+-
+- // get calls Get and verifies that the cache entry
+- // matches one of the values passed to Set.
+- get := func(mustBeFound bool) error {
+- got, err := filecache.Get(kind, key)
+- if err != nil {
+- if err == filecache.ErrNotFound && !mustBeFound {
+- return nil // not found
+- }
+- return err
+- }
+- for _, want := range values {
+- if bytes.Equal(want[:], got) {
+- return nil // a match
+- }
+- }
+- return fmt.Errorf("Get returned a value that was never Set")
+- }
+-
+- // Perform N concurrent calls to Set and Get.
+- // All sets must succeed.
+- // All gets must return nothing, or one of the Set values;
+- // there is no third possibility.
+- var group errgroup.Group
+- for i := range values {
+- group.Go(func() error { return filecache.Set(kind, key, values[i][:]) })
+- group.Go(func() error { return get(false) })
+- }
+- if err := group.Wait(); err != nil {
+- if strings.Contains(err.Error(), "operation not supported") ||
+- strings.Contains(err.Error(), "not implemented") {
+- t.Skipf("skipping: %v", err)
+- }
+- t.Fatal(err)
+- }
+-
+- // A final Get must report one of the values that was Set.
+- if err := get(true); err != nil {
+- t.Fatalf("final Get failed: %v", err)
+- }
+-}
+-
+-const (
+- testIPCKind = "TestIPC"
+- testIPCValueA = "hello"
+- testIPCValueB = "world"
+-)
+-
+-// TestIPC exercises interprocess communication through the cache.
+-// It calls Set(A) in the parent, { Get(A); Set(B) } in the child
+-// process, then Get(B) in the parent.
+-func TestIPC(t *testing.T) {
+- testenv.NeedsExec(t)
+-
+- keyA := uniqueKey()
+- keyB := uniqueKey()
+- value := []byte(testIPCValueA)
+-
+- // Set keyA.
+- if err := filecache.Set(testIPCKind, keyA, value); err != nil {
+- if strings.Contains(err.Error(), "operation not supported") {
+- t.Skipf("skipping: %v", err)
+- }
+- t.Fatalf("Set: %v", err)
+- }
+-
+- // Call ipcChild in a child process,
+- // passing it the keys in the environment
+- // (quoted, to avoid NUL termination of C strings).
+- // It will Get(A) then Set(B).
+- cmd := exec.Command(os.Args[0], os.Args[1:]...)
+- cmd.Env = append(os.Environ(),
+- "ENTRYPOINT=ipcChild",
+- fmt.Sprintf("KEYA=%q", keyA),
+- fmt.Sprintf("KEYB=%q", keyB))
+- cmd.Stdout = os.Stderr
+- cmd.Stderr = os.Stderr
+- if err := cmd.Run(); err != nil {
+- t.Fatal(err)
+- }
+-
+- // Verify keyB.
+- got, err := filecache.Get(testIPCKind, keyB)
+- if err != nil {
+- t.Fatal(err)
+- }
+- if string(got) != "world" {
+- t.Fatalf("Get(keyB) = %q, want %q", got, "world")
+- }
+-}
+-
+-// We define our own main function so that portions of
+-// some tests can run in a separate (child) process.
+-func TestMain(m *testing.M) {
+- switch os.Getenv("ENTRYPOINT") {
+- case "ipcChild":
+- ipcChild()
+- default:
+- os.Exit(m.Run())
+- }
+-}
+-
+-// ipcChild is the portion of TestIPC that runs in a child process.
+-func ipcChild() {
+- getenv := func(name string) (key [32]byte) {
+- s, _ := strconv.Unquote(os.Getenv(name))
+- copy(key[:], []byte(s))
+- return
+- }
+-
+- // Verify key A.
+- got, err := filecache.Get(testIPCKind, getenv("KEYA"))
+- if err != nil || string(got) != testIPCValueA {
+- log.Fatalf("child: Get(key) = %q, %v; want %q", got, err, testIPCValueA)
+- }
+-
+- // Set key B.
+- if err := filecache.Set(testIPCKind, getenv("KEYB"), []byte(testIPCValueB)); err != nil {
+- log.Fatalf("child: Set(keyB) failed: %v", err)
+- }
+-}
+-
+-// uniqueKey returns a key that has never been used before.
+-func uniqueKey() (key [32]byte) {
+- if _, err := cryptorand.Read(key[:]); err != nil {
+- log.Fatalf("rand: %v", err)
+- }
+- return
+-}
+-
+-func BenchmarkUncontendedGet(b *testing.B) {
+- const kind = "BenchmarkUncontendedGet"
+- key := uniqueKey()
+-
+- var value [8192]byte
+- if _, err := mathrand.Read(value[:]); err != nil {
+- b.Fatalf("rand: %v", err)
+- }
+- if err := filecache.Set(kind, key, value[:]); err != nil {
+- b.Fatal(err)
+- }
+-
+- b.SetBytes(int64(len(value)))
+-
+- var group errgroup.Group
+- group.SetLimit(50)
+- for b.Loop() {
+- group.Go(func() error {
+- _, err := filecache.Get(kind, key)
+- return err
+- })
+- }
+- if err := group.Wait(); err != nil {
+- b.Fatal(err)
+- }
+-}
+-
+-// These two benchmarks are asymmetric: the one for Get imposes a
+-// modest bound on concurrency (50) whereas the one for Set imposes a
+-// much higher concurrency (1000) to test the implementation's
+-// self-imposed bound.
+-
+-func BenchmarkUncontendedSet(b *testing.B) {
+- const kind = "BenchmarkUncontendedSet"
+- key := uniqueKey()
+- var value [8192]byte
+-
+- const P = 1000 // parallelism
+- b.SetBytes(P * int64(len(value)))
+-
+- for b.Loop() {
+- // Perform P concurrent calls to Set. All must succeed.
+- var group errgroup.Group
+- for range [P]bool{} {
+- group.Go(func() error {
+- return filecache.Set(kind, key, value[:])
+- })
+- }
+- if err := group.Wait(); err != nil {
+- if strings.Contains(err.Error(), "operation not supported") ||
+- strings.Contains(err.Error(), "not implemented") {
+- b.Skipf("skipping: %v", err)
+- }
+- b.Fatal(err)
+- }
+- }
+-}
+diff -urN a/gopls/internal/filewatcher/export_test.go b/gopls/internal/filewatcher/export_test.go
+--- a/gopls/internal/filewatcher/export_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/filewatcher/export_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,13 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package filewatcher
+-
+-// This file defines things (and opens backdoors) needed only by tests.
+-
+-// SetAfterAddHook sets a hook to be called after a path is added to the watcher.
+-// This is used in tests to inspect the error returned by the underlying watcher.
+-func SetAfterAddHook(f func(string, error)) {
+- afterAddHook = f
+-}
+diff -urN a/gopls/internal/filewatcher/filewatcher.go b/gopls/internal/filewatcher/filewatcher.go
+--- a/gopls/internal/filewatcher/filewatcher.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/filewatcher/filewatcher.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,420 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package filewatcher
+-
+-import (
+- "errors"
+- "io/fs"
+- "log/slog"
+- "os"
+- "path/filepath"
+- "strings"
+- "sync"
+- "time"
+-
+- "github.com/fsnotify/fsnotify"
+- "golang.org/x/tools/gopls/internal/protocol"
+-)
+-
+-// ErrClosed is used when trying to operate on a closed Watcher.
+-var ErrClosed = errors.New("file watcher: watcher already closed")
+-
+-// Watcher collects events from a [fsnotify.Watcher] and converts them into
+-// batched LSP [protocol.FileEvent]s.
+-type Watcher struct {
+- logger *slog.Logger
+-
+- stop chan struct{} // closed by Close to terminate run loop
+-
+- // errs is an internal channel for surfacing errors from the file watcher,
+- // distinct from the fsnotify watcher's error channel.
+- errs chan error
+-
+- runners sync.WaitGroup // counts the number of active run goroutines (max 1)
+-
+- watcher *fsnotify.Watcher
+-
+- mu sync.Mutex // guards all fields below
+-
+- // watchers counts the number of active watch registration goroutines,
+- // including their error handling.
+- // After [Watcher.Close] called, watchers's counter will no longer increase.
+- watchers sync.WaitGroup
+-
+- // dirCancel maps a directory path to its cancellation channel.
+- // A nil map indicates the watcher is closing and prevents new directory
+- // watch registrations.
+- dirCancel map[string]chan struct{}
+-
+- // events is the current batch of unsent file events, which will be sent
+- // when the timer expires.
+- events []protocol.FileEvent
+-}
+-
+-// New creates a new file watcher and starts its event-handling loop. The
+-// [Watcher.Close] method must be called to clean up resources.
+-//
+-// The provided handler is called sequentially with either a batch of file
+-// events or an error. Events and errors may be interleaved. The watcher blocks
+-// until the handler returns, so the handler should be fast and non-blocking.
+-func New(delay time.Duration, logger *slog.Logger, handler func([]protocol.FileEvent, error)) (*Watcher, error) {
+- watcher, err := fsnotify.NewWatcher()
+- if err != nil {
+- return nil, err
+- }
+- w := &Watcher{
+- logger: logger,
+- watcher: watcher,
+- dirCancel: make(map[string]chan struct{}),
+- errs: make(chan error),
+- stop: make(chan struct{}),
+- }
+-
+- w.runners.Add(1)
+- go w.run(delay, handler)
+-
+- return w, nil
+-}
+-
+-// run is the main event-handling loop for the watcher. It should be run in a
+-// separate goroutine.
+-func (w *Watcher) run(delay time.Duration, handler func([]protocol.FileEvent, error)) {
+- defer w.runners.Done()
+-
+- // timer is used to debounce events.
+- timer := time.NewTimer(delay)
+- defer timer.Stop()
+-
+- for {
+- select {
+- case <-w.stop:
+- return
+-
+- case <-timer.C:
+- if events := w.drainEvents(); len(events) > 0 {
+- handler(events, nil)
+- }
+- timer.Reset(delay)
+-
+- case err, ok := <-w.watcher.Errors:
+- // When the watcher is closed, its Errors channel is closed, which
+- // unblocks this case. We continue to the next loop iteration,
+- // allowing the <-w.stop case to handle the shutdown.
+- if !ok {
+- continue
+- }
+- if err != nil {
+- handler(nil, err)
+- }
+-
+- case err, ok := <-w.errs:
+- if !ok {
+- continue
+- }
+- if err != nil {
+- handler(nil, err)
+- }
+-
+- case event, ok := <-w.watcher.Events:
+- if !ok {
+- continue
+- }
+-
+- // fsnotify does not guarantee clean filepaths.
+- event.Name = filepath.Clean(event.Name)
+-
+- // fsnotify.Event should not be handled concurrently, to preserve their
+- // original order. For example, if a file is deleted and recreated,
+- // concurrent handling could process the events in reverse order.
+- //
+- // Only reset the timer if a relevant event happened.
+- // https://github.com/fsnotify/fsnotify?tab=readme-ov-file#why-do-i-get-many-chmod-events
+- e, isDir := w.convertEvent(event)
+- if e == (protocol.FileEvent{}) {
+- continue
+- }
+-
+- if isDir {
+- switch e.Type {
+- case protocol.Created:
+- // Newly created directories are watched asynchronously to prevent
+- // a potential deadlock on Windows(see fsnotify/fsnotify#502).
+- // Errors are reported internally.
+- if done, release := w.addWatchHandle(event.Name); done != nil {
+- go func() {
+- w.errs <- w.watchDir(event.Name, done)
+-
+- // Only release after the error is sent.
+- release()
+- }()
+- }
+- case protocol.Deleted:
+- // Upon removal, we only need to remove the entries from
+- // the map. The [fsnotify.Watcher] removes the watch for
+- // us. fsnotify/fsnotify#268
+- w.removeWatchHandle(event.Name)
+- default:
+- // convertEvent enforces that dirs are only Created or Deleted.
+- panic("impossible")
+- }
+- }
+-
+- w.addEvent(e)
+- timer.Reset(delay)
+- }
+- }
+-}
+-
+-// skipDir reports whether the input dir should be skipped.
+-// Directories that are unlikely to contain Go source files relevant for
+-// analysis, such as .git directories or testdata, should be skipped to
+-// avoid unnecessary file system notifications. This reduces noise and
+-// improves efficiency. Conversely, any directory that might contain Go
+-// source code should be watched to ensure that gopls can respond to
+-// file changes.
+-func skipDir(dirName string) bool {
+- // TODO(hxjiang): the file watcher should honor gopls directory
+- // filter or the new go.mod ignore directive, or actively listening
+- // to gopls register capability request with method
+- // "workspace/didChangeWatchedFiles" like a real LSP client.
+- return strings.HasPrefix(dirName, ".") || strings.HasPrefix(dirName, "_") || dirName == "testdata"
+-}
+-
+-// WatchDir walks through the directory and all its subdirectories, adding
+-// them to the watcher.
+-func (w *Watcher) WatchDir(path string) error {
+- return filepath.WalkDir(filepath.Clean(path), func(path string, d fs.DirEntry, err error) error {
+- if d.IsDir() {
+- if skipDir(d.Name()) {
+- return filepath.SkipDir
+- }
+-
+- done, release := w.addWatchHandle(path)
+- if done == nil { // file watcher closing
+- return filepath.SkipAll
+- }
+- defer release()
+-
+- return w.watchDir(path, done)
+- }
+- return nil
+- })
+-}
+-
+-// convertEvent translates an [fsnotify.Event] into a [protocol.FileEvent].
+-// It returns the translated event and a boolean indicating if the path was a
+-// directory. For directories, the event Type is either Created or Deleted.
+-// It returns empty event for events that should be ignored.
+-func (w *Watcher) convertEvent(event fsnotify.Event) (_ protocol.FileEvent, isDir bool) {
+- // Determine if the event is for a directory.
+- if info, err := os.Stat(event.Name); err == nil {
+- isDir = info.IsDir()
+- } else if os.IsNotExist(err) {
+- // Upon deletion, the file/dir has been removed. fsnotify does not
+- // provide information regarding the deleted item.
+- // Use watchHandles to determine if the deleted item was a directory.
+- isDir = w.isWatchedDir(event.Name)
+- } else {
+- // If statting failed, something is wrong with the file system.
+- // Log and move on.
+- if w.logger != nil {
+- w.logger.Error("failed to stat path, skipping event as its type (file/dir) is unknown", "path", event.Name, "err", err)
+- }
+- return protocol.FileEvent{}, false
+- }
+-
+- // Filter out events for directories and files that are not of interest.
+- if isDir {
+- if skipDir(filepath.Base(event.Name)) {
+- return protocol.FileEvent{}, true
+- }
+- } else {
+- switch strings.TrimPrefix(filepath.Ext(event.Name), ".") {
+- case "go", "mod", "sum", "work", "s":
+- default:
+- return protocol.FileEvent{}, false
+- }
+- }
+-
+- var t protocol.FileChangeType
+- switch {
+- case event.Op.Has(fsnotify.Rename):
+- // A rename is treated as a deletion of the old path because the
+- // fsnotify RENAME event doesn't include the new path. A separate
+- // CREATE event will be sent for the new path if the destination
+- // directory is watched.
+- fallthrough
+- case event.Op.Has(fsnotify.Remove):
+- // TODO(hxjiang): Directory removal events from some LSP clients may
+- // not include corresponding removal events for child files and
+- // subdirectories. Should we do some filtering when adding the dir
+- // deletion event to the events slice.
+- t = protocol.Deleted
+- case event.Op.Has(fsnotify.Create):
+- t = protocol.Created
+- case event.Op.Has(fsnotify.Write):
+- if isDir {
+- return protocol.FileEvent{}, isDir // ignore dir write events
+- }
+- t = protocol.Changed
+- default:
+- return protocol.FileEvent{}, isDir // ignore the rest of the events
+- }
+-
+- return protocol.FileEvent{
+- URI: protocol.URIFromPath(event.Name),
+- Type: t,
+- }, isDir
+-}
+-
+-// watchDir registers a watch for a directory, retrying with backoff if it fails.
+-// It can be canceled by calling removeWatchHandle.
+-// Returns nil on success or cancellation; otherwise, the last error after all
+-// retries.
+-func (w *Watcher) watchDir(path string, done chan struct{}) error {
+- // On darwin, watching a directory will fail if it contains broken symbolic
+- // links. This state can occur temporarily during operations like a git
+- // branch switch. To handle this, we retry multiple times with exponential
+- // backoff, allowing time for the symbolic link's target to be created.
+-
+- // TODO(hxjiang): Address a race condition where file or directory creations
+- // under current directory might be missed between the current directory
+- // creation and the establishment of the file watch.
+- //
+- // To fix this, we should:
+- // 1. Retrospectively check for and trigger creation events for any new
+- // files/directories.
+- // 2. Recursively add watches for any newly created subdirectories.
+- var (
+- delay = 500 * time.Millisecond
+- err error
+- )
+-
+- for i := range 5 {
+- if i > 0 {
+- select {
+- case <-time.After(delay):
+- delay *= 2
+- case <-done:
+- return nil // cancelled
+- }
+- }
+- // This function may block due to fsnotify/fsnotify#502.
+- err = w.watcher.Add(path)
+- if afterAddHook != nil {
+- afterAddHook(path, err)
+- }
+- if err == nil {
+- break
+- }
+- }
+-
+- return err
+-}
+-
+-var afterAddHook func(path string, err error)
+-
+-// addWatchHandle registers a new directory watch.
+-// The returned 'done' channel should be used to signal cancellation of a
+-// pending watch, the release function should be called once watch registration
+-// is done.
+-// It returns nil if the watcher is already closing.
+-func (w *Watcher) addWatchHandle(path string) (done chan struct{}, release func()) {
+- w.mu.Lock()
+- defer w.mu.Unlock()
+-
+- if w.dirCancel == nil { // file watcher is closing.
+- return nil, nil
+- }
+-
+- done = make(chan struct{})
+- w.dirCancel[path] = done
+-
+- w.watchers.Add(1)
+-
+- return done, w.watchers.Done
+-}
+-
+-// removeWatchHandle removes the handle for a directory watch and cancels any
+-// pending watch attempt for that path.
+-func (w *Watcher) removeWatchHandle(path string) {
+- w.mu.Lock()
+- defer w.mu.Unlock()
+-
+- if done, ok := w.dirCancel[path]; ok {
+- delete(w.dirCancel, path)
+- close(done)
+- }
+-}
+-
+-// isWatchedDir reports whether the given path has a watch handle, meaning it is
+-// a directory the watcher is managing.
+-func (w *Watcher) isWatchedDir(path string) bool {
+- w.mu.Lock()
+- defer w.mu.Unlock()
+-
+- _, isDir := w.dirCancel[path]
+- return isDir
+-}
+-
+-func (w *Watcher) addEvent(event protocol.FileEvent) {
+- w.mu.Lock()
+- defer w.mu.Unlock()
+-
+- // Some systems emit duplicate change events in close
+- // succession upon file modification. While the current
+- // deduplication is naive and only handles immediate duplicates,
+- // a more robust solution is needed.
+- //
+- // TODO(hxjiang): Enhance deduplication. The current batching of
+- // events means all duplicates, regardless of proximity, should
+- // be removed. Consider checking the entire buffered slice or
+- // using a map for this.
+- if len(w.events) == 0 || w.events[len(w.events)-1] != event {
+- w.events = append(w.events, event)
+- }
+-}
+-
+-func (w *Watcher) drainEvents() []protocol.FileEvent {
+- w.mu.Lock()
+- events := w.events
+- w.events = nil
+- w.mu.Unlock()
+-
+- return events
+-}
+-
+-// Close shuts down the watcher, waits for the internal goroutine to terminate,
+-// and returns any final error.
+-func (w *Watcher) Close() error {
+- // Set dirCancel to nil which prevent any future watch attempts.
+- w.mu.Lock()
+- dirCancel := w.dirCancel
+- w.dirCancel = nil
+- w.mu.Unlock()
+-
+- // Cancel any ongoing watch registration.
+- for _, ch := range dirCancel {
+- close(ch)
+- }
+-
+- // Wait for all watch registration goroutines to finish, including their
+- // error handling. This ensures that:
+- // - All [Watcher.watchDir] goroutines have exited and it's error is sent
+- // to the internal error channel. So it is safe to close the internal
+- // error channel.
+- // - There are no ongoing [fsnotify.Watcher.Add] calls, so it is safe to
+- // close the fsnotify watcher (see fsnotify/fsnotify#704).
+- w.watchers.Wait()
+- close(w.errs)
+-
+- err := w.watcher.Close()
+-
+- // Wait for the main run loop to terminate.
+- close(w.stop)
+- w.runners.Wait()
+-
+- return err
+-}
+diff -urN a/gopls/internal/filewatcher/filewatcher_test.go b/gopls/internal/filewatcher/filewatcher_test.go
+--- a/gopls/internal/filewatcher/filewatcher_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/filewatcher/filewatcher_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,479 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package filewatcher_test
+-
+-import (
+- "cmp"
+- "fmt"
+- "os"
+- "path/filepath"
+- "runtime"
+- "slices"
+- "testing"
+- "time"
+-
+- "golang.org/x/sync/errgroup"
+- "golang.org/x/tools/gopls/internal/filewatcher"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/moremaps"
+- "golang.org/x/tools/txtar"
+-)
+-
+-func TestFileWatcher(t *testing.T) {
+- switch runtime.GOOS {
+- case "darwin", "linux", "windows":
+- default:
+- t.Skip("unsupported OS")
+- }
+-
+- testCases := []struct {
+- name string
+- goos []string // if not empty, only run in these OS.
+- // If set, sends watch errors for this path to an error channel
+- // passed to the 'changes' func.
+- watchErrorPath string
+- initWorkspace string
+- changes func(root string, errs chan error) error
+- expectedEvents []protocol.FileEvent
+- }{
+- {
+- name: "create file in darwin",
+- goos: []string{"darwin"},
+- initWorkspace: `
+--- foo.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.WriteFile(filepath.Join(root, "bar.go"), []byte("package main"), 0644)
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "bar.go", Type: protocol.Created},
+- },
+- },
+- {
+- name: "create file in linux & windows",
+- goos: []string{"linux", "windows"},
+- initWorkspace: `
+--- foo.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.WriteFile(filepath.Join(root, "bar.go"), []byte("package main"), 0644)
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "bar.go", Type: protocol.Created},
+- {URI: "bar.go", Type: protocol.Changed},
+- },
+- },
+- {
+- name: "modify file",
+- initWorkspace: `
+--- foo.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.WriteFile(filepath.Join(root, "foo.go"), []byte("package main // modified"), 0644)
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "foo.go", Type: protocol.Changed},
+- },
+- },
+- {
+- name: "delete file",
+- initWorkspace: `
+--- foo.go --
+-package foo
+--- bar.go --
+-package bar
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.Remove(filepath.Join(root, "foo.go"))
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "foo.go", Type: protocol.Deleted},
+- },
+- },
+- {
+- name: "rename file in linux & windows",
+- goos: []string{"linux", "windows"},
+- initWorkspace: `
+--- foo.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.Rename(filepath.Join(root, "foo.go"), filepath.Join(root, "bar.go"))
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "foo.go", Type: protocol.Deleted},
+- {URI: "bar.go", Type: protocol.Created},
+- },
+- },
+- {
+- name: "rename file in darwin",
+- goos: []string{"darwin"},
+- initWorkspace: `
+--- foo.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.Rename(filepath.Join(root, "foo.go"), filepath.Join(root, "bar.go"))
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "bar.go", Type: protocol.Created},
+- {URI: "foo.go", Type: protocol.Deleted},
+- },
+- },
+- {
+- name: "create directory",
+- initWorkspace: `
+--- foo.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.Mkdir(filepath.Join(root, "bar"), 0755)
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "bar", Type: protocol.Created},
+- },
+- },
+- {
+- name: "delete directory",
+- initWorkspace: `
+--- foo/bar.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.RemoveAll(filepath.Join(root, "foo"))
+- },
+- expectedEvents: []protocol.FileEvent{
+- // We only assert that the directory deletion event exists,
+- // because file system event behavior is inconsistent across
+- // platforms when deleting a non-empty directory.
+- // e.g. windows-amd64 may only emit a single dir removal event,
+- // freebsd-amd64 report dir removal before file removal,
+- // linux-amd64 report the reverse order.
+- // Therefore, the most reliable and cross-platform compatible
+- // signal is the deletion event for the directory itself.
+- {URI: "foo", Type: protocol.Deleted},
+- },
+- },
+- {
+- name: "rename directory in linux & windows",
+- goos: []string{"linux", "windows"},
+- initWorkspace: `
+--- foo/bar.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.Rename(filepath.Join(root, "foo"), filepath.Join(root, "baz"))
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "foo", Type: protocol.Deleted},
+- {URI: "baz", Type: protocol.Created},
+- },
+- },
+- {
+- name: "rename directory in darwin",
+- goos: []string{"darwin"},
+- initWorkspace: `
+--- foo/bar.go --
+-package foo
+-`,
+- changes: func(root string, errs chan error) error {
+- return os.Rename(filepath.Join(root, "foo"), filepath.Join(root, "baz"))
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "baz", Type: protocol.Created},
+- {URI: "foo", Type: protocol.Deleted},
+- },
+- },
+- {
+- name: "broken symlink in darwin",
+- goos: []string{"darwin"},
+- watchErrorPath: "foo",
+- changes: func(root string, errs chan error) error {
+- // Prepare a dir with with broken symbolic link.
+- // foo <- 1st
+- // └── from.go -> root/to.go <- 1st
+- tmp := filepath.Join(t.TempDir(), "foo")
+- if err := os.Mkdir(tmp, 0755); err != nil {
+- return err
+- }
+- from := filepath.Join(tmp, "from.go")
+-
+- to := filepath.Join(root, "to.go")
+- // Create the symbolic link to a non-existing file. This would
+- // cause the watch registration to fail.
+- if err := os.Symlink(to, from); err != nil {
+- return err
+- }
+-
+- // Move the directory containing the broken symlink into place
+- // to avoids a flaky test where the directory could be watched
+- // before the symlink is created. See golang/go#74782.
+- if err := os.Rename(tmp, filepath.Join(root, "foo")); err != nil {
+- return err
+- }
+-
+- // root
+- // ├── foo <- 2nd (Move)
+- // │ ├── from.go -> ../to.go <- 2nd (Move)
+- // │ └── foo.go <- 4th (Create)
+- // └── to.go <- 3rd (Create)
+-
+- // Should be able to capture an error from [fsnotify.Watcher.Add].
+- err := <-errs
+- if err == nil {
+- return fmt.Errorf("did not capture watch registration failure")
+- }
+-
+- // The file watcher should retry watch registration and
+- // eventually succeed after the file got created.
+- if err := os.WriteFile(to, []byte("package main"), 0644); err != nil {
+- return err
+- }
+-
+- timer := time.NewTimer(30 * time.Second)
+- for {
+- var (
+- err error
+- ok bool
+- )
+- select {
+- case err, ok = <-errs:
+- if !ok {
+- return fmt.Errorf("can not register watch for foo")
+- }
+- case <-timer.C:
+- return fmt.Errorf("can not register watch for foo after 30 seconds")
+- }
+-
+- if err == nil {
+- break // watch registration success
+- }
+- }
+-
+- // Once the watch registration is done, file events under the
+- // dir should be captured.
+- return os.WriteFile(filepath.Join(root, "foo", "foo.go"), []byte("package main"), 0644)
+- },
+- expectedEvents: []protocol.FileEvent{
+- {URI: "foo", Type: protocol.Created},
+- // TODO(hxjiang): enable this after implementing retrospectively
+- // generate create events.
+- // {URI: "foo/from.go", Type: protocol.Created},
+- {URI: "to.go", Type: protocol.Created},
+- {URI: "foo/foo.go", Type: protocol.Created},
+- },
+- },
+- }
+-
+- for _, tt := range testCases {
+- t.Run(tt.name, func(t *testing.T) {
+- if len(tt.goos) > 0 && !slices.Contains(tt.goos, runtime.GOOS) {
+- t.Skipf("skipping on %s", runtime.GOOS)
+- }
+-
+- root := t.TempDir()
+-
+- var errs chan error
+- if tt.watchErrorPath != "" {
+- errs = make(chan error, 10)
+- filewatcher.SetAfterAddHook(func(path string, err error) {
+- if path == filepath.Join(root, tt.watchErrorPath) {
+- errs <- err
+- if err == nil {
+- close(errs)
+- }
+- }
+- })
+- defer filewatcher.SetAfterAddHook(nil)
+- }
+-
+- archive := txtar.Parse([]byte(tt.initWorkspace))
+- for _, f := range archive.Files {
+- path := filepath.Join(root, f.Name)
+- if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+- t.Fatal(err)
+- }
+- if err := os.WriteFile(path, f.Data, 0644); err != nil {
+- t.Fatal(err)
+- }
+- }
+-
+- matched := 0
+- foundAll := make(chan struct{})
+- var gots []protocol.FileEvent
+- handler := func(events []protocol.FileEvent, err error) {
+- if err != nil {
+- t.Errorf("error from watcher: %v", err)
+- }
+- gots = append(gots, events...)
+- // This verifies that the list of wanted events is a subsequence of
+- // the received events. It confirms not only that all wanted events
+- // are present, but also that their relative order is preserved.
+- for _, got := range events {
+- if matched == len(tt.expectedEvents) {
+- break
+- }
+- want := protocol.FileEvent{
+- URI: protocol.URIFromPath(filepath.Join(root, string(tt.expectedEvents[matched].URI))),
+- Type: tt.expectedEvents[matched].Type,
+- }
+- if want == got {
+- matched++
+- }
+- }
+- if matched == len(tt.expectedEvents) {
+- close(foundAll)
+- }
+- }
+- w, err := filewatcher.New(50*time.Millisecond, nil, handler)
+- if err != nil {
+- t.Fatal(err)
+- }
+-
+- if err := w.WatchDir(root); err != nil {
+- t.Fatal(err)
+- }
+-
+- if tt.changes != nil {
+- if err := tt.changes(root, errs); err != nil {
+- t.Fatal(err)
+- }
+- }
+-
+- select {
+- case <-foundAll:
+- case <-time.After(30 * time.Second):
+- if matched < len(tt.expectedEvents) {
+- t.Errorf("found %v matching events\nall want: %#v\nall got: %#v", matched, tt.expectedEvents, gots)
+- }
+- }
+-
+- if err := w.Close(); err != nil {
+- t.Errorf("failed to close the file watcher: %v", err)
+- }
+- })
+- }
+-}
+-
+-func TestStress(t *testing.T) {
+- switch runtime.GOOS {
+- case "darwin", "linux", "windows":
+- default:
+- t.Skip("unsupported OS")
+- }
+-
+- const (
+- delay = 50 * time.Millisecond
+- parallelism = 100 // number of parallel instances of each kind of operation
+- )
+-
+- root := t.TempDir()
+-
+- mkdir := func(base string) func() error {
+- return func() error {
+- return os.Mkdir(filepath.Join(root, base), 0755)
+- }
+- }
+- write := func(base string) func() error {
+- return func() error {
+- return os.WriteFile(filepath.Join(root, base), []byte("package main"), 0644)
+- }
+- }
+- remove := func(base string) func() error {
+- return func() error {
+- return os.Remove(filepath.Join(root, base))
+- }
+- }
+- rename := func(old, new string) func() error {
+- return func() error {
+- return os.Rename(filepath.Join(root, old), filepath.Join(root, new))
+- }
+- }
+-
+- wants := make(map[protocol.FileEvent]bool)
+- want := func(base string, t protocol.FileChangeType) {
+- wants[protocol.FileEvent{URI: protocol.URIFromPath(filepath.Join(root, base)), Type: t}] = true
+- }
+-
+- for i := range parallelism {
+- // Create files and dirs that will be deleted or renamed later.
+- if err := cmp.Or(
+- mkdir(fmt.Sprintf("delete-dir-%d", i))(),
+- mkdir(fmt.Sprintf("old-dir-%d", i))(),
+- write(fmt.Sprintf("delete-file-%d.go", i))(),
+- write(fmt.Sprintf("old-file-%d.go", i))(),
+- ); err != nil {
+- t.Fatal(err)
+- }
+-
+- // Add expected notification events to the "wants" set.
+- want(fmt.Sprintf("file-%d.go", i), protocol.Created)
+- want(fmt.Sprintf("delete-file-%d.go", i), protocol.Deleted)
+- want(fmt.Sprintf("old-file-%d.go", i), protocol.Deleted)
+- want(fmt.Sprintf("new-file-%d.go", i), protocol.Created)
+- want(fmt.Sprintf("dir-%d", i), protocol.Created)
+- want(fmt.Sprintf("delete-dir-%d", i), protocol.Deleted)
+- want(fmt.Sprintf("old-dir-%d", i), protocol.Deleted)
+- want(fmt.Sprintf("new-dir-%d", i), protocol.Created)
+- }
+-
+- foundAll := make(chan struct{})
+- w, err := filewatcher.New(delay, nil, func(events []protocol.FileEvent, err error) {
+- if err != nil {
+- t.Errorf("error from watcher: %v", err)
+- return
+- }
+- for _, e := range events {
+- delete(wants, e)
+- }
+- if len(wants) == 0 {
+- close(foundAll)
+- }
+- })
+- if err != nil {
+- t.Fatal(err)
+- }
+-
+- if err := w.WatchDir(root); err != nil {
+- t.Fatal(err)
+- }
+-
+- // Spin up multiple goroutines, to perform 6 file system operations i.e.
+- // create, delete, rename of file or directory. For deletion and rename,
+- // the goroutine deletes / renames files or directories created before the
+- // watcher starts.
+- var g errgroup.Group
+- for id := range parallelism {
+- ops := []func() error{
+- write(fmt.Sprintf("file-%d.go", id)),
+- remove(fmt.Sprintf("delete-file-%d.go", id)),
+- rename(fmt.Sprintf("old-file-%d.go", id), fmt.Sprintf("new-file-%d.go", id)),
+- mkdir(fmt.Sprintf("dir-%d", id)),
+- remove(fmt.Sprintf("delete-dir-%d", id)),
+- rename(fmt.Sprintf("old-dir-%d", id), fmt.Sprintf("new-dir-%d", id)),
+- }
+- for _, f := range ops {
+- g.Go(f)
+- }
+- }
+- if err := g.Wait(); err != nil {
+- t.Fatal(err)
+- }
+-
+- select {
+- case <-foundAll:
+- case <-time.After(30 * time.Second):
+- if len(wants) > 0 {
+- t.Errorf("missing expected events: %#v", moremaps.KeySlice(wants))
+- }
+- }
+-
+- if err := w.Close(); err != nil {
+- t.Errorf("failed to close the file watcher: %v", err)
+- }
+-}
+diff -urN a/gopls/internal/fuzzy/input.go b/gopls/internal/fuzzy/input.go
+--- a/gopls/internal/fuzzy/input.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/input.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,183 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package fuzzy
+-
+-import (
+- "unicode"
+-)
+-
+-// RuneRole specifies the role of a rune in the context of an input.
+-type RuneRole byte
+-
+-const (
+- // RNone specifies a rune without any role in the input (i.e., whitespace/non-ASCII).
+- RNone RuneRole = iota
+- // RSep specifies a rune with the role of segment separator.
+- RSep
+- // RTail specifies a rune which is a lower-case tail in a word in the input.
+- RTail
+- // RUCTail specifies a rune which is an upper-case tail in a word in the input.
+- RUCTail
+- // RHead specifies a rune which is the first character in a word in the input.
+- RHead
+-)
+-
+-// RuneRoles detects the roles of each byte rune in an input string and stores it in the output
+-// slice. The rune role depends on the input type. Stops when it parsed all the runes in the string
+-// or when it filled the output. If output is nil, then it gets created.
+-func RuneRoles(candidate []byte, reuse []RuneRole) []RuneRole {
+- var output []RuneRole
+- if cap(reuse) < len(candidate) {
+- output = make([]RuneRole, 0, len(candidate))
+- } else {
+- output = reuse[:0]
+- }
+-
+- prev, prev2 := rtNone, rtNone
+- for i := range candidate {
+- r := rune(candidate[i])
+-
+- role := RNone
+-
+- curr := rtLower
+- if candidate[i] <= unicode.MaxASCII {
+- curr = runeType(rt[candidate[i]] - '0')
+- }
+-
+- if curr == rtLower {
+- if prev == rtNone || prev == rtPunct {
+- role = RHead
+- } else {
+- role = RTail
+- }
+- } else if curr == rtUpper {
+- role = RHead
+-
+- if prev == rtUpper {
+- // This and previous characters are both upper case.
+-
+- if i+1 == len(candidate) {
+- // This is last character, previous was also uppercase -> this is UCTail
+- // i.e., (current char is C): aBC / BC / ABC
+- role = RUCTail
+- }
+- }
+- } else if curr == rtPunct {
+- switch r {
+- case '.', ':':
+- role = RSep
+- }
+- }
+- if curr != rtLower {
+- if i > 1 && output[i-1] == RHead && prev2 == rtUpper && (output[i-2] == RHead || output[i-2] == RUCTail) {
+- // The previous two characters were uppercase. The current one is not a lower case, so the
+- // previous one can't be a HEAD. Make it a UCTail.
+- // i.e., (last char is current char - B must be a UCTail): ABC / ZABC / AB.
+- output[i-1] = RUCTail
+- }
+- }
+-
+- output = append(output, role)
+- prev2 = prev
+- prev = curr
+- }
+- return output
+-}
+-
+-type runeType byte
+-
+-const (
+- rtNone runeType = iota
+- rtPunct
+- rtLower
+- rtUpper
+-)
+-
+-const rt = "00000000000000000000000000000000000000000000001122222222221000000333333333333333333333333330000002222222222222222222222222200000"
+-
+-// LastSegment returns the substring representing the last segment from the input, where each
+-// byte has an associated RuneRole in the roles slice. This makes sense only for inputs of Symbol
+-// or Filename type.
+-func LastSegment(input string, roles []RuneRole) string {
+- // Exclude ending separators.
+- end := len(input) - 1
+- for end >= 0 && roles[end] == RSep {
+- end--
+- }
+- if end < 0 {
+- return ""
+- }
+-
+- start := end - 1
+- for start >= 0 && roles[start] != RSep {
+- start--
+- }
+-
+- return input[start+1 : end+1]
+-}
+-
+-// fromChunks copies string chunks into the given buffer.
+-func fromChunks(chunks []string, buffer []byte) []byte {
+- ii := 0
+- for _, chunk := range chunks {
+- for i := range len(chunk) {
+- if ii >= cap(buffer) {
+- break
+- }
+- buffer[ii] = chunk[i]
+- ii++
+- }
+- }
+- return buffer[:ii]
+-}
+-
+-// toLower transforms the input string to lower case, which is stored in the output byte slice.
+-// The lower casing considers only ASCII values - non ASCII values are left unmodified.
+-// Stops when parsed all input or when it filled the output slice. If output is nil, then it gets
+-// created.
+-func toLower(input []byte, reuse []byte) []byte {
+- output := reuse
+- if cap(reuse) < len(input) {
+- output = make([]byte, len(input))
+- }
+-
+- for i := range input {
+- r := rune(input[i])
+- if input[i] <= unicode.MaxASCII {
+- if 'A' <= r && r <= 'Z' {
+- r += 'a' - 'A'
+- }
+- }
+- output[i] = byte(r)
+- }
+- return output[:len(input)]
+-}
+-
+-// WordConsumer defines a consumer for a word delimited by the [start,end) byte offsets in an input
+-// (start is inclusive, end is exclusive).
+-type WordConsumer func(start, end int)
+-
+-// Words find word delimiters in an input based on its bytes' mappings to rune roles. The offset
+-// delimiters for each word are fed to the provided consumer function.
+-func Words(roles []RuneRole, consume WordConsumer) {
+- var wordStart int
+- for i, r := range roles {
+- switch r {
+- case RUCTail, RTail:
+- case RHead, RNone, RSep:
+- if i != wordStart {
+- consume(wordStart, i)
+- }
+- wordStart = i
+- if r != RHead {
+- // Skip this character.
+- wordStart = i + 1
+- }
+- }
+- }
+- if wordStart != len(roles) {
+- consume(wordStart, len(roles))
+- }
+-}
+diff -urN a/gopls/internal/fuzzy/input_test.go b/gopls/internal/fuzzy/input_test.go
+--- a/gopls/internal/fuzzy/input_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/input_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,134 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package fuzzy_test
+-
+-import (
+- "bytes"
+- "slices"
+- "sort"
+- "testing"
+-
+- "golang.org/x/tools/gopls/internal/fuzzy"
+-)
+-
+-var rolesTests = []struct {
+- str string
+- want string
+-}{
+- {str: "abc::def::goo", want: "Ccc//Ccc//Ccc"},
+- {str: "proto::Message", want: "Ccccc//Ccccccc"},
+- {str: "AbstractSWTFactory", want: "CcccccccCuuCcccccc"},
+- {str: "Abs012", want: "Cccccc"},
+- {str: "/", want: " "},
+- {str: "fOO", want: "CCu"},
+- {str: "fo_oo.o_oo", want: "Cc Cc/C Cc"},
+-}
+-
+-func rolesString(roles []fuzzy.RuneRole) string {
+- var buf bytes.Buffer
+- for _, r := range roles {
+- buf.WriteByte(" /cuC"[int(r)])
+- }
+- return buf.String()
+-}
+-
+-func TestRoles(t *testing.T) {
+- for _, tc := range rolesTests {
+- gotRoles := make([]fuzzy.RuneRole, len(tc.str))
+- fuzzy.RuneRoles([]byte(tc.str), gotRoles)
+- got := rolesString(gotRoles)
+- if got != tc.want {
+- t.Errorf("roles(%s) = %v; want %v", tc.str, got, tc.want)
+- }
+- }
+-}
+-
+-var wordSplitTests = []struct {
+- input string
+- want []string
+-}{
+- {
+- input: "foo bar baz",
+- want: []string{"foo", "bar", "baz"},
+- },
+- {
+- input: "fooBarBaz",
+- want: []string{"foo", "Bar", "Baz"},
+- },
+- {
+- input: "FOOBarBAZ",
+- want: []string{"FOO", "Bar", "BAZ"},
+- },
+- {
+- input: "foo123_bar2Baz3",
+- want: []string{"foo123", "bar2", "Baz3"},
+- },
+-}
+-
+-func TestWordSplit(t *testing.T) {
+- for _, tc := range wordSplitTests {
+- roles := fuzzy.RuneRoles([]byte(tc.input), nil)
+-
+- var got []string
+- consumer := func(i, j int) {
+- got = append(got, tc.input[i:j])
+- }
+- fuzzy.Words(roles, consumer)
+-
+- if eq := diffStringLists(tc.want, got); !eq {
+- t.Errorf("input %v: (want %v -> got %v)", tc.input, tc.want, got)
+- }
+- }
+-}
+-
+-func diffStringLists(a, b []string) bool {
+- sort.Strings(a)
+- sort.Strings(b)
+- return slices.Equal(a, b)
+-}
+-
+-var lastSegmentSplitTests = []struct {
+- str string
+- want string
+-}{
+- {
+- str: "identifier",
+- want: "identifier",
+- },
+- {
+- str: "two_words",
+- want: "two_words",
+- },
+- {
+- str: "first::second",
+- want: "second",
+- },
+- {
+- str: "foo.bar.FOOBar_buz123_test",
+- want: "FOOBar_buz123_test",
+- },
+-}
+-
+-func TestLastSegment(t *testing.T) {
+- for _, tc := range lastSegmentSplitTests {
+- roles := fuzzy.RuneRoles([]byte(tc.str), nil)
+-
+- got := fuzzy.LastSegment(tc.str, roles)
+-
+- if got != tc.want {
+- t.Errorf("str %v: want %v; got %v", tc.str, tc.want, got)
+- }
+- }
+-}
+-
+-func BenchmarkRoles(b *testing.B) {
+- str := "AbstractSWTFactory"
+- out := make([]fuzzy.RuneRole, len(str))
+-
+- for b.Loop() {
+- fuzzy.RuneRoles([]byte(str), out)
+- }
+- b.SetBytes(int64(len(str)))
+-}
+diff -urN a/gopls/internal/fuzzy/matcher.go b/gopls/internal/fuzzy/matcher.go
+--- a/gopls/internal/fuzzy/matcher.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/matcher.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,438 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// Package fuzzy implements a fuzzy matching algorithm.
+-package fuzzy
+-
+-import (
+- "bytes"
+- "fmt"
+-)
+-
+-const (
+- // MaxInputSize is the maximum size of the input scored against the fuzzy matcher. Longer inputs
+- // will be truncated to this size.
+- MaxInputSize = 127
+- // MaxPatternSize is the maximum size of the pattern used to construct the fuzzy matcher. Longer
+- // inputs are truncated to this size.
+- MaxPatternSize = 63
+-)
+-
+-type scoreVal int
+-
+-func (s scoreVal) val() int {
+- return int(s) >> 1
+-}
+-
+-func (s scoreVal) prevK() int {
+- return int(s) & 1
+-}
+-
+-func score(val int, prevK int /*0 or 1*/) scoreVal {
+- return scoreVal(val<<1 + prevK)
+-}
+-
+-// Matcher implements a fuzzy matching algorithm for scoring candidates against a pattern.
+-// The matcher does not support parallel usage.
+-type Matcher struct {
+- pattern string
+- patternLower []byte // lower-case version of the pattern
+- patternShort []byte // first characters of the pattern
+- caseSensitive bool // set if the pattern is mix-cased
+-
+- patternRoles []RuneRole // the role of each character in the pattern
+- roles []RuneRole // the role of each character in the tested string
+-
+- scores [MaxInputSize + 1][MaxPatternSize + 1][2]scoreVal
+-
+- scoreScale float32
+-
+- lastCandidateLen int // in bytes
+- lastCandidateMatched bool
+-
+- // Reusable buffers to avoid allocating for every candidate.
+- // - inputBuf stores the concatenated input chunks
+- // - lowerBuf stores the last candidate in lower-case
+- // - rolesBuf stores the calculated roles for each rune in the last
+- // candidate.
+- inputBuf [MaxInputSize]byte
+- lowerBuf [MaxInputSize]byte
+- rolesBuf [MaxInputSize]RuneRole
+-}
+-
+-func (m *Matcher) String() string { return m.pattern }
+-
+-func (m *Matcher) bestK(i, j int) int {
+- if m.scores[i][j][0].val() < m.scores[i][j][1].val() {
+- return 1
+- }
+- return 0
+-}
+-
+-// NewMatcher returns a new fuzzy matcher for scoring candidates against the provided pattern.
+-func NewMatcher(pattern string) *Matcher {
+- if len(pattern) > MaxPatternSize {
+- pattern = pattern[:MaxPatternSize]
+- }
+-
+- m := &Matcher{
+- pattern: pattern,
+- patternLower: toLower([]byte(pattern), nil),
+- }
+-
+- for i, c := range m.patternLower {
+- if pattern[i] != c {
+- m.caseSensitive = true
+- break
+- }
+- }
+-
+- if len(pattern) > 3 {
+- m.patternShort = m.patternLower[:3]
+- } else {
+- m.patternShort = m.patternLower
+- }
+-
+- m.patternRoles = RuneRoles([]byte(pattern), nil)
+-
+- if len(pattern) > 0 {
+- maxCharScore := 4
+- m.scoreScale = 1 / float32(maxCharScore*len(pattern))
+- }
+-
+- return m
+-}
+-
+-// Score returns the score returned by matching the candidate to the pattern.
+-// This is not designed for parallel use. Multiple candidates must be scored sequentially.
+-// Returns a score between 0 and 1 (0 - no match, 1 - perfect match).
+-func (m *Matcher) Score(candidate string) float32 {
+- return m.ScoreChunks([]string{candidate})
+-}
+-
+-func (m *Matcher) ScoreChunks(chunks []string) float32 {
+- candidate := fromChunks(chunks, m.inputBuf[:])
+- if len(candidate) > MaxInputSize {
+- candidate = candidate[:MaxInputSize]
+- }
+- lower := toLower(candidate, m.lowerBuf[:])
+- m.lastCandidateLen = len(candidate)
+-
+- if len(m.pattern) == 0 {
+- // Empty patterns perfectly match candidates.
+- return 1
+- }
+-
+- if m.match(candidate, lower) {
+- sc := m.computeScore(candidate, lower)
+- if sc > minScore/2 && !m.poorMatch() {
+- m.lastCandidateMatched = true
+- if len(m.pattern) == len(candidate) {
+- // Perfect match.
+- return 1
+- }
+-
+- if sc < 0 {
+- sc = 0
+- }
+- normalizedScore := min(float32(sc)*m.scoreScale, 1)
+-
+- return normalizedScore
+- }
+- }
+-
+- m.lastCandidateMatched = false
+- return 0
+-}
+-
+-const minScore = -10000
+-
+-// MatchedRanges returns matches ranges for the last scored string as a flattened array of
+-// [begin, end) byte offset pairs.
+-func (m *Matcher) MatchedRanges() []int {
+- if len(m.pattern) == 0 || !m.lastCandidateMatched {
+- return nil
+- }
+- i, j := m.lastCandidateLen, len(m.pattern)
+- if m.scores[i][j][0].val() < minScore/2 && m.scores[i][j][1].val() < minScore/2 {
+- return nil
+- }
+-
+- var ret []int
+- k := m.bestK(i, j)
+- for i > 0 {
+- take := (k == 1)
+- k = m.scores[i][j][k].prevK()
+- if take {
+- if len(ret) == 0 || ret[len(ret)-1] != i {
+- ret = append(ret, i)
+- ret = append(ret, i-1)
+- } else {
+- ret[len(ret)-1] = i - 1
+- }
+- j--
+- }
+- i--
+- }
+- // Reverse slice.
+- for i := range len(ret) / 2 {
+- ret[i], ret[len(ret)-1-i] = ret[len(ret)-1-i], ret[i]
+- }
+- return ret
+-}
+-
+-func (m *Matcher) match(candidate []byte, candidateLower []byte) bool {
+- i, j := 0, 0
+- for ; i < len(candidateLower) && j < len(m.patternLower); i++ {
+- if candidateLower[i] == m.patternLower[j] {
+- j++
+- }
+- }
+- if j != len(m.patternLower) {
+- return false
+- }
+-
+- // The input passes the simple test against pattern, so it is time to classify its characters.
+- // Character roles are used below to find the last segment.
+- m.roles = RuneRoles(candidate, m.rolesBuf[:])
+-
+- return true
+-}
+-
+-func (m *Matcher) computeScore(candidate []byte, candidateLower []byte) int {
+- pattLen, candLen := len(m.pattern), len(candidate)
+-
+- for j := 0; j <= len(m.pattern); j++ {
+- m.scores[0][j][0] = minScore << 1
+- m.scores[0][j][1] = minScore << 1
+- }
+- m.scores[0][0][0] = score(0, 0) // Start with 0.
+-
+- segmentsLeft, lastSegStart := 1, 0
+- for i := range candLen {
+- if m.roles[i] == RSep {
+- segmentsLeft++
+- lastSegStart = i + 1
+- }
+- }
+-
+- // A per-character bonus for a consecutive match.
+- consecutiveBonus := 2
+- wordIdx := 0 // Word count within segment.
+- for i := 1; i <= candLen; i++ {
+-
+- role := m.roles[i-1]
+- isHead := role == RHead
+-
+- if isHead {
+- wordIdx++
+- } else if role == RSep && segmentsLeft > 1 {
+- wordIdx = 0
+- segmentsLeft--
+- }
+-
+- var skipPenalty int
+- if i == 1 || (i-1) == lastSegStart {
+- // Skipping the start of first or last segment.
+- skipPenalty++
+- }
+-
+- for j := 0; j <= pattLen; j++ {
+- // By default, we don't have a match. Fill in the skip data.
+- m.scores[i][j][1] = minScore << 1
+-
+- // Compute the skip score.
+- k := 0
+- if m.scores[i-1][j][0].val() < m.scores[i-1][j][1].val() {
+- k = 1
+- }
+-
+- skipScore := m.scores[i-1][j][k].val()
+- // Do not penalize missing characters after the last matched segment.
+- if j != pattLen {
+- skipScore -= skipPenalty
+- }
+- m.scores[i][j][0] = score(skipScore, k)
+-
+- if j == 0 || candidateLower[i-1] != m.patternLower[j-1] {
+- // Not a match.
+- continue
+- }
+- pRole := m.patternRoles[j-1]
+-
+- if role == RTail && pRole == RHead {
+- if j > 1 {
+- // Not a match: a head in the pattern matches a tail character in the candidate.
+- continue
+- }
+- // Special treatment for the first character of the pattern. We allow
+- // matches in the middle of a word if they are long enough, at least
+- // min(3, pattern.length) characters.
+- if !bytes.HasPrefix(candidateLower[i-1:], m.patternShort) {
+- continue
+- }
+- }
+-
+- // Compute the char score.
+- var charScore int
+- // Bonus: the char is in the candidate's last segment.
+- if segmentsLeft <= 1 {
+- charScore++
+- }
+-
+- // Bonus: exact case match between pattern and candidate.
+- if candidate[i-1] == m.pattern[j-1] ||
+- // Bonus: candidate char is a head and pattern is all
+- // lowercase. There is no segmentation in an all lowercase
+- // pattern, so assume any char in pattern can be a head. Note
+- // that we are intentionally _not_ giving a bonus to a case
+- // insensitive match when the pattern is case sensitive.
+- role == RHead && !m.caseSensitive {
+- charScore++
+- }
+-
+- // Penalty: pattern char is Head, candidate char is Tail.
+- if role == RTail && pRole == RHead {
+- charScore--
+- }
+- // Penalty: first pattern character matched in the middle of a word.
+- if j == 1 && role == RTail {
+- charScore -= 4
+- }
+-
+- // Third dimension encodes whether there is a gap between the previous match and the current
+- // one.
+- for k := range 2 {
+- sc := m.scores[i-1][j-1][k].val() + charScore
+-
+- isConsecutive := k == 1 || i-1 == 0 || i-1 == lastSegStart
+- if isConsecutive {
+- // Bonus: a consecutive match. First character match also gets a bonus to
+- // ensure prefix final match score normalizes to 1.0.
+- // Logically, this is a part of charScore, but we have to compute it here because it
+- // only applies for consecutive matches (k == 1).
+- sc += consecutiveBonus
+- }
+- if k == 0 {
+- // Penalty: Matching inside a segment (and previous char wasn't matched). Penalize for the lack
+- // of alignment.
+- if role == RTail || role == RUCTail {
+- sc -= 3
+- }
+- }
+-
+- if sc > m.scores[i][j][1].val() {
+- m.scores[i][j][1] = score(sc, k)
+- }
+- }
+- }
+- }
+-
+- result := m.scores[len(candidate)][len(m.pattern)][m.bestK(len(candidate), len(m.pattern))].val()
+-
+- return result
+-}
+-
+-// ScoreTable returns the score table computed for the provided candidate. Used only for debugging.
+-func (m *Matcher) ScoreTable(candidate string) string {
+- var buf bytes.Buffer
+-
+- var line1, line2, separator bytes.Buffer
+- line1.WriteString("\t")
+- line2.WriteString("\t")
+- for j := range len(m.pattern) {
+- line1.WriteString(fmt.Sprintf("%c\t\t", m.pattern[j]))
+- separator.WriteString("----------------")
+- }
+-
+- buf.WriteString(line1.String())
+- buf.WriteString("\n")
+- buf.WriteString(separator.String())
+- buf.WriteString("\n")
+-
+- for i := 1; i <= len(candidate); i++ {
+- line1.Reset()
+- line2.Reset()
+-
+- line1.WriteString(fmt.Sprintf("%c\t", candidate[i-1]))
+- line2.WriteString("\t")
+-
+- for j := 1; j <= len(m.pattern); j++ {
+- line1.WriteString(fmt.Sprintf("M%6d(%c)\t", m.scores[i][j][0].val(), dir(m.scores[i][j][0].prevK())))
+- line2.WriteString(fmt.Sprintf("H%6d(%c)\t", m.scores[i][j][1].val(), dir(m.scores[i][j][1].prevK())))
+- }
+- buf.WriteString(line1.String())
+- buf.WriteString("\n")
+- buf.WriteString(line2.String())
+- buf.WriteString("\n")
+- buf.WriteString(separator.String())
+- buf.WriteString("\n")
+- }
+-
+- return buf.String()
+-}
+-
+-func dir(prevK int) rune {
+- if prevK == 0 {
+- return 'M'
+- }
+- return 'H'
+-}
+-
+-func (m *Matcher) poorMatch() bool {
+- if len(m.pattern) < 2 {
+- return false
+- }
+-
+- i, j := m.lastCandidateLen, len(m.pattern)
+- k := m.bestK(i, j)
+-
+- var counter, len int
+- for i > 0 {
+- take := (k == 1)
+- k = m.scores[i][j][k].prevK()
+- if take {
+- len++
+- if k == 0 && len < 3 && m.roles[i-1] == RTail {
+- // Short match in the middle of a word
+- counter++
+- if counter > 1 {
+- return true
+- }
+- }
+- j--
+- } else {
+- len = 0
+- }
+- i--
+- }
+- return false
+-}
+-
+-// BestMatch returns the name most similar to the
+-// pattern, using fuzzy matching, or the empty string.
+-func BestMatch(pattern string, names []string) string {
+- fuzz := NewMatcher(pattern)
+- best := ""
+- highScore := float32(0) // minimum score is 0 (no match)
+- for _, name := range names {
+- // TODO: Improve scoring algorithm.
+- score := fuzz.Score(name)
+- if score > highScore {
+- highScore = score
+- best = name
+- } else if score == 0 {
+- // Order matters in the fuzzy matching algorithm. If we find no match
+- // when matching the target to the identifier, try matching the identifier
+- // to the target.
+- revFuzz := NewMatcher(name)
+- revScore := revFuzz.Score(pattern)
+- if revScore > highScore {
+- highScore = revScore
+- best = name
+- }
+- }
+- }
+- return best
+-}
+diff -urN a/gopls/internal/fuzzy/matcher_test.go b/gopls/internal/fuzzy/matcher_test.go
+--- a/gopls/internal/fuzzy/matcher_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/matcher_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,306 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// Benchmark results:
+-//
+-// BenchmarkMatcher-12 1000000 1615 ns/op 30.95 MB/s 0 B/op 0 allocs/op
+-package fuzzy_test
+-
+-import (
+- "bytes"
+- "fmt"
+- "math"
+- "testing"
+-
+- "golang.org/x/tools/gopls/internal/fuzzy"
+-)
+-
+-type comparator struct {
+- f func(val, ref float32) bool
+- descr string
+-}
+-
+-var (
+- eq = comparator{
+- f: func(val, ref float32) bool {
+- return val == ref
+- },
+- descr: "==",
+- }
+- ge = comparator{
+- f: func(val, ref float32) bool {
+- return val >= ref
+- },
+- descr: ">=",
+- }
+- gt = comparator{
+- f: func(val, ref float32) bool {
+- return val > ref
+- },
+- descr: ">",
+- }
+-)
+-
+-func (c comparator) eval(val, ref float32) bool {
+- return c.f(val, ref)
+-}
+-
+-func (c comparator) String() string {
+- return c.descr
+-}
+-
+-type scoreTest struct {
+- candidate string
+- comparator
+- ref float32
+-}
+-
+-var matcherTests = []struct {
+- pattern string
+- tests []scoreTest
+-}{
+- {
+- pattern: "",
+- tests: []scoreTest{
+- {"def", eq, 1},
+- {"Ab stuff c", eq, 1},
+- },
+- },
+- {
+- pattern: "abc",
+- tests: []scoreTest{
+- {"def", eq, 0},
+- {"abd", eq, 0},
+- {"abc", ge, 0},
+- {"Abc", ge, 0},
+- {"Ab stuff c", ge, 0},
+- },
+- },
+- {
+- pattern: "Abc",
+- tests: []scoreTest{
+- {"def", eq, 0},
+- {"abd", eq, 0},
+- {"abc", ge, 0},
+- {"Abc", ge, 0},
+- {"Ab stuff c", ge, 0},
+- },
+- },
+- {
+- pattern: "U",
+- tests: []scoreTest{
+- {"ErrUnexpectedEOF", gt, 0},
+- {"ErrUnexpectedEOF.Error", eq, 0},
+- },
+- },
+-}
+-
+-func TestScore(t *testing.T) {
+- for _, tc := range matcherTests {
+- m := fuzzy.NewMatcher(tc.pattern)
+- for _, sct := range tc.tests {
+- score := m.Score(sct.candidate)
+- if !sct.comparator.eval(score, sct.ref) {
+- t.Errorf("m.Score(%q) = %.2g, want %s %v", sct.candidate, score, sct.comparator, sct.ref)
+- }
+- }
+- }
+-}
+-
+-var compareCandidatesTestCases = []struct {
+- pattern string
+- // In `[][]string{{"foo", "bar"}, {"baz"}}`,
+- // "foo" and "bar" must have same score, "baz" must be strictly higher scoring.
+- orderedCandidates [][]string
+-}{
+- {
+- pattern: "Foo",
+- orderedCandidates: [][]string{
+- {"Barfoo"},
+- {"Faoo"},
+- {"F_o_o"},
+- {"FaoFooa", "BarFoo"},
+- {"F__oo", "F_oo"},
+- {"FooA", "FooBar", "Foo"},
+- },
+- },
+- {
+- pattern: "U",
+- orderedCandidates: [][]string{
+- {"ErrUnexpectedEOF.Error"},
+- {"ErrUnexpectedEOF"},
+- },
+- },
+- {
+- pattern: "N",
+- orderedCandidates: [][]string{
+- {"name"},
+- {"Name"},
+- },
+- },
+-}
+-
+-func TestCompareCandidateScores(t *testing.T) {
+- for _, tc := range compareCandidatesTestCases {
+- m := fuzzy.NewMatcher(tc.pattern)
+-
+- var prevScore float32
+- var prevCandGroup []string
+- for i, candGroup := range tc.orderedCandidates {
+- var groupScore float32
+- for j, cand := range candGroup {
+- score := m.Score(cand)
+- if j > 0 && score != groupScore {
+- t.Fatalf("score %f of %q different than group", score, cand)
+- }
+- groupScore = score
+- }
+-
+- if i > 0 && prevScore >= groupScore {
+- t.Errorf("%s[=%v] is not scored higher than %s[=%v]", candGroup, groupScore, prevCandGroup, prevScore)
+- }
+- if groupScore < 0 || groupScore > 1 {
+- t.Errorf("%s score is %v; want value between [0, 1]", candGroup, groupScore)
+- }
+- prevScore = groupScore
+- prevCandGroup = candGroup
+- }
+- }
+-}
+-
+-var fuzzyMatcherTestCases = []struct {
+- p string
+- str string
+- want string
+-}{
+- {p: "foo", str: "abc::foo", want: "abc::[foo]"},
+- {p: "foo", str: "foo.foo", want: "foo.[foo]"},
+- {p: "foo", str: "fo_oo.o_oo", want: "[fo]_oo.[o]_oo"},
+- {p: "foo", str: "fo_oo.fo_oo", want: "fo_oo.[fo]_[o]o"},
+- {p: "fo_o", str: "fo_oo.o_oo", want: "[f]o_oo.[o_o]o"},
+- {p: "fOO", str: "fo_oo.o_oo", want: "[f]o_oo.[o]_[o]o"},
+- {p: "tedit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"},
+- {p: "TEdit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"},
+- {p: "Tedit", str: "foo.TextEdit", want: "foo.[T]ext[Edit]"},
+- {p: "Tedit", str: "foo.Textedit", want: "foo.[Te]xte[dit]"},
+- {p: "TEdit", str: "foo.Textedit", want: ""},
+- {p: "te", str: "foo.Textedit", want: "foo.[Te]xtedit"},
+- {p: "ee", str: "foo.Textedit", want: ""}, // short middle of the word match
+- {p: "ex", str: "foo.Textedit", want: "foo.T[ex]tedit"},
+- {p: "exdi", str: "foo.Textedit", want: ""}, // short middle of the word match
+- {p: "exdit", str: "foo.Textedit", want: ""}, // short middle of the word match
+- {p: "extdit", str: "foo.Textedit", want: "foo.T[ext]e[dit]"},
+- {p: "e", str: "foo.Textedit", want: "foo.T[e]xtedit"},
+- {p: "E", str: "foo.Textedit", want: "foo.T[e]xtedit"},
+- {p: "ed", str: "foo.Textedit", want: "foo.Text[ed]it"},
+- {p: "edt", str: "foo.Textedit", want: ""}, // short middle of the word match
+- {p: "edit", str: "foo.Textedit", want: "foo.Text[edit]"},
+- {p: "edin", str: "foo.TexteditNum", want: "foo.Text[edi]t[N]um"},
+- {p: "n", str: "node.GoNodeMax", want: "[n]ode.GoNodeMax"},
+- {p: "N", str: "node.GoNodeMax", want: "[n]ode.GoNodeMax"},
+- {p: "completio", str: "completion", want: "[completio]n"},
+- {p: "completio", str: "completion.None", want: "[completio]n.None"},
+-}
+-
+-func TestFuzzyMatcherRanges(t *testing.T) {
+- for _, tc := range fuzzyMatcherTestCases {
+- matcher := fuzzy.NewMatcher(tc.p)
+- score := matcher.Score(tc.str)
+- if tc.want == "" {
+- if score > 0 {
+- t.Errorf("Score(%s, %s) = %v; want: <= 0", tc.p, tc.str, score)
+- }
+- continue
+- }
+- if score < 0 {
+- t.Errorf("Score(%s, %s) = %v, want: > 0", tc.p, tc.str, score)
+- continue
+- }
+- got := highlightMatches(tc.str, matcher)
+- if tc.want != got {
+- t.Errorf("highlightMatches(%s, %s) = %v, want: %v", tc.p, tc.str, got, tc.want)
+- }
+- }
+-}
+-
+-var scoreTestCases = []struct {
+- p string
+- str string
+- want float64
+-}{
+- // Score precision up to five digits. Modify if changing the score, but make sure the new values
+- // are reasonable.
+- {p: "abc", str: "abc", want: 1},
+- {p: "abc", str: "Abc", want: 1},
+- {p: "abc", str: "Abcdef", want: 1},
+- {p: "strc", str: "StrCat", want: 1},
+- {p: "abc_def", str: "abc_def_xyz", want: 1},
+- {p: "abcdef", str: "abc_def_xyz", want: 0.91667},
+- {p: "abcxyz", str: "abc_def_xyz", want: 0.91667},
+- {p: "sc", str: "StrCat", want: 0.75},
+- {p: "abc", str: "AbstrBasicCtor", want: 0.83333},
+- {p: "foo", str: "abc::foo", want: 0.91667},
+- {p: "afoo", str: "abc::foo", want: 0.9375},
+- {p: "abr", str: "abc::bar", want: 0.5},
+- {p: "br", str: "abc::bar", want: 0.25},
+- {p: "aar", str: "abc::bar", want: 0.41667},
+- {p: "edin", str: "foo.TexteditNum", want: 0.125},
+- {p: "ediu", str: "foo.TexteditNum", want: 0},
+- // We want the next two items to have roughly similar scores.
+- {p: "up", str: "unique_ptr", want: 0.75},
+- {p: "up", str: "upper_bound", want: 1},
+-}
+-
+-func TestScores(t *testing.T) {
+- for _, tc := range scoreTestCases {
+- matcher := fuzzy.NewMatcher(tc.p)
+- got := math.Round(float64(matcher.Score(tc.str))*1e5) / 1e5
+- if got != tc.want {
+- t.Errorf("Score(%s, %s) = %v, want: %v", tc.p, tc.str, got, tc.want)
+- }
+- }
+-}
+-
+-func highlightMatches(str string, matcher *fuzzy.Matcher) string {
+- matches := matcher.MatchedRanges()
+-
+- var buf bytes.Buffer
+- index := 0
+- for i := 0; i < len(matches)-1; i += 2 {
+- s, e := matches[i], matches[i+1]
+- fmt.Fprintf(&buf, "%s[%s]", str[index:s], str[s:e])
+- index = e
+- }
+- buf.WriteString(str[index:])
+- return buf.String()
+-}
+-
+-func BenchmarkMatcher(b *testing.B) {
+- pattern := "Foo"
+- candidates := []string{
+- "F_o_o",
+- "Barfoo",
+- "Faoo",
+- "F__oo",
+- "F_oo",
+- "FaoFooa",
+- "BarFoo",
+- "FooA",
+- "FooBar",
+- "Foo",
+- }
+-
+- matcher := fuzzy.NewMatcher(pattern)
+-
+- for b.Loop() {
+- for _, c := range candidates {
+- matcher.Score(c)
+- }
+- }
+- var numBytes int
+- for _, c := range candidates {
+- numBytes += len(c)
+- }
+- b.SetBytes(int64(numBytes))
+-}
+diff -urN a/gopls/internal/fuzzy/self_test.go b/gopls/internal/fuzzy/self_test.go
+--- a/gopls/internal/fuzzy/self_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/self_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,39 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package fuzzy_test
+-
+-import (
+- "testing"
+-
+- . "golang.org/x/tools/gopls/internal/fuzzy"
+-)
+-
+-func BenchmarkSelf_Matcher(b *testing.B) {
+- idents := collectIdentifiers(b)
+- patterns := generatePatterns()
+-
+- for b.Loop() {
+- for _, pattern := range patterns {
+- sm := NewMatcher(pattern)
+- for _, ident := range idents {
+- _ = sm.Score(ident)
+- }
+- }
+- }
+-}
+-
+-func BenchmarkSelf_SymbolMatcher(b *testing.B) {
+- idents := collectIdentifiers(b)
+- patterns := generatePatterns()
+-
+- for b.Loop() {
+- for _, pattern := range patterns {
+- sm := NewSymbolMatcher(pattern)
+- for _, ident := range idents {
+- _, _ = sm.Match([]string{ident})
+- }
+- }
+- }
+-}
+diff -urN a/gopls/internal/fuzzy/symbol.go b/gopls/internal/fuzzy/symbol.go
+--- a/gopls/internal/fuzzy/symbol.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/symbol.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,309 +0,0 @@
+-// Copyright 2021 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package fuzzy
+-
+-import (
+- "bytes"
+- "fmt"
+- "log"
+- "unicode"
+-)
+-
+-// SymbolMatcher implements a fuzzy matching algorithm optimized for Go symbols
+-// of the form:
+-//
+-// example.com/path/to/package.object.field
+-//
+-// Knowing that we are matching symbols like this allows us to make the
+-// following optimizations:
+-// - We can incorporate right-to-left relevance directly into the score
+-// calculation.
+-// - We can match from right to left, discarding leading bytes if the input is
+-// too long.
+-// - We just take the right-most match without losing too much precision. This
+-// allows us to use an O(n) algorithm.
+-// - We can operate directly on chunked strings; in many cases we will
+-// be storing the package path and/or package name separately from the
+-// symbol or identifiers, so doing this avoids allocating strings.
+-// - We can return the index of the right-most match, allowing us to trim
+-// irrelevant qualification.
+-type SymbolMatcher struct {
+- // Using buffers of length 256 is both a reasonable size for most qualified
+- // symbols, and makes it easy to avoid bounds checks by using uint8 indexes.
+- pattern [256]rune
+- patternLen uint8
+- inputBuffer [256]rune // avoid allocating when considering chunks
+- roles [256]uint32 // which roles does a rune play (word start, etc.)
+- segments [256]uint8 // how many segments from the right is each rune
+-}
+-
+-// Rune roles.
+-const (
+- segmentStart uint32 = 1 << iota // input rune starts a segment (i.e. follows '/' or '.')
+- wordStart // input rune starts a word, per camel-case naming rules
+- separator // input rune is a separator ('/' or '.')
+- upper // input rune is an upper case letter
+-)
+-
+-// NewSymbolMatcher creates a SymbolMatcher that may be used to match the given
+-// search pattern.
+-//
+-// Currently this matcher only accepts case-insensitive fuzzy patterns.
+-//
+-// An empty pattern matches no input.
+-func NewSymbolMatcher(pattern string) *SymbolMatcher {
+- m := &SymbolMatcher{}
+- for _, p := range pattern {
+- m.pattern[m.patternLen] = unicode.ToLower(p)
+- m.patternLen++
+- if m.patternLen == 255 || int(m.patternLen) == len(pattern) {
+- // break at 255 so that we can represent patternLen with a uint8.
+- break
+- }
+- }
+- return m
+-}
+-
+-// Match searches for the right-most match of the search pattern within the
+-// symbol represented by concatenating the given chunks.
+-//
+-// If a match is found, the first result holds the absolute byte offset within
+-// all chunks for the start of the symbol. In other words, the index of the
+-// match within strings.Join(chunks, "").
+-//
+-// The second return value will be the score of the match, which is always
+-// between 0 and 1, inclusive. A score of 0 indicates no match.
+-//
+-// If no match is found, Match returns (-1, 0).
+-func (m *SymbolMatcher) Match(chunks []string) (int, float64) {
+- // Explicit behavior for an empty pattern.
+- //
+- // As a minor optimization, this also avoids nilness checks later on, since
+- // the compiler can prove that m != nil.
+- if m.patternLen == 0 {
+- return -1, 0
+- }
+-
+- // Matching implements a heavily optimized linear scoring algorithm on the
+- // input. This is not guaranteed to produce the highest score, but works well
+- // enough, particularly due to the right-to-left significance of qualified
+- // symbols.
+- //
+- // Matching proceeds in three passes through the input:
+- // - The first pass populates the input buffer and collects rune roles.
+- // - The second pass proceeds right-to-left to find the right-most match.
+- // - The third pass proceeds left-to-right from the start of the right-most
+- // match, to find the most *compact* match, and computes the score of this
+- // match.
+- //
+- // See below for more details of each pass, as well as the scoring algorithm.
+-
+- // First pass: populate the input buffer out of the provided chunks
+- // (lower-casing in the process), and collect rune roles.
+- //
+- // We could also check for a forward match here, but since we'd have to write
+- // the entire input anyway this has negligible impact on performance.
+- var (
+- inputLen = uint8(0)
+- modifiers = wordStart | segmentStart
+- )
+-
+-input:
+- for _, chunk := range chunks {
+- for _, r := range chunk {
+- if r == '.' || r == '/' {
+- modifiers |= separator
+- }
+- // optimization: avoid calls to unicode.ToLower, which can't be inlined.
+- l := r
+- if r <= unicode.MaxASCII {
+- if 'A' <= r && r <= 'Z' {
+- l = r + 'a' - 'A'
+- }
+- } else {
+- l = unicode.ToLower(r)
+- }
+- if l != r {
+- modifiers |= upper
+-
+- // If the current rune is capitalized *and the preceding rune was not*,
+- // mark this as a word start. This avoids spuriously high ranking of
+- // non-camelcase naming schemas, such as the
+- // yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE example of
+- // golang/go#60201.
+- if inputLen == 0 || m.roles[inputLen-1]&upper == 0 {
+- modifiers |= wordStart
+- }
+- }
+- m.inputBuffer[inputLen] = l
+- m.roles[inputLen] = modifiers
+- inputLen++
+- if m.roles[inputLen-1]&separator != 0 {
+- modifiers = wordStart | segmentStart
+- } else {
+- modifiers = 0
+- }
+- // TODO: we should prefer the right-most input if it overflows, rather
+- // than the left-most as we're doing here.
+- if inputLen == 255 {
+- break input
+- }
+- }
+- }
+-
+- // Second pass: find the right-most match, and count segments from the
+- // right.
+- var (
+- pi = uint8(m.patternLen - 1) // pattern index
+- p = m.pattern[pi] // pattern rune
+- start = -1 // start offset of match
+- rseg = uint8(0) // effective "depth" from the right of the current rune in consideration
+- )
+- const maxSeg = 3 // maximum number of segments from the right to count, for scoring purposes.
+-
+- for ii := inputLen - 1; ; ii-- {
+- r := m.inputBuffer[ii]
+- if rseg < maxSeg && m.roles[ii]&separator != 0 {
+- rseg++
+- }
+- m.segments[ii] = rseg
+- if p == r {
+- if pi == 0 {
+- // TODO(rfindley): BUG: the docstring for Match says that it returns an
+- // absolute byte offset, but clearly it is returning a rune offset here.
+- start = int(ii)
+- break
+- }
+- pi--
+- p = m.pattern[pi]
+- }
+- // Don't check ii >= 0 in the loop condition: ii is a uint8.
+- if ii == 0 {
+- break
+- }
+- }
+-
+- if start < 0 {
+- // no match: skip scoring
+- return -1, 0
+- }
+-
+- // Third pass: find the shortest match and compute the score.
+-
+- // Score is the average score for each rune.
+- //
+- // A rune score is the multiple of:
+- // 1. The base score, which is 1.0 if the rune starts a segment, 0.9 if the
+- // rune starts a mid-segment word, else 0.6.
+- //
+- // Runes preceded by a matching rune are treated the same as the start
+- // of a mid-segment word (with a 0.9 score), so that sequential or exact
+- // matches are preferred. We call this a sequential bonus.
+- //
+- // For the final rune match, this sequential bonus is reduced to 0.8 if
+- // the next rune in the input is a mid-segment word, or 0.7 if the next
+- // rune in the input is not a word or segment start. This ensures that
+- // we favor whole-word or whole-segment matches over prefix matches.
+- //
+- // 2. 1.0 if the rune is part of the last segment, otherwise
+- // 1.0-0.1*, with a max segment count of 3.
+- // Notably 1.0-0.1*3 = 0.7 > 0.6, so that foo/_/_/_/_ (a match very
+- // early in a qualified symbol name) still scores higher than _f_o_o_ (a
+- // completely split match).
+- //
+- // This is a naive algorithm, but it is fast. There's lots of prior art here
+- // that could be leveraged. For example, we could explicitly consider
+- // rune distance, and exact matches of words or segments.
+- //
+- // Also note that this might not actually find the highest scoring match, as
+- // doing so could require a non-linear algorithm, depending on how the score
+- // is calculated.
+-
+- // debugging support
+- const debug = false // enable to log debugging information
+- var (
+- runeScores []float64
+- runeIdxs []int
+- )
+-
+- pi = 0
+- p = m.pattern[pi]
+-
+- const (
+- segStartScore = 1.0 // base score of runes starting a segment
+- wordScore = 0.9 // base score of runes starting or continuing a word
+- noStreak = 0.6
+- perSegment = 0.1 // we count at most 3 segments above
+- )
+-
+- totScore := 0.0
+- lastMatch := uint8(255)
+- for ii := uint8(start); ii < inputLen; ii++ {
+- r := m.inputBuffer[ii]
+- if r == p {
+- pi++
+- finalRune := pi >= m.patternLen
+- p = m.pattern[pi]
+-
+- baseScore := noStreak
+-
+- // Calculate the sequence bonus based on preceding matches.
+- //
+- // We do this first as it is overridden by role scoring below.
+- if lastMatch == ii-1 {
+- baseScore = wordScore
+- // Reduce the sequence bonus for the final rune of the pattern based on
+- // whether it borders a new segment or word.
+- if finalRune {
+- switch {
+- case ii == inputLen-1 || m.roles[ii+1]&separator != 0:
+- // Full segment: no reduction
+- case m.roles[ii+1]&wordStart != 0:
+- baseScore = wordScore - 0.1
+- default:
+- baseScore = wordScore - 0.2
+- }
+- }
+- }
+- lastMatch = ii
+-
+- // Calculate the rune's role score. If the rune starts a segment or word,
+- // this overrides the sequence score, as the rune starts a new sequence.
+- switch {
+- case m.roles[ii]&segmentStart != 0:
+- baseScore = segStartScore
+- case m.roles[ii]&wordStart != 0:
+- baseScore = wordScore
+- }
+-
+- // Apply the segment-depth penalty (segments from the right).
+- runeScore := baseScore * (1.0 - float64(m.segments[ii])*perSegment)
+- if debug {
+- runeScores = append(runeScores, runeScore)
+- runeIdxs = append(runeIdxs, int(ii))
+- }
+- totScore += runeScore
+- if finalRune {
+- break
+- }
+- }
+- }
+-
+- if debug {
+- // Format rune roles and scores in line:
+- // fo[o:.52].[b:1]a[r:.6]
+- var summary bytes.Buffer
+- last := 0
+- for i, idx := range runeIdxs {
+- summary.WriteString(string(m.inputBuffer[last:idx])) // encode runes
+- fmt.Fprintf(&summary, "[%s:%.2g]", string(m.inputBuffer[idx]), runeScores[i])
+- last = idx + 1
+- }
+- summary.WriteString(string(m.inputBuffer[last:inputLen])) // encode runes
+- log.Println(summary.String())
+- }
+-
+- return start, totScore / float64(m.patternLen)
+-}
+diff -urN a/gopls/internal/fuzzy/symbol_test.go b/gopls/internal/fuzzy/symbol_test.go
+--- a/gopls/internal/fuzzy/symbol_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/fuzzy/symbol_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,253 +0,0 @@
+-// Copyright 2021 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package fuzzy_test
+-
+-import (
+- "go/ast"
+- "go/token"
+- "sort"
+- "testing"
+-
+- "golang.org/x/tools/go/packages"
+- . "golang.org/x/tools/gopls/internal/fuzzy"
+-)
+-
+-func TestSymbolMatchIndex(t *testing.T) {
+- tests := []struct {
+- pattern, input string
+- want int
+- }{
+- {"test", "foo.TestFoo", 4},
+- {"test", "test", 0},
+- {"test", "Test", 0},
+- {"test", "est", -1},
+- {"t", "shortest", 7},
+- {"", "foo", -1},
+- {"", string([]rune{0}), -1}, // verify that we don't default to an empty pattern.
+- {"anything", "", -1},
+- }
+-
+- for _, test := range tests {
+- matcher := NewSymbolMatcher(test.pattern)
+- if got, _ := matcher.Match([]string{test.input}); got != test.want {
+- t.Errorf("NewSymbolMatcher(%q).Match(%q) = %v, _, want %v, _", test.pattern, test.input, got, test.want)
+- }
+- }
+-}
+-
+-func TestSymbolRanking(t *testing.T) {
+-
+- // query -> symbols to match, in ascending order of score
+- queryRanks := map[string][]string{
+- "test": {
+- "this.is.better.than.most",
+- "test.foo.bar",
+- "thebest",
+- "atest",
+- "test.foo",
+- "testage",
+- "tTest",
+- "foo.test",
+- },
+- "parseside": { // golang/go#60201
+- "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE",
+- "parseContext.parse_sidebyside",
+- },
+- "cvb": {
+- "filecache_test.testIPCValueB",
+- "cover.Boundary",
+- },
+- "dho": {
+- "gocommand.DebugHangingGoCommands",
+- "protocol.DocumentHighlightOptions",
+- },
+- "flg": {
+- "completion.FALLTHROUGH",
+- "main.flagGoCmd",
+- },
+- "fvi": {
+- "godoc.fileIndexVersion",
+- "macho.FlagSubsectionsViaSymbols",
+- },
+- }
+-
+- for query, symbols := range queryRanks {
+- t.Run(query, func(t *testing.T) {
+- matcher := NewSymbolMatcher(query)
+- prev := 0.0
+- for _, sym := range symbols {
+- _, score := matcher.Match([]string{sym})
+- t.Logf("Match(%q) = %v", sym, score)
+- if score <= prev {
+- t.Errorf("Match(%q) = _, %v, want > %v", sym, score, prev)
+- }
+- prev = score
+- }
+- })
+- }
+-}
+-
+-func TestMatcherSimilarities(t *testing.T) {
+- // This test compares the fuzzy matcher with the symbol matcher on a corpus
+- // of qualified identifiers extracted from x/tools.
+- //
+- // These two matchers are not expected to agree, but inspecting differences
+- // can be useful for finding interesting ranking edge cases.
+- t.Skip("unskip this test to compare matchers")
+-
+- idents := collectIdentifiers(t)
+- t.Logf("collected %d unique identifiers", len(idents))
+-
+- // We can't use slices.MaxFunc because we want a custom
+- // scoring (not equivalence) function.
+- topMatch := func(score func(string) float64) string {
+- top := ""
+- topScore := 0.0
+- for _, cand := range idents {
+- if s := score(cand); s > topScore {
+- top = cand
+- topScore = s
+- }
+- }
+- return top
+- }
+-
+- agreed := 0
+- total := 0
+- bad := 0
+- patterns := generatePatterns()
+- for _, pattern := range patterns {
+- total++
+-
+- fm := NewMatcher(pattern)
+- topFuzzy := topMatch(func(input string) float64 {
+- return float64(fm.Score(input))
+- })
+- sm := NewSymbolMatcher(pattern)
+- topSymbol := topMatch(func(input string) float64 {
+- _, score := sm.Match([]string{input})
+- return score
+- })
+- switch {
+- case topFuzzy == "" && topSymbol != "":
+- if false {
+- // The fuzzy matcher has a bug where it misses some matches; for this
+- // test we only care about the symbol matcher.
+- t.Logf("%q matched %q but no fuzzy match", pattern, topSymbol)
+- }
+- total--
+- bad++
+- case topFuzzy != "" && topSymbol == "":
+- t.Fatalf("%q matched %q but no symbol match", pattern, topFuzzy)
+- case topFuzzy == topSymbol:
+- agreed++
+- default:
+- // Enable this log to see mismatches.
+- if false {
+- t.Logf("mismatch for %q: fuzzy: %q, symbol: %q", pattern, topFuzzy, topSymbol)
+- }
+- }
+- }
+- t.Logf("fuzzy matchers agreed on %d out of %d queries (%d bad)", agreed, total, bad)
+-}
+-
+-func collectIdentifiers(tb testing.TB) []string {
+- cfg := &packages.Config{
+- Mode: packages.NeedName | packages.NeedSyntax | packages.NeedFiles,
+- Tests: true,
+- }
+- pkgs, err := packages.Load(cfg, "golang.org/x/tools/...")
+- if err != nil {
+- tb.Fatal(err)
+- }
+- uniqueIdents := make(map[string]bool)
+- decls := 0
+- for _, pkg := range pkgs {
+- for _, f := range pkg.Syntax {
+- for _, decl := range f.Decls {
+- decls++
+- switch decl := decl.(type) {
+- case *ast.GenDecl:
+- for _, spec := range decl.Specs {
+- switch decl.Tok {
+- case token.IMPORT:
+- case token.TYPE:
+- name := spec.(*ast.TypeSpec).Name.Name
+- qualified := pkg.Name + "." + name
+- uniqueIdents[qualified] = true
+- case token.CONST, token.VAR:
+- for _, n := range spec.(*ast.ValueSpec).Names {
+- qualified := pkg.Name + "." + n.Name
+- uniqueIdents[qualified] = true
+- }
+- }
+- }
+- }
+- }
+- }
+- }
+- var idents []string
+- for k := range uniqueIdents {
+- idents = append(idents, k)
+- }
+- sort.Strings(idents)
+- return idents
+-}
+-
+-func generatePatterns() []string {
+- var patterns []string
+- for x := 'a'; x <= 'z'; x++ {
+- for y := 'a'; y <= 'z'; y++ {
+- for z := 'a'; z <= 'z'; z++ {
+- patterns = append(patterns, string(x)+string(y)+string(z))
+- }
+- }
+- }
+- return patterns
+-}
+-
+-// Test that we strongly prefer exact matches.
+-//
+-// In golang/go#60027, we preferred "Runner" for the query "rune" over several
+-// results containing the word "rune" exactly. Following this observation,
+-// scoring was tweaked to more strongly emphasize sequential characters and
+-// exact matches.
+-func TestSymbolRanking_Issue60027(t *testing.T) {
+- matcher := NewSymbolMatcher("rune")
+-
+- // symbols to match, in ascending order of ranking.
+- symbols := []string{
+- "Runner",
+- "singleRuneParam",
+- "Config.ifsRune",
+- "Parser.rune",
+- }
+- prev := 0.0
+- for _, sym := range symbols {
+- _, score := matcher.Match([]string{sym})
+- t.Logf("Match(%q) = %v", sym, score)
+- if score < prev {
+- t.Errorf("Match(%q) = _, %v, want > %v", sym, score, prev)
+- }
+- prev = score
+- }
+-}
+-
+-func TestChunkedMatch(t *testing.T) {
+- matcher := NewSymbolMatcher("test")
+- _, want := matcher.Match([]string{"test"})
+- chunked := [][]string{
+- {"", "test"},
+- {"test", ""},
+- {"te", "st"},
+- }
+-
+- for _, chunks := range chunked {
+- offset, score := matcher.Match(chunks)
+- if offset != 0 || score != want {
+- t.Errorf("Match(%v) = %v, %v, want 0, 1.0", chunks, offset, score)
+- }
+- }
+-}
+diff -urN a/gopls/internal/goasm/definition.go b/gopls/internal/goasm/definition.go
+--- a/gopls/internal/goasm/definition.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/goasm/definition.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,127 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-// Package goasm provides language-server features for files in Go
+-// assembly language (https://go.dev/doc/asm).
+-package goasm
+-
+-import (
+- "context"
+- "fmt"
+- "go/token"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/asm"
+- "golang.org/x/tools/gopls/internal/util/morestrings"
+- "golang.org/x/tools/internal/event"
+-)
+-
+-// Definition handles the textDocument/definition request for Go assembly files.
+-func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) {
+- ctx, done := event.Start(ctx, "goasm.Definition")
+- defer done()
+-
+- mp, err := snapshot.NarrowestMetadataForFile(ctx, fh.URI())
+- if err != nil {
+- return nil, err
+- }
+-
+- // Read the file.
+- content, err := fh.Content()
+- if err != nil {
+- return nil, err
+- }
+- mapper := protocol.NewMapper(fh.URI(), content)
+- offset, err := mapper.PositionOffset(position)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Parse the assembly.
+- //
+- // TODO(adonovan): make this just another
+- // attribute of the type-checked cache.Package.
+- file := asm.Parse(content)
+-
+- // Figure out the selected symbol.
+- // For now, just find the identifier around the cursor.
+- var found *asm.Ident
+- for _, id := range file.Idents {
+- if id.Offset <= offset && offset <= id.End() {
+- found = &id
+- break
+- }
+- }
+- if found == nil {
+- return nil, fmt.Errorf("not an identifier")
+- }
+-
+- // Resolve a symbol with a "." prefix to the current package.
+- sym := found.Name
+- if sym != "" && sym[0] == '.' {
+- sym = string(mp.PkgPath) + sym
+- }
+-
+- // package-qualified symbol?
+- if pkgpath, name, ok := morestrings.CutLast(sym, "."); ok {
+- // Find declaring package among dependencies.
+- //
+- // TODO(adonovan): assembly may legally reference
+- // non-dependencies. For example, sync/atomic calls
+- // internal/runtime/atomic. Perhaps we should search
+- // the entire metadata graph, but that's path-dependent.
+- var declaring *metadata.Package
+- for pkg := range snapshot.MetadataGraph().ForwardReflexiveTransitiveClosure(mp.ID) {
+- if pkg.PkgPath == metadata.PackagePath(pkgpath) {
+- declaring = pkg
+- break
+- }
+- }
+- if declaring == nil {
+- return nil, fmt.Errorf("package %q is not a dependency", pkgpath)
+- }
+-
+- // Find declared symbol in syntax package.
+- pkgs, err := snapshot.TypeCheck(ctx, declaring.ID)
+- if err != nil {
+- return nil, err
+- }
+- pkg := pkgs[0]
+- def := pkg.Types().Scope().Lookup(name)
+- if def == nil {
+- return nil, fmt.Errorf("no symbol %q in package %q", name, pkgpath)
+- }
+-
+- // Map position.
+- pos := def.Pos()
+- pgf, err := pkg.FileEnclosing(pos)
+- if err != nil {
+- return nil, err
+- }
+- loc, err := pgf.PosLocation(pos, pos+token.Pos(len(name)))
+- if err != nil {
+- return nil, err
+- }
+- return []protocol.Location{loc}, nil
+-
+- } else {
+- // local symbols (funcs, vars, labels)
+- for _, id := range file.Idents {
+- if id.Name == found.Name &&
+- (id.Kind == asm.Text || id.Kind == asm.Global || id.Kind == asm.Label) {
+-
+- loc, err := mapper.OffsetLocation(id.Offset, id.End())
+- if err != nil {
+- return nil, err
+- }
+- return []protocol.Location{loc}, nil
+- }
+- }
+- }
+-
+- return nil, nil
+-}
+diff -urN a/gopls/internal/golang/add_import.go b/gopls/internal/golang/add_import.go
+--- a/gopls/internal/golang/add_import.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/add_import.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,29 +0,0 @@
+-// Copyright 2020 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/internal/imports"
+-)
+-
+-// AddImport adds a single import statement to the given file
+-func AddImport(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, importPath string) ([]protocol.TextEdit, error) {
+- pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
+- if err != nil {
+- return nil, err
+- }
+- return ComputeImportFixEdits(snapshot.Options().Local, pgf.Src, &imports.ImportFix{
+- StmtInfo: imports.ImportInfo{
+- ImportPath: importPath,
+- },
+- FixType: imports.AddImport,
+- })
+-}
+diff -urN a/gopls/internal/golang/addtest.go b/gopls/internal/golang/addtest.go
+--- a/gopls/internal/golang/addtest.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/addtest.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,814 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file defines the behavior of the "Add test for FUNC" command.
+-
+-import (
+- "bytes"
+- "context"
+- "errors"
+- "fmt"
+- "go/ast"
+- "go/format"
+- "go/types"
+- "os"
+- "path/filepath"
+- "strconv"
+- "strings"
+- "text/template"
+- "unicode"
+-
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/moremaps"
+- internalastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/imports"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-const testTmplString = `
+-func {{.TestFuncName}}(t *{{.TestingPackageName}}.T) {
+- {{- /* Test cases struct declaration and empty initialization. */}}
+- tests := []struct {
+- name string // description of this test case
+-
+- {{- $commentPrinted := false }}
+- {{- if and .Receiver .Receiver.Constructor}}
+- {{- range .Receiver.Constructor.Args}}
+- {{- if .Name}}
+- {{- if not $commentPrinted}}
+- // Named input parameters for receiver constructor.
+- {{- $commentPrinted = true }}
+- {{- end}}
+- {{.Name}} {{.Type}}
+- {{- end}}
+- {{- end}}
+- {{- end}}
+-
+- {{- $commentPrinted := false }}
+- {{- range .Func.Args}}
+- {{- if .Name}}
+- {{- if not $commentPrinted}}
+- // Named input parameters for target function.
+- {{- $commentPrinted = true }}
+- {{- end}}
+- {{.Name}} {{.Type}}
+- {{- end}}
+- {{- end}}
+-
+- {{- range $index, $res := .Func.Results}}
+- {{- if eq $res.Name "gotErr"}}
+- wantErr bool
+- {{- else if eq $index 0}}
+- want {{$res.Type}}
+- {{- else}}
+- want{{add $index 1}} {{$res.Type}}
+- {{- end}}
+- {{- end}}
+- }{
+- // TODO: Add test cases.
+- }
+-
+- {{- /* Loop over all the test cases. */}}
+- for _, tt := range tests {
+- t.Run(tt.name, func(t *{{.TestingPackageName}}.T) {
+- {{- /* Constructor or empty initialization. */}}
+- {{- if .Receiver}}
+- {{- if .Receiver.Constructor}}
+- {{- /* Receiver variable by calling constructor. */}}
+- {{fieldNames .Receiver.Constructor.Results ""}} := {{if .PackageName}}{{.PackageName}}.{{end}}
+- {{- .Receiver.Constructor.Name}}
+-
+- {{- /* Constructor input parameters. */ -}}
+- (
+- {{- range $index, $arg := .Receiver.Constructor.Args}}
+- {{- if ne $index 0}}, {{end}}
+- {{- if .Name}}tt.{{.Name}}{{else}}{{.Value}}{{end}}
+- {{- end -}}
+- )
+-
+- {{- /* Handles the error return from constructor. */}}
+- {{- $last := last .Receiver.Constructor.Results}}
+- {{- if eq $last.Type "error"}}
+- if err != nil {
+- t.Fatalf("could not construct receiver type: %v", err)
+- }
+- {{- end}}
+- {{- else}}
+- {{- /* Receiver variable declaration. */}}
+- // TODO: construct the receiver type.
+- var {{.Receiver.Var.Name}} {{.Receiver.Var.Type}}
+- {{- end}}
+- {{- end}}
+-
+- {{- /* Got variables. */}}
+- {{if .Func.Results}}{{fieldNames .Func.Results ""}} := {{end}}
+-
+- {{- /* Call expression. */}}
+- {{- if .Receiver}}{{/* Call method by VAR.METHOD. */}}
+- {{- .Receiver.Var.Name}}.
+- {{- else if .PackageName}}{{/* Call function by PACKAGE.FUNC. */}}
+- {{- .PackageName}}.
+- {{- end}}{{.Func.Name}}
+-
+- {{- /* Input parameters. */ -}}
+- (
+- {{- range $index, $arg := .Func.Args}}
+- {{- if ne $index 0}}, {{end}}
+- {{- if .Name}}tt.{{.Name}}{{else}}{{.Value}}{{end}}
+- {{- end -}}
+- )
+-
+- {{- /* Handles the returned error before the rest of return value. */}}
+- {{- $last := last .Func.Results}}
+- {{- if eq $last.Type "error"}}
+- if gotErr != nil {
+- if !tt.wantErr {
+- t.Errorf("{{$.Func.Name}}() failed: %v", gotErr)
+- }
+- return
+- }
+- if tt.wantErr {
+- t.Fatal("{{$.Func.Name}}() succeeded unexpectedly")
+- }
+- {{- end}}
+-
+- {{- /* Compare the returned values except for the last returned error. */}}
+- {{- if or (and .Func.Results (ne $last.Type "error")) (and (gt (len .Func.Results) 1) (eq $last.Type "error"))}}
+- // TODO: update the condition below to compare got with tt.want.
+- {{- range $index, $res := .Func.Results}}
+- {{- if ne $res.Name "gotErr"}}
+- if true {
+- t.Errorf("{{$.Func.Name}}() = %v, want %v", {{.Name}}, tt.{{if eq $index 0}}want{{else}}want{{add $index 1}}{{end}})
+- }
+- {{- end}}
+- {{- end}}
+- {{- end}}
+- })
+- }
+-}
+-`
+-
+-// Name is the name of the field this input parameter should reference.
+-// Value is the expression this input parameter should accept.
+-//
+-// Exactly one of Name or Value must be set.
+-type field struct {
+- Name, Type, Value string
+-}
+-
+-type function struct {
+- Name string
+- Args []field
+- Results []field
+-}
+-
+-type receiver struct {
+- // Var is the name and type of the receiver variable.
+- Var field
+- // Constructor holds information about the constructor for the receiver type.
+- // If no qualified constructor is found, this field will be nil.
+- Constructor *function
+-}
+-
+-type testInfo struct {
+- // TestingPackageName is the package name should be used when referencing
+- // package "testing"
+- TestingPackageName string
+- // PackageName is the package name the target function/method is declared from.
+- PackageName string
+- TestFuncName string
+- // Func holds information about the function or method being tested.
+- Func function
+- // Receiver holds information about the receiver of the function or method
+- // being tested.
+- // This field is nil for functions and non-nil for methods.
+- Receiver *receiver
+-}
+-
+-var testTmpl = template.Must(template.New("test").Funcs(template.FuncMap{
+- "add": func(a, b int) int { return a + b },
+- "last": func(slice []field) field {
+- if len(slice) == 0 {
+- return field{}
+- }
+- return slice[len(slice)-1]
+- },
+- "fieldNames": func(fields []field, qualifier string) (res string) {
+- var names []string
+- for _, f := range fields {
+- names = append(names, qualifier+f.Name)
+- }
+- return strings.Join(names, ", ")
+- },
+-}).Parse(testTmplString))
+-
+-// AddTestForFunc adds a test for the function enclosing the given input range.
+-// It creates a _test.go file if one does not already exist.
+-// It returns the required text edits and the predicted location of the new test
+-// function, which is only valid after the edits have been successfully applied.
+-func AddTestForFunc(ctx context.Context, snapshot *cache.Snapshot, loc protocol.Location) (changes []protocol.DocumentChange, show *protocol.Location, _ error) {
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, loc.URI)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- if metadata.IsCommandLineArguments(pkg.Metadata().ID) {
+- return nil, nil, fmt.Errorf("current file in command-line-arguments package")
+- }
+-
+- if errors := pkg.ParseErrors(); len(errors) > 0 {
+- return nil, nil, fmt.Errorf("package has parse errors: %v", errors[0])
+- }
+- if errors := pkg.TypeErrors(); len(errors) > 0 {
+- return nil, nil, fmt.Errorf("package has type errors: %v", errors[0])
+- }
+-
+- // All three maps map the path of an imported package to
+- // the local name if explicit or "" otherwise.
+- var (
+- fileImports map[string]string // imports in foo.go file
+- testImports map[string]string // imports in foo_test.go file
+- extraImports = make(map[string]string) // imports to add to test file
+- )
+-
+- var collectImports = func(file *ast.File) (map[string]string, error) {
+- imps := make(map[string]string)
+- for _, spec := range file.Imports {
+- // TODO(hxjiang): support dot imports.
+- if spec.Name != nil && spec.Name.Name == "." {
+- return nil, fmt.Errorf("\"add test for func\" does not support files containing dot imports")
+- }
+- path, err := strconv.Unquote(spec.Path.Value)
+- if err != nil {
+- return nil, err
+- }
+- if spec.Name != nil {
+- if spec.Name.Name == "_" {
+- continue
+- }
+- imps[path] = spec.Name.Name
+- } else {
+- imps[path] = ""
+- }
+- }
+- return imps, nil
+- }
+-
+- // Collect all the imports from the x.go, keep track of the local package name.
+- if fileImports, err = collectImports(pgf.File); err != nil {
+- return nil, nil, err
+- }
+-
+- testBase := strings.TrimSuffix(loc.URI.Base(), ".go") + "_test.go"
+- goTestFileURI := protocol.URIFromPath(filepath.Join(loc.URI.DirPath(), testBase))
+-
+- testFH, err := snapshot.ReadFile(ctx, goTestFileURI)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- // TODO(hxjiang): use a fresh name if the same test function name already
+- // exist.
+-
+- var (
+- eofRange protocol.Range // empty selection at end of new file
+- // edits contains all the text edits to be applied to the test file.
+- edits []protocol.TextEdit
+- // xtest indicates whether the test file use package x or x_test.
+- // TODO(hxjiang): We can discuss the option to interpret the user's
+- // intention by which function they are selecting. Have one file for
+- // x_test package testing, one file for x package testing.
+- xtest = true
+- )
+-
+- start, end, err := pgf.RangePos(loc.Range)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
+- if len(path) < 2 {
+- return nil, nil, fmt.Errorf("no enclosing function")
+- }
+-
+- decl, ok := path[len(path)-2].(*ast.FuncDecl)
+- if !ok {
+- return nil, nil, fmt.Errorf("no enclosing function")
+- }
+-
+- fn := pkg.TypesInfo().Defs[decl.Name].(*types.Func)
+- sig := fn.Signature()
+-
+- testPGF, err := snapshot.ParseGo(ctx, testFH, parsego.Header)
+- if err != nil {
+- if !errors.Is(err, os.ErrNotExist) {
+- return nil, nil, err
+- }
+- changes = append(changes, protocol.DocumentChangeCreate(goTestFileURI))
+-
+- // header is the buffer containing the text to add to the beginning of the file.
+- var header bytes.Buffer
+-
+- // If this test file was created by the gopls, add a copyright header and
+- // package decl based on the originating file.
+- // Search for something that looks like a copyright header, to replicate
+- // in the new file.
+- if c := CopyrightComment(pgf.File); c != nil {
+- text, err := pgf.NodeText(c)
+- if err != nil {
+- return nil, nil, err
+- }
+- header.Write(text)
+- // One empty line between copyright header and following.
+- header.WriteString("\n\n")
+- }
+-
+- // If this test file was created by gopls, add build constraints
+- // matching the non-test file.
+- if c := buildConstraintComment(pgf.File); c != nil {
+- text, err := pgf.NodeText(c)
+- if err != nil {
+- return nil, nil, err
+- }
+- header.Write(text)
+- // One empty line between build constraint and following.
+- header.WriteString("\n\n")
+- }
+-
+- // Determine if a new test file should use in-package test (package x)
+- // or external test (package x_test). If any of the function parameters
+- // reference an unexported object, we cannot write out test cases from
+- // an x_test package.
+- externalTestOK := func() bool {
+- if !fn.Exported() {
+- return false
+- }
+- if fn.Signature().Recv() != nil {
+- if _, ident, _ := internalastutil.UnpackRecv(decl.Recv.List[0].Type); ident == nil || !ident.IsExported() {
+- return false
+- }
+- }
+- refsUnexported := false
+- ast.Inspect(decl, func(n ast.Node) bool {
+- // The original function refs to an unexported object from the
+- // same package, so further inspection is unnecessary.
+- if refsUnexported {
+- return false
+- }
+- switch t := n.(type) {
+- case *ast.BlockStmt:
+- // Avoid inspect the function body.
+- return false
+- case *ast.Ident:
+- // Use test variant (package foo) if the function signature
+- // references any unexported objects (like types or
+- // constants) from the same package.
+- // Note: types.PkgName is excluded from this check as it's
+- // always defined in the same package.
+- if obj, ok := pkg.TypesInfo().Uses[t]; ok && !obj.Exported() && obj.Pkg() == pkg.Types() && !is[*types.PkgName](obj) {
+- refsUnexported = true
+- }
+- return false
+- default:
+- return true
+- }
+- })
+- return !refsUnexported
+- }
+-
+- xtest = externalTestOK()
+- if xtest {
+- fmt.Fprintf(&header, "package %s_test\n", pkg.Types().Name())
+- } else {
+- fmt.Fprintf(&header, "package %s\n", pkg.Types().Name())
+- }
+-
+- // Write the copyright and package decl to the beginning of the file.
+- edits = append(edits, protocol.TextEdit{
+- Range: protocol.Range{},
+- NewText: header.String(),
+- })
+- } else { // existing _test.go file.
+- file := testPGF.File
+- if !file.Name.NamePos.IsValid() {
+- return nil, nil, fmt.Errorf("missing package declaration")
+- }
+- switch file.Name.Name {
+- case pgf.File.Name.Name:
+- xtest = false
+- case pgf.File.Name.Name + "_test":
+- xtest = true
+- default:
+- return nil, nil, fmt.Errorf("invalid package declaration %q in test file %q", file.Name, testPGF)
+- }
+-
+- eofRange, err = testPGF.PosRange(file.FileEnd, file.FileEnd)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- // Collect all the imports from the foo_test.go.
+- if testImports, err = collectImports(file); err != nil {
+- return nil, nil, err
+- }
+- }
+-
+- // qual qualifier determines the correct package name to use for a type in
+- // foo_test.go. It does this by:
+- // - Consult imports map from test file foo_test.go.
+- // - If not found, consult imports map from original file foo.go.
+- // If the package is not imported in test file foo_test.go, it is added to
+- // extraImports map.
+- qual := func(p *types.Package) string {
+- // References from an in-package test should not be qualified.
+- if !xtest && p == pkg.Types() {
+- return ""
+- }
+- // Prefer using the package name if already defined in foo_test.go
+- if local, ok := testImports[p.Path()]; ok {
+- if local != "" {
+- return local
+- } else {
+- return p.Name()
+- }
+- }
+- // TODO(hxjiang): we should consult the scope of the test package to
+- // ensure these new imports do not shadow any package-level names.
+- // Prefer the local import name (if any) used in the package under test.
+- if local, ok := fileImports[p.Path()]; ok && local != "" {
+- extraImports[p.Path()] = local
+- return local
+- }
+- // Fall back to the package name since there is no renaming.
+- extraImports[p.Path()] = ""
+- return p.Name()
+- }
+-
+- if xtest {
+- // Reject if function/method is unexported.
+- if !fn.Exported() {
+- return nil, nil, fmt.Errorf("cannot add test of unexported function %s to external test package %s_test", decl.Name, pgf.File.Name)
+- }
+-
+- // Reject if receiver is unexported.
+- if sig.Recv() != nil {
+- if _, ident, _ := internalastutil.UnpackRecv(decl.Recv.List[0].Type); ident == nil || !ident.IsExported() {
+- return nil, nil, fmt.Errorf("cannot add external test for method %s.%s as receiver type is not exported", ident.Name, decl.Name)
+- }
+- }
+- // TODO(hxjiang): reject if the any input parameter type is unexported.
+- // TODO(hxjiang): reject if any return value type is unexported. Explore
+- // the option to drop the return value if the type is unexported.
+- }
+-
+- testName, err := testName(fn)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- data := testInfo{
+- TestingPackageName: qual(types.NewPackage("testing", "testing")),
+- PackageName: qual(pkg.Types()),
+- TestFuncName: testName,
+- Func: function{
+- Name: fn.Name(),
+- },
+- }
+-
+- isContextType := func(t types.Type) bool {
+- return typesinternal.IsTypeNamed(t, "context", "Context")
+- }
+-
+- for i := range sig.Params().Len() {
+- param := sig.Params().At(i)
+- name, typ := param.Name(), param.Type()
+- f := field{Type: types.TypeString(typ, qual)}
+- if i == 0 && isContextType(typ) {
+- f.Value = qual(types.NewPackage("context", "context")) + ".Background()"
+- } else if name == "" || name == "_" {
+- f.Value, _ = typesinternal.ZeroString(typ, qual)
+- } else {
+- f.Name = name
+- }
+- data.Func.Args = append(data.Func.Args, f)
+- }
+-
+- for i := range sig.Results().Len() {
+- typ := sig.Results().At(i).Type()
+- var name string
+- if i == sig.Results().Len()-1 && types.Identical(typ, errorType) {
+- name = "gotErr"
+- } else if i == 0 {
+- name = "got"
+- } else {
+- name = fmt.Sprintf("got%d", i+1)
+- }
+- data.Func.Results = append(data.Func.Results, field{
+- Name: name,
+- Type: types.TypeString(typ, qual),
+- })
+- }
+-
+- if sig.Recv() != nil {
+- // Find the preferred type for the receiver. We don't use
+- // typesinternal.ReceiverNamed here as we want to preserve aliases.
+- recvType := sig.Recv().Type()
+- if ptr, ok := recvType.(*types.Pointer); ok {
+- recvType = ptr.Elem()
+- }
+-
+- t, ok := recvType.(typesinternal.NamedOrAlias)
+- if !ok {
+- return nil, nil, fmt.Errorf("the receiver type is neither named type nor alias type")
+- }
+-
+- var varName string
+- {
+- var possibleNames []string // list of candidates, preferring earlier entries.
+- if len(sig.Recv().Name()) > 0 {
+- possibleNames = append(possibleNames,
+- sig.Recv().Name(), // receiver name.
+- string(sig.Recv().Name()[0]), // first character of receiver name.
+- )
+- }
+- possibleNames = append(possibleNames,
+- string(t.Obj().Name()[0]), // first character of receiver type name.
+- )
+- if len(t.Obj().Name()) >= 2 {
+- possibleNames = append(possibleNames,
+- string(t.Obj().Name()[:2]), // first two character of receiver type name.
+- )
+- }
+- var camelCase []rune
+- for i, s := range t.Obj().Name() {
+- if i == 0 || unicode.IsUpper(s) {
+- camelCase = append(camelCase, s)
+- }
+- }
+- possibleNames = append(possibleNames,
+- string(camelCase), // captalized initials.
+- )
+- for _, name := range possibleNames {
+- name = strings.ToLower(name)
+- if name == "" || name == "t" || name == "tt" {
+- continue
+- }
+- varName = name
+- break
+- }
+- if varName == "" {
+- varName = "r" // default as "r" for "receiver".
+- }
+- }
+-
+- data.Receiver = &receiver{
+- Var: field{
+- Name: varName,
+- Type: types.TypeString(recvType, qual),
+- },
+- }
+-
+- // constructor is the selected constructor for type T.
+- var constructor *types.Func
+-
+- // When finding the qualified constructor, the function should return the
+- // any type whose named type is the same type as T's named type.
+- _, wantType := typesinternal.ReceiverNamed(sig.Recv())
+- for _, name := range pkg.Types().Scope().Names() {
+- f, ok := pkg.Types().Scope().Lookup(name).(*types.Func)
+- if !ok {
+- continue
+- }
+- if f.Signature().Recv() != nil {
+- continue
+- }
+- // Unexported constructor is not visible in x_test package.
+- if xtest && !f.Exported() {
+- continue
+- }
+- // Only allow constructors returning T, T, (T, error), or (T, error).
+- if f.Signature().Results().Len() > 2 || f.Signature().Results().Len() == 0 {
+- continue
+- }
+-
+- _, gotType := typesinternal.ReceiverNamed(f.Signature().Results().At(0))
+- if gotType == nil || !types.Identical(gotType, wantType) {
+- continue
+- }
+-
+- if f.Signature().Results().Len() == 2 && !types.Identical(f.Signature().Results().At(1).Type(), errorType) {
+- continue
+- }
+-
+- if constructor == nil {
+- constructor = f
+- }
+-
+- // Functions named NewType are prioritized as constructors over other
+- // functions that match only the signature criteria.
+- if strings.EqualFold(strings.ToLower(f.Name()), strings.ToLower("new"+t.Obj().Name())) {
+- constructor = f
+- }
+- }
+-
+- if constructor != nil {
+- data.Receiver.Constructor = &function{Name: constructor.Name()}
+- for i := range constructor.Signature().Params().Len() {
+- param := constructor.Signature().Params().At(i)
+- name, typ := param.Name(), param.Type()
+- f := field{Type: types.TypeString(typ, qual)}
+- if i == 0 && isContextType(typ) {
+- f.Value = qual(types.NewPackage("context", "context")) + ".Background()"
+- } else if name == "" || name == "_" {
+- f.Value, _ = typesinternal.ZeroString(typ, qual)
+- } else {
+- f.Name = name
+- }
+- data.Receiver.Constructor.Args = append(data.Receiver.Constructor.Args, f)
+- }
+- for i := range constructor.Signature().Results().Len() {
+- typ := constructor.Signature().Results().At(i).Type()
+- var name string
+- if i == 0 {
+- // The first return value must be of type T, *T, or a type whose named
+- // type is the same as named type of T.
+- name = varName
+- } else if i == constructor.Signature().Results().Len()-1 && types.Identical(typ, errorType) {
+- name = "err"
+- } else {
+- // Drop any return values beyond the first and the last.
+- // e.g., "f, _, _, err := NewFoo()".
+- name = "_"
+- }
+- data.Receiver.Constructor.Results = append(data.Receiver.Constructor.Results, field{
+- Name: name,
+- Type: types.TypeString(typ, qual),
+- })
+- }
+- }
+- }
+-
+- // Resolves duplicate parameter names between the function and its
+- // receiver's constructor. It adds prefix to the constructor's parameters
+- // until no conflicts remain.
+- if data.Receiver != nil && data.Receiver.Constructor != nil {
+- seen := map[string]bool{}
+- for _, f := range data.Func.Args {
+- if f.Name == "" {
+- continue
+- }
+- seen[f.Name] = true
+- }
+-
+- // "" for no change, "c" for constructor, "i" for input.
+- for _, prefix := range []string{"", "c", "c_", "i", "i_"} {
+- conflict := false
+- for _, f := range data.Receiver.Constructor.Args {
+- if f.Name == "" {
+- continue
+- }
+- if seen[prefix+f.Name] {
+- conflict = true
+- break
+- }
+- }
+- if !conflict {
+- for i, f := range data.Receiver.Constructor.Args {
+- if f.Name == "" {
+- continue
+- }
+- data.Receiver.Constructor.Args[i].Name = prefix + data.Receiver.Constructor.Args[i].Name
+- }
+- break
+- }
+- }
+- }
+-
+- // Compute edits to update imports.
+- //
+- // If we're adding to an existing test file, we need to adjust existing
+- // imports. Otherwise, we can simply write out the imports to the new file.
+- if testPGF != nil {
+- var importFixes []*imports.ImportFix
+- for path, name := range extraImports {
+- importFixes = append(importFixes, &imports.ImportFix{
+- StmtInfo: imports.ImportInfo{
+- ImportPath: path,
+- Name: name,
+- },
+- FixType: imports.AddImport,
+- })
+- }
+- importEdits, err := ComputeImportFixEdits(snapshot.Options().Local, testPGF.Src, importFixes...)
+- if err != nil {
+- return nil, nil, fmt.Errorf("could not compute the import fix edits: %w", err)
+- }
+- edits = append(edits, importEdits...)
+- } else {
+- var importsBuffer bytes.Buffer
+- if len(extraImports) == 1 {
+- importsBuffer.WriteString("\nimport ")
+- for path, name := range extraImports {
+- if name != "" {
+- importsBuffer.WriteString(name + " ")
+- }
+- importsBuffer.WriteString(fmt.Sprintf("\"%s\"\n", path))
+- }
+- } else {
+- importsBuffer.WriteString("\nimport(")
+- // Sort for determinism.
+- for path, name := range moremaps.Sorted(extraImports) {
+- importsBuffer.WriteString("\n\t")
+- if name != "" {
+- importsBuffer.WriteString(name + " ")
+- }
+- importsBuffer.WriteString(fmt.Sprintf("\"%s\"", path))
+- }
+- importsBuffer.WriteString("\n)\n")
+- }
+- edits = append(edits, protocol.TextEdit{
+- Range: protocol.Range{},
+- NewText: importsBuffer.String(),
+- })
+- }
+-
+- var test bytes.Buffer
+- if err := testTmpl.Execute(&test, data); err != nil {
+- return nil, nil, err
+- }
+-
+- formatted, err := format.Source(test.Bytes())
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- edits = append(edits,
+- protocol.TextEdit{
+- Range: eofRange,
+- NewText: string(formatted),
+- },
+- )
+-
+- // Show the line of generated test function.
+- {
+- line := eofRange.Start.Line
+- for i := range len(edits) - 1 { // last edits is the func decl
+- e := edits[i]
+- oldLines := e.Range.End.Line - e.Range.Start.Line
+- newLines := uint32(strings.Count(e.NewText, "\n"))
+- line += (newLines - oldLines)
+- }
+- show = &protocol.Location{
+- URI: testFH.URI(),
+- Range: protocol.Range{
+- // Test function template have a new line at beginning.
+- Start: protocol.Position{Line: line + 1},
+- End: protocol.Position{Line: line + 1},
+- },
+- }
+- }
+-
+- return append(changes, protocol.DocumentChangeEdit(testFH, edits)), show, nil
+-}
+-
+-// testName returns the name of the function to use for the new function that
+-// tests fn.
+-// Returns empty string if the fn is ill typed or nil.
+-func testName(fn *types.Func) (string, error) {
+- if fn == nil {
+- return "", fmt.Errorf("input nil function")
+- }
+- testName := "Test"
+- if recv := fn.Signature().Recv(); recv != nil { // method declaration.
+- // Retrieve the unpointered receiver type to ensure the test name is based
+- // on the topmost alias or named type, not the alias' RHS type (potentially
+- // unexported) type.
+- // For example:
+- // type Foo = foo // Foo is an exported alias for the unexported type foo
+- recvType := recv.Type()
+- if ptr, ok := recv.Type().(*types.Pointer); ok {
+- recvType = ptr.Elem()
+- }
+-
+- t, ok := recvType.(typesinternal.NamedOrAlias)
+- if !ok {
+- return "", fmt.Errorf("receiver type is not named type or alias type")
+- }
+-
+- if !t.Obj().Exported() {
+- testName += "_"
+- }
+-
+- testName += t.Obj().Name() + "_"
+- } else if !fn.Exported() { // unexported function declaration.
+- testName += "_"
+- }
+- return testName + fn.Name(), nil
+-}
+diff -urN a/gopls/internal/golang/assembly.go b/gopls/internal/golang/assembly.go
+--- a/gopls/internal/golang/assembly.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/assembly.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,157 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file produces the "Browse GOARCH assembly of f" HTML report.
+-//
+-// See also:
+-// - ./codeaction.go - computes the symbol and offers the CodeAction command.
+-// - ../server/command.go - handles the command by opening a web page.
+-// - ../server/server.go - handles the HTTP request and calls this function.
+-//
+-// For language-server behavior in Go assembly language files,
+-// see [golang.org/x/tools/gopls/internal/goasm].
+-
+-import (
+- "bytes"
+- "context"
+- "fmt"
+- "html"
+- "io"
+- "net/http"
+- "os"
+- "regexp"
+- "strconv"
+- "strings"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/util/morestrings"
+-)
+-
+-// AssemblyHTML returns an HTML document containing an assembly listing of the selected function.
+-//
+-// TODO(adonovan): cross-link jumps and block labels, like github.com/aclements/objbrowse.
+-//
+-// See gopls/internal/test/integration/misc/webserver_test.go for tests.
+-func AssemblyHTML(ctx context.Context, snapshot *cache.Snapshot, w http.ResponseWriter, pkg *cache.Package, symbol string, web Web) {
+- // Prepare to compile the package with -S, and capture its stderr stream.
+- // We use "go test -c" not "go build" as it covers all three packages
+- // (p, "p [p.test]", "p_test [p.test]") in the directory, if they exist.
+- // (See also compileropt.go.)
+- inv, cleanupInvocation, err := snapshot.GoCommandInvocation(cache.NoNetwork, pkg.Metadata().CompiledGoFiles[0].DirPath(),
+- "test", []string{
+- "-c",
+- "-o", os.DevNull,
+- "-gcflags=-S",
+- ".",
+- })
+- if err != nil {
+- // e.g. failed to write overlays (rare)
+- http.Error(w, err.Error(), http.StatusInternalServerError)
+- return
+- }
+- defer cleanupInvocation()
+-
+- escape := html.EscapeString
+-
+- // Emit the start of the report.
+- titleHTML := fmt.Sprintf("%s assembly for %s",
+- escape(snapshot.View().GOARCH()),
+- escape(symbol))
+- io.WriteString(w, `
+-
+-
+-
+- `+titleHTML+`
+-
+-
+-
+-
+-
+- Click on a source line marker L1234 to navigate your editor there.
+- (VS Code users: please upvote #208093)
+-
+-
Compiling...
+-
+-`)
+- if flusher, ok := w.(http.Flusher); ok {
+- flusher.Flush()
+- }
+-
+- // At this point errors must be reported by writing HTML.
+- // To do this, set "status" return early.
+-
+- var buf bytes.Buffer
+- status := "Reload the page to recompile."
+- defer func() {
+- // Update the "Compiling..." message.
+- fmt.Fprintf(&buf, `
+-
+- The selected code contains references to these free* symbols:
+-
+-`)
+-
+- // Present the refs in three sections: imported, same package, local.
+-
+- // -- imported symbols --
+-
+- // Show one item per package, with a list of symbols.
+- fmt.Fprintf(&buf, "
⬤ Imported symbols
\n")
+- fmt.Fprintf(&buf, "
\n")
+- for _, imp := range model.Imported {
+- fmt.Fprintf(&buf, "
\n")
+-
+- // -- package and local symbols --
+-
+- showSymbols := func(scope, title string, symbols []Symbol) {
+- fmt.Fprintf(&buf, "
⬤ %s
\n", scope, title)
+- fmt.Fprintf(&buf, "
\n")
+- pre := buf.Len()
+- for _, sym := range symbols {
+- fmt.Fprintf(&buf, "
%s ", sym.Kind) // of rightmost symbol in dotted path
+- for i, obj := range sym.Refs {
+- if i > 0 {
+- buf.WriteByte('.')
+- }
+- buf.WriteString(objHTML(pkg.FileSet(), web, obj))
+- }
+- fmt.Fprintf(&buf, " %s
\n", html.EscapeString(sym.Type))
+- }
+- if buf.Len() == pre {
+- fmt.Fprintf(&buf, "
+- *A symbol is "free" if it is referenced within the selection but declared
+- outside of it.
+-
+- The free variables are approximately the set of parameters that
+- would be needed if the block were extracted into its own function in
+- the same package.
+-
+- Free identifiers may include local types and control labels as well.
+-
+- Even when you don't intend to extract a block into a new function,
+- this information can help you to tell at a glance what names a block
+- of code depends on.
+-
+-
+- Each dotted path of identifiers (such as file.Name.Pos) is reported
+- as a separate item, so that you can see which parts of a complex
+- type are actually needed.
+-
+- The free symbols referenced by the body of a function may
+- reveal that only a small part (a single field of a struct, say) of
+- one of the function's parameters is used, allowing you to simplify
+- and generalize the function by choosing a different type for that
+- parameter.
+-
+-`)
+- return buf.Bytes()
+-}
+-
+-// A freeRef records a reference to a dotted path obj.x.y,
+-// where obj (=objects[0]) is a free symbol.
+-type freeRef struct {
+- objects []types.Object // [obj x y]
+- dotted string // "obj.x.y" (used as sort key)
+- scope string // scope of obj: pkg|file|local
+- expr ast.Expr // =*Ident|*SelectorExpr
+- typ types.Type // type of obj.x.y
+-}
+-
+-// freeRefs returns the list of references to free symbols (from
+-// within the selection to a symbol declared outside of it).
+-// It uses only info.{Scopes,Types,Uses}.
+-func freeRefs(pkg *types.Package, info *types.Info, file *ast.File, start, end token.Pos) []*freeRef {
+- // Keep us honest about which fields we access.
+- info = &types.Info{
+- Scopes: info.Scopes,
+- Types: info.Types,
+- Uses: info.Uses,
+- }
+-
+- fileScope := info.Scopes[file]
+- pkgScope := fileScope.Parent()
+-
+- // id is called for the leftmost id x in each dotted chain such as (x.y).z.
+- // suffix is the reversed suffix of selections (e.g. [z y]).
+- id := func(n *ast.Ident, suffix []types.Object) *freeRef {
+- obj := info.Uses[n]
+- if obj == nil {
+- return nil // not a reference
+- }
+- if start <= obj.Pos() && obj.Pos() < end {
+- return nil // defined within selection => not free
+- }
+- parent := obj.Parent()
+-
+- // Compute dotted path.
+- objects := append(suffix, obj)
+- if obj.Pkg() != nil && obj.Pkg() != pkg && typesinternal.IsPackageLevel(obj) { // dot import
+- // Synthesize the implicit PkgName.
+- pkgName := types.NewPkgName(token.NoPos, pkg, obj.Pkg().Name(), obj.Pkg())
+- parent = fileScope
+- objects = append(objects, pkgName)
+- }
+- slices.Reverse(objects)
+- var dotted strings.Builder
+- for i, obj := range objects {
+- if obj == nil {
+- return nil // type error
+- }
+- if i > 0 {
+- dotted.WriteByte('.')
+- }
+- dotted.WriteString(obj.Name())
+- }
+-
+- // Compute scope of base object.
+- var scope string
+- switch parent {
+- case nil:
+- return nil // interface method or struct field
+- case types.Universe:
+- return nil // built-in (not interesting)
+- case fileScope:
+- scope = "file" // defined at file scope (imported package)
+- case pkgScope:
+- scope = "pkg" // defined at package level
+- default:
+- scope = "local" // defined within current function
+- }
+-
+- return &freeRef{
+- objects: objects,
+- dotted: dotted.String(),
+- scope: scope,
+- }
+- }
+-
+- // sel(x.y.z, []) calls sel(x.y, [z]) calls id(x, [z, y]).
+- sel := func(sel *ast.SelectorExpr, suffix []types.Object) *freeRef {
+- for {
+- suffix = append(suffix, info.Uses[sel.Sel])
+-
+- switch x := ast.Unparen(sel.X).(type) {
+- case *ast.Ident:
+- return id(x, suffix)
+- default:
+- return nil
+- case *ast.SelectorExpr:
+- sel = x
+- }
+- }
+- }
+-
+- // Visit all the identifiers in the selected ASTs.
+- var free []*freeRef
+- path, _ := astutil.PathEnclosingInterval(file, start, end)
+- var visit func(n ast.Node) bool
+- visit = func(n ast.Node) bool {
+- // Is this node contained within the selection?
+- // (freesymbols permits inexact selections,
+- // like two stmts in a block.)
+- if n != nil && start <= n.Pos() && n.End() <= end {
+- var ref *freeRef
+- switch n := n.(type) {
+- case *ast.Ident:
+- ref = id(n, nil)
+- case *ast.SelectorExpr:
+- ref = sel(n, nil)
+- }
+-
+- if ref != nil {
+- ref.expr = n.(ast.Expr)
+- if tv, ok := info.Types[ref.expr]; ok {
+- ref.typ = tv.Type
+- } else {
+- ref.typ = types.Typ[types.Invalid]
+- }
+- free = append(free, ref)
+- }
+-
+- // After visiting x.sel, don't descend into sel.
+- // Descend into x only if we didn't get a ref for x.sel.
+- if sel, ok := n.(*ast.SelectorExpr); ok {
+- if ref == nil {
+- ast.Inspect(sel.X, visit)
+- }
+- return false
+- }
+- }
+-
+- return true // descend
+- }
+- ast.Inspect(path[0], visit)
+- return free
+-}
+-
+-// objHTML returns HTML for obj.Name(), possibly marked up as a link
+-// to the web server that, when visited, opens the declaration in the
+-// client editor.
+-func objHTML(fset *token.FileSet, web Web, obj types.Object) string {
+- text := obj.Name()
+- if posn := safetoken.StartPosition(fset, obj.Pos()); posn.IsValid() {
+- url := web.SrcURL(posn.Filename, posn.Line, posn.Column)
+- return sourceLink(text, url)
+- }
+- return text
+-}
+-
+-// sourceLink returns HTML for a link to open a file in the client editor.
+-func sourceLink(text, url string) string {
+- // The /src URL returns nothing but has the side effect
+- // of causing the LSP client to open the requested file.
+- // So we use onclick to prevent the browser from navigating.
+- // We keep the href attribute as it causes the to render
+- // as a link: blue, underlined, with URL hover information.
+- return fmt.Sprintf(`%[2]s`,
+- html.EscapeString(url), text)
+-}
+diff -urN a/gopls/internal/golang/freesymbols_test.go b/gopls/internal/golang/freesymbols_test.go
+--- a/gopls/internal/golang/freesymbols_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/freesymbols_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,132 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "fmt"
+- "go/ast"
+- "go/importer"
+- "go/parser"
+- "go/token"
+- "go/types"
+- "reflect"
+- "runtime"
+- "strings"
+- "testing"
+-
+- "github.com/google/go-cmp/cmp"
+-)
+-
+-// TestFreeRefs is a unit test of the free-references algorithm.
+-func TestFreeRefs(t *testing.T) {
+- if runtime.GOOS == "js" || runtime.GOARCH == "wasm" {
+- t.Skip("some test imports are unsupported on js or wasm")
+- }
+-
+- for i, test := range []struct {
+- src string
+- want []string // expected list of "scope kind dotted-path" triples
+- }{
+- {
+- // basic example (has a "cannot infer" type error)
+- `package p; func f[T ~int](x any) { var y T; « f(x.(T) + y) » }`,
+- []string{"pkg func f", "local var x", "local typename T", "local var y"},
+- },
+- {
+- // selection need not be tree-aligned
+- `package p; type T int; type U « T; func _(x U) »`,
+- []string{"pkg typename T", "pkg typename U"},
+- },
+- {
+- // imported symbols
+- `package p; import "fmt"; func f() { « var x fmt.Stringer » }`,
+- []string{"file pkgname fmt.Stringer"},
+- },
+- {
+- // unsafe and error, our old nemeses
+- `package p; import "unsafe"; var ( « _ unsafe.Pointer; _ = error(nil).Error »; )`,
+- []string{"file pkgname unsafe.Pointer"},
+- },
+- {
+- // two attributes of a var, but not the var itself
+- `package p; import "bytes"; func _(buf bytes.Buffer) { « buf.WriteByte(0); buf.WriteString(""); » }`,
+- []string{"local var buf.WriteByte", "local var buf.WriteString"},
+- },
+- {
+- // dot imports (an edge case)
+- `package p; import . "errors"; var _ = « New»`,
+- []string{"file pkgname errors.New"},
+- },
+- {
+- // struct field (regression test for overzealous dot import logic)
+- `package p; import "net/url"; var _ = «url.URL{Host: ""}»`,
+- []string{"file pkgname url.URL"},
+- },
+- {
+- // dot imports (another regression test of same)
+- `package p; import . "net/url"; var _ = «URL{Host: ""}»`,
+- []string{"file pkgname url.URL"},
+- },
+- {
+- // dot import of unsafe (a corner case)
+- `package p; import . "unsafe"; var _ « Pointer»`,
+- []string{"file pkgname unsafe.Pointer"},
+- },
+- {
+- // dotted path
+- `package p; import "go/build"; var _ = « build.Default.GOOS »`,
+- []string{"file pkgname build.Default.GOOS"},
+- },
+- {
+- // type error
+- `package p; import "nope"; var _ = « nope.nope.nope »`,
+- []string{"file pkgname nope"},
+- },
+- } {
+- name := fmt.Sprintf("file%d.go", i)
+- t.Run(name, func(t *testing.T) {
+- fset := token.NewFileSet()
+- startOffset := strings.Index(test.src, "«")
+- endOffset := strings.Index(test.src, "»")
+- if startOffset < 0 || endOffset < startOffset {
+- t.Fatalf("invalid «...» selection (%d:%d)", startOffset, endOffset)
+- }
+- src := test.src[:startOffset] +
+- " " +
+- test.src[startOffset+len("«"):endOffset] +
+- " " +
+- test.src[endOffset+len("»"):]
+- f, err := parser.ParseFile(fset, name, src, parser.SkipObjectResolution)
+- if err != nil {
+- t.Fatal(err)
+- }
+- conf := &types.Config{
+- Importer: importer.Default(),
+- Error: func(err error) { t.Log(err) }, // not fatal
+- }
+- info := &types.Info{
+- Uses: make(map[*ast.Ident]types.Object),
+- Scopes: make(map[ast.Node]*types.Scope),
+- Types: make(map[ast.Expr]types.TypeAndValue),
+- }
+- pkg, _ := conf.Check(f.Name.Name, fset, []*ast.File{f}, info) // ignore errors
+- tf := fset.File(f.Package)
+- refs := freeRefs(pkg, info, f, tf.Pos(startOffset), tf.Pos(endOffset))
+-
+- kind := func(obj types.Object) string { // e.g. "var", "const"
+- return strings.ToLower(reflect.TypeOf(obj).Elem().Name())
+- }
+-
+- var got []string
+- for _, ref := range refs {
+- msg := ref.scope + " " + kind(ref.objects[0]) + " " + ref.dotted
+- got = append(got, msg)
+- }
+- if diff := cmp.Diff(test.want, got); diff != "" {
+- t.Errorf("(-want +got)\n%s", diff)
+- }
+- })
+- }
+-}
+diff -urN a/gopls/internal/golang/highlight.go b/gopls/internal/golang/highlight.go
+--- a/gopls/internal/golang/highlight.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/highlight.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,744 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "strconv"
+- "strings"
+-
+- astutil "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- internalastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/fmtstr"
+-)
+-
+-func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.DocumentHighlight, error) {
+- ctx, done := event.Start(ctx, "golang.Highlight")
+- defer done()
+-
+- // We always want fully parsed files for highlight, regardless
+- // of whether the file belongs to a workspace package.
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+- if err != nil {
+- return nil, fmt.Errorf("getting package for Highlight: %w", err)
+- }
+-
+- pos, err := pgf.PositionPos(position)
+- if err != nil {
+- return nil, err
+- }
+- path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
+- if len(path) == 0 {
+- return nil, fmt.Errorf("no enclosing position found for %v:%v", position.Line, position.Character)
+- }
+- // If start == end for astutil.PathEnclosingInterval, the 1-char interval
+- // following start is used instead. As a result, we might not get an exact
+- // match so we should check the 1-char interval to the left of the passed
+- // in position to see if that is an exact match.
+- if _, ok := path[0].(*ast.Ident); !ok {
+- if p, _ := astutil.PathEnclosingInterval(pgf.File, pos-1, pos-1); p != nil {
+- switch p[0].(type) {
+- case *ast.Ident, *ast.SelectorExpr:
+- path = p // use preceding ident/selector
+- }
+- }
+- }
+- result, err := highlightPath(pkg.TypesInfo(), path, pos)
+- if err != nil {
+- return nil, err
+- }
+- var ranges []protocol.DocumentHighlight
+- for rng, kind := range result {
+- rng, err := pgf.PosRange(rng.start, rng.end)
+- if err != nil {
+- return nil, err
+- }
+- ranges = append(ranges, protocol.DocumentHighlight{
+- Range: rng,
+- Kind: kind,
+- })
+- }
+- return ranges, nil
+-}
+-
+-// highlightPath returns ranges to highlight for the given enclosing path,
+-// which should be the result of astutil.PathEnclosingInterval.
+-func highlightPath(info *types.Info, path []ast.Node, pos token.Pos) (map[posRange]protocol.DocumentHighlightKind, error) {
+- result := make(map[posRange]protocol.DocumentHighlightKind)
+-
+- // Inside a call to a printf-like function (as identified
+- // by a simple heuristic).
+- // Treat each corresponding ("%v", arg) pair as a highlight class.
+- for _, node := range path {
+- if call, ok := node.(*ast.CallExpr); ok {
+- lit, idx := formatStringAndIndex(info, call)
+- if idx != -1 {
+- highlightPrintf(call, idx, pos, lit, result)
+- }
+- }
+- }
+-
+- file := path[len(path)-1].(*ast.File)
+- switch node := path[0].(type) {
+- case *ast.BasicLit:
+- // Import path string literal?
+- if len(path) > 1 {
+- if imp, ok := path[1].(*ast.ImportSpec); ok {
+- highlight := func(n ast.Node) {
+- highlightNode(result, n, protocol.Text)
+- }
+-
+- // Highlight the import itself...
+- highlight(imp)
+-
+- // ...and all references to it in the file.
+- if pkgname := info.PkgNameOf(imp); pkgname != nil {
+- ast.Inspect(file, func(n ast.Node) bool {
+- if id, ok := n.(*ast.Ident); ok &&
+- info.Uses[id] == pkgname {
+- highlight(id)
+- }
+- return true
+- })
+- }
+- return result, nil
+- }
+- }
+- highlightFuncControlFlow(path, result)
+- case *ast.ReturnStmt, *ast.FuncDecl, *ast.FuncType:
+- highlightFuncControlFlow(path, result)
+- case *ast.Ident:
+- // Check if ident is inside return or func decl.
+- highlightFuncControlFlow(path, result)
+- highlightIdentifier(node, file, info, result)
+- case *ast.ForStmt, *ast.RangeStmt:
+- highlightLoopControlFlow(path, info, result)
+- case *ast.SwitchStmt, *ast.TypeSwitchStmt:
+- highlightSwitchFlow(path, info, result)
+- case *ast.BranchStmt:
+- // BREAK can exit a loop, switch or select, while CONTINUE exit a loop so
+- // these need to be handled separately. They can also be embedded in any
+- // other loop/switch/select if they have a label. TODO: add support for
+- // GOTO and FALLTHROUGH as well.
+- switch node.Tok {
+- case token.BREAK:
+- if node.Label != nil {
+- highlightLabeledFlow(path, info, node, result)
+- } else {
+- highlightUnlabeledBreakFlow(path, info, result)
+- }
+- case token.CONTINUE:
+- if node.Label != nil {
+- highlightLabeledFlow(path, info, node, result)
+- } else {
+- highlightLoopControlFlow(path, info, result)
+- }
+- }
+- }
+-
+- return result, nil
+-}
+-
+-// formatStringAndIndex returns the BasicLit and index of the BasicLit (the last
+-// non-variadic parameter) within the given printf-like call
+-// expression, returns -1 as index if unknown.
+-func formatStringAndIndex(info *types.Info, call *ast.CallExpr) (*ast.BasicLit, int) {
+- typ := info.Types[call.Fun].Type
+- if typ == nil {
+- return nil, -1 // missing type
+- }
+- sig, ok := typ.(*types.Signature)
+- if !ok {
+- return nil, -1 // ill-typed
+- }
+- if !sig.Variadic() {
+- // Skip checking non-variadic functions.
+- return nil, -1
+- }
+- idx := sig.Params().Len() - 2
+- if !(0 <= idx && idx < len(call.Args)) {
+- // Skip checking functions without a format string parameter, or
+- // missing the corresponding format argument.
+- return nil, -1
+- }
+- // We only care about literal format strings, so fmt.Sprint("a"+"b%s", "bar") won't be highlighted.
+- if lit, ok := call.Args[idx].(*ast.BasicLit); ok && lit.Kind == token.STRING {
+- return lit, idx
+- }
+- return nil, -1
+-}
+-
+-// highlightPrintf highlights operations in a format string and their corresponding
+-// variadic arguments in a (possible) printf-style function call.
+-// For example:
+-//
+-// fmt.Printf("Hello %s, you scored %d", name, score)
+-//
+-// If the cursor is on %s or name, it will highlight %s as a write operation,
+-// and name as a read operation.
+-func highlightPrintf(call *ast.CallExpr, idx int, cursorPos token.Pos, lit *ast.BasicLit, result map[posRange]protocol.DocumentHighlightKind) {
+- format, err := strconv.Unquote(lit.Value)
+- if err != nil {
+- return
+- }
+- if !strings.Contains(format, "%") {
+- return
+- }
+- operations, err := fmtstr.Parse(format, idx)
+- if err != nil {
+- return
+- }
+-
+- // fmt.Printf("%[1]d %[1].2d", 3)
+- //
+- // When cursor is in `%[1]d`, we record `3` being successfully highlighted.
+- // And because we will also record `%[1].2d`'s corresponding arguments index is `3`
+- // in `visited`, even though it will not highlight any item in the first pass,
+- // in the second pass we can correctly highlight it. So the three are the same class.
+- succeededArg := 0
+- visited := make(map[posRange]int, 0)
+-
+- // highlightPair highlights the operation and its potential argument pair if the cursor is within either range.
+- highlightPair := func(rang fmtstr.Range, argIndex int) {
+- rangeStart, rangeEnd, err := internalastutil.RangeInStringLiteral(lit, rang.Start, rang.End)
+- if err != nil {
+- return
+- }
+- visited[posRange{rangeStart, rangeEnd}] = argIndex
+-
+- var arg ast.Expr
+- if argIndex < len(call.Args) {
+- arg = call.Args[argIndex]
+- }
+-
+- // cursorPos can't equal to end position, otherwise the two
+- // neighborhood such as (%[2]*d) are both highlighted if cursor in "d" (ending of [2]*).
+- if rangeStart <= cursorPos && cursorPos < rangeEnd ||
+- arg != nil && internalastutil.NodeContains(arg, cursorPos) {
+- highlightRange(result, rangeStart, rangeEnd, protocol.Write)
+- if arg != nil {
+- succeededArg = argIndex
+- highlightRange(result, arg.Pos(), arg.End(), protocol.Read)
+- }
+- }
+- }
+-
+- for _, op := range operations {
+- // If width or prec has any *, we can not highlight the full range from % to verb,
+- // because it will overlap with the sub-range of *, for example:
+- //
+- // fmt.Printf("%*[3]d", 4, 5, 6)
+- // ^ ^ we can only highlight this range when cursor in 6. '*' as a one-rune range will
+- // highlight for 4.
+- hasAsterisk := false
+-
+- // Try highlight Width if there is a *.
+- if op.Width.Dynamic != -1 {
+- hasAsterisk = true
+- highlightPair(op.Width.Range, op.Width.Dynamic)
+- }
+-
+- // Try highlight Precision if there is a *.
+- if op.Prec.Dynamic != -1 {
+- hasAsterisk = true
+- highlightPair(op.Prec.Range, op.Prec.Dynamic)
+- }
+-
+- // Try highlight Verb.
+- if op.Verb.Verb != '%' {
+- // If any * is found inside operation, narrow the highlight range.
+- if hasAsterisk {
+- highlightPair(op.Verb.Range, op.Verb.ArgIndex)
+- } else {
+- highlightPair(op.Range, op.Verb.ArgIndex)
+- }
+- }
+- }
+-
+- // Second pass, try to highlight those missed operations.
+- for rang, argIndex := range visited {
+- if succeededArg == argIndex {
+- highlightRange(result, rang.start, rang.end, protocol.Write)
+- }
+- }
+-}
+-
+-type posRange struct {
+- start, end token.Pos
+-}
+-
+-// highlightFuncControlFlow adds highlight ranges to the result map to
+-// associate results and result parameters.
+-//
+-// Specifically, if the cursor is in a result or result parameter, all
+-// results and result parameters with the same index are highlighted. If the
+-// cursor is in a 'func' or 'return' keyword, the func keyword as well as all
+-// returns from that func are highlighted.
+-//
+-// As a special case, if the cursor is within a complicated expression, control
+-// flow highlighting is disabled, as it would highlight too much.
+-func highlightFuncControlFlow(path []ast.Node, result map[posRange]protocol.DocumentHighlightKind) {
+-
+- var (
+- funcType *ast.FuncType // type of enclosing func, or nil
+- funcBody *ast.BlockStmt // body of enclosing func, or nil
+- returnStmt *ast.ReturnStmt // enclosing ReturnStmt within the func, or nil
+- )
+-
+-findEnclosingFunc:
+- for i, n := range path {
+- switch n := n.(type) {
+- // TODO(rfindley, low priority): these pre-existing cases for KeyValueExpr
+- // and CallExpr appear to avoid highlighting when the cursor is in a
+- // complicated expression. However, the basis for this heuristic is
+- // unclear. Can we formalize a rationale?
+- case *ast.KeyValueExpr:
+- // If cursor is in a key: value expr, we don't want control flow highlighting.
+- return
+-
+- case *ast.CallExpr:
+- // If cursor is an arg in a callExpr, we don't want control flow highlighting.
+- if i > 0 {
+- for _, arg := range n.Args {
+- if arg == path[i-1] {
+- return
+- }
+- }
+- }
+-
+- case *ast.FuncLit:
+- funcType = n.Type
+- funcBody = n.Body
+- break findEnclosingFunc
+-
+- case *ast.FuncDecl:
+- funcType = n.Type
+- funcBody = n.Body
+- break findEnclosingFunc
+-
+- case *ast.ReturnStmt:
+- returnStmt = n
+- }
+- }
+-
+- if funcType == nil {
+- return // cursor is not in a function
+- }
+-
+- // Helper functions for inspecting the current location.
+- var (
+- pos = path[0].Pos()
+- inSpan = func(start, end token.Pos) bool { return start <= pos && pos < end }
+- inNode = func(n ast.Node) bool { return inSpan(n.Pos(), n.End()) }
+- )
+-
+- inResults := funcType.Results != nil && inNode(funcType.Results)
+-
+- // If the cursor is on a "return" or "func" keyword, but not highlighting any
+- // specific field or expression, we should highlight all of the exit points
+- // of the function, including the "return" and "func" keywords.
+- funcEnd := funcType.Func + token.Pos(len("func"))
+- highlightAll := path[0] == returnStmt || inSpan(funcType.Func, funcEnd)
+- var highlightIndexes map[int]bool
+-
+- if highlightAll {
+- // Add the "func" part of the func declaration.
+- highlightRange(result, funcType.Func, funcEnd, protocol.Text)
+- } else if returnStmt == nil && !inResults {
+- return // nothing to highlight
+- } else {
+- // If we're not highlighting the entire return statement, we need to collect
+- // specific result indexes to highlight. This may be more than one index if
+- // the cursor is on a multi-name result field, but not in any specific name.
+- if !highlightAll {
+- highlightIndexes = make(map[int]bool)
+- if returnStmt != nil {
+- for i, n := range returnStmt.Results {
+- if inNode(n) {
+- highlightIndexes[i] = true
+- break
+- }
+- }
+- }
+-
+- if funcType.Results != nil {
+- // Scan fields, either adding highlights according to the highlightIndexes
+- // computed above, or accounting for the cursor position within the result
+- // list.
+- // (We do both at once to avoid repeating the cumbersome field traversal.)
+- i := 0
+- findField:
+- for _, field := range funcType.Results.List {
+- for j, name := range field.Names {
+- if inNode(name) || highlightIndexes[i+j] {
+- highlightNode(result, name, protocol.Text)
+- highlightIndexes[i+j] = true
+- break findField // found/highlighted the specific name
+- }
+- }
+- // If the cursor is in a field but not in a name (e.g. in the space, or
+- // the type), highlight the whole field.
+- //
+- // Note that this may not be ideal if we're at e.g.
+- //
+- // (x,‸y int, z int8)
+- //
+- // ...where it would make more sense to highlight only y. But we don't
+- // reach this function if not in a func, return, ident, or basiclit.
+- if inNode(field) || highlightIndexes[i] {
+- highlightNode(result, field, protocol.Text)
+- highlightIndexes[i] = true
+- if inNode(field) {
+- for j := range field.Names {
+- highlightIndexes[i+j] = true
+- }
+- }
+- break findField // found/highlighted the field
+- }
+-
+- n := len(field.Names)
+- if n == 0 {
+- n = 1
+- }
+- i += n
+- }
+- }
+- }
+- }
+-
+- if funcBody != nil {
+- ast.Inspect(funcBody, func(n ast.Node) bool {
+- switch n := n.(type) {
+- case *ast.FuncDecl, *ast.FuncLit:
+- // Don't traverse into any functions other than enclosingFunc.
+- return false
+- case *ast.ReturnStmt:
+- if highlightAll {
+- // Add the entire return statement.
+- highlightNode(result, n, protocol.Text)
+- } else {
+- // Add the highlighted indexes.
+- for i, expr := range n.Results {
+- if highlightIndexes[i] {
+- highlightNode(result, expr, protocol.Text)
+- }
+- }
+- }
+- return false
+-
+- }
+- return true
+- })
+- }
+-}
+-
+-// highlightUnlabeledBreakFlow highlights the innermost enclosing for/range/switch or swlect
+-func highlightUnlabeledBreakFlow(path []ast.Node, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) {
+- // Reverse walk the path until we find closest loop, select, or switch.
+- for _, n := range path {
+- switch n.(type) {
+- case *ast.ForStmt, *ast.RangeStmt:
+- highlightLoopControlFlow(path, info, result)
+- return // only highlight the innermost statement
+- case *ast.SwitchStmt, *ast.TypeSwitchStmt:
+- highlightSwitchFlow(path, info, result)
+- return
+- case *ast.SelectStmt:
+- // TODO: add highlight when breaking a select.
+- return
+- }
+- }
+-}
+-
+-// highlightLabeledFlow highlights the enclosing labeled for, range,
+-// or switch statement denoted by a labeled break or continue stmt.
+-func highlightLabeledFlow(path []ast.Node, info *types.Info, stmt *ast.BranchStmt, result map[posRange]protocol.DocumentHighlightKind) {
+- use := info.Uses[stmt.Label]
+- if use == nil {
+- return
+- }
+- for _, n := range path {
+- if label, ok := n.(*ast.LabeledStmt); ok && info.Defs[label.Label] == use {
+- switch label.Stmt.(type) {
+- case *ast.ForStmt, *ast.RangeStmt:
+- highlightLoopControlFlow([]ast.Node{label.Stmt, label}, info, result)
+- case *ast.SwitchStmt, *ast.TypeSwitchStmt:
+- highlightSwitchFlow([]ast.Node{label.Stmt, label}, info, result)
+- }
+- return
+- }
+- }
+-}
+-
+-func labelFor(path []ast.Node) *ast.Ident {
+- if len(path) > 1 {
+- if n, ok := path[1].(*ast.LabeledStmt); ok {
+- return n.Label
+- }
+- }
+- return nil
+-}
+-
+-func highlightLoopControlFlow(path []ast.Node, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) {
+- var loop ast.Node
+- var loopLabel *ast.Ident
+- stmtLabel := labelFor(path)
+-Outer:
+- // Reverse walk the path till we get to the for loop.
+- for i := range path {
+- switch n := path[i].(type) {
+- case *ast.ForStmt, *ast.RangeStmt:
+- loopLabel = labelFor(path[i:])
+-
+- if stmtLabel == nil || loopLabel == stmtLabel {
+- loop = n
+- break Outer
+- }
+- }
+- }
+- if loop == nil {
+- return
+- }
+-
+- // Add the for statement.
+- rngStart := loop.Pos()
+- rngEnd := loop.Pos() + token.Pos(len("for"))
+- highlightRange(result, rngStart, rngEnd, protocol.Text)
+-
+- // Traverse AST to find branch statements within the same for-loop.
+- ast.Inspect(loop, func(n ast.Node) bool {
+- switch n.(type) {
+- case *ast.ForStmt, *ast.RangeStmt:
+- return loop == n
+- case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
+- return false
+- }
+- b, ok := n.(*ast.BranchStmt)
+- if !ok {
+- return true
+- }
+- if b.Label == nil || info.Uses[b.Label] == info.Defs[loopLabel] {
+- highlightNode(result, b, protocol.Text)
+- }
+- return true
+- })
+-
+- // Find continue statements in the same loop or switches/selects.
+- ast.Inspect(loop, func(n ast.Node) bool {
+- switch n.(type) {
+- case *ast.ForStmt, *ast.RangeStmt:
+- return loop == n
+- }
+-
+- if n, ok := n.(*ast.BranchStmt); ok && n.Tok == token.CONTINUE {
+- highlightNode(result, n, protocol.Text)
+- }
+- return true
+- })
+-
+- // We don't need to check other for loops if we aren't looking for labeled statements.
+- if loopLabel == nil {
+- return
+- }
+-
+- // Find labeled branch statements in any loop.
+- ast.Inspect(loop, func(n ast.Node) bool {
+- b, ok := n.(*ast.BranchStmt)
+- if !ok {
+- return true
+- }
+- // statement with labels that matches the loop
+- if b.Label != nil && info.Uses[b.Label] == info.Defs[loopLabel] {
+- highlightNode(result, b, protocol.Text)
+- }
+- return true
+- })
+-}
+-
+-func highlightSwitchFlow(path []ast.Node, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) {
+- var switchNode ast.Node
+- var switchNodeLabel *ast.Ident
+- stmtLabel := labelFor(path)
+-Outer:
+- // Reverse walk the path till we get to the switch statement.
+- for i := range path {
+- switch n := path[i].(type) {
+- case *ast.SwitchStmt, *ast.TypeSwitchStmt:
+- switchNodeLabel = labelFor(path[i:])
+- if stmtLabel == nil || switchNodeLabel == stmtLabel {
+- switchNode = n
+- break Outer
+- }
+- }
+- }
+- // Cursor is not in a switch statement
+- if switchNode == nil {
+- return
+- }
+-
+- // Add the switch statement.
+- rngStart := switchNode.Pos()
+- rngEnd := switchNode.Pos() + token.Pos(len("switch"))
+- highlightRange(result, rngStart, rngEnd, protocol.Text)
+-
+- // Traverse AST to find break statements within the same switch.
+- ast.Inspect(switchNode, func(n ast.Node) bool {
+- switch n.(type) {
+- case *ast.SwitchStmt, *ast.TypeSwitchStmt:
+- return switchNode == n
+- case *ast.ForStmt, *ast.RangeStmt, *ast.SelectStmt:
+- return false
+- }
+-
+- b, ok := n.(*ast.BranchStmt)
+- if !ok || b.Tok != token.BREAK {
+- return true
+- }
+-
+- if b.Label == nil || info.Uses[b.Label] == info.Defs[switchNodeLabel] {
+- highlightNode(result, b, protocol.Text)
+- }
+- return true
+- })
+-
+- // We don't need to check other switches if we aren't looking for labeled statements.
+- if switchNodeLabel == nil {
+- return
+- }
+-
+- // Find labeled break statements in any switch
+- ast.Inspect(switchNode, func(n ast.Node) bool {
+- b, ok := n.(*ast.BranchStmt)
+- if !ok || b.Tok != token.BREAK {
+- return true
+- }
+-
+- if b.Label != nil && info.Uses[b.Label] == info.Defs[switchNodeLabel] {
+- highlightNode(result, b, protocol.Text)
+- }
+-
+- return true
+- })
+-}
+-
+-func highlightNode(result map[posRange]protocol.DocumentHighlightKind, n ast.Node, kind protocol.DocumentHighlightKind) {
+- highlightRange(result, n.Pos(), n.End(), kind)
+-}
+-
+-func highlightRange(result map[posRange]protocol.DocumentHighlightKind, pos, end token.Pos, kind protocol.DocumentHighlightKind) {
+- rng := posRange{pos, end}
+- // Order of traversal is important: some nodes (e.g. identifiers) are
+- // visited more than once, but the kind set during the first visitation "wins".
+- if _, exists := result[rng]; !exists {
+- result[rng] = kind
+- }
+-}
+-
+-func highlightIdentifier(id *ast.Ident, file *ast.File, info *types.Info, result map[posRange]protocol.DocumentHighlightKind) {
+-
+- // obj may be nil if the Ident is undefined.
+- // In this case, the behavior expected by tests is
+- // to match other undefined Idents of the same name.
+- obj := info.ObjectOf(id)
+-
+- highlightIdent := func(n *ast.Ident, kind protocol.DocumentHighlightKind) {
+- if n.Name == id.Name && info.ObjectOf(n) == obj {
+- highlightNode(result, n, kind)
+- }
+- }
+- // highlightWriteInExpr is called for expressions that are
+- // logically on the left side of an assignment.
+- // We follow the behavior of VSCode+Rust and GoLand, which differs
+- // slightly from types.TypeAndValue.Assignable:
+- // *ptr = 1 // ptr write
+- // *ptr.field = 1 // ptr read, field write
+- // s.field = 1 // s read, field write
+- // array[i] = 1 // array read
+- var highlightWriteInExpr func(expr ast.Expr)
+- highlightWriteInExpr = func(expr ast.Expr) {
+- switch expr := expr.(type) {
+- case *ast.Ident:
+- highlightIdent(expr, protocol.Write)
+- case *ast.SelectorExpr:
+- highlightIdent(expr.Sel, protocol.Write)
+- case *ast.StarExpr:
+- highlightWriteInExpr(expr.X)
+- case *ast.ParenExpr:
+- highlightWriteInExpr(expr.X)
+- }
+- }
+-
+- ast.Inspect(file, func(n ast.Node) bool {
+- switch n := n.(type) {
+- case *ast.AssignStmt:
+- for _, s := range n.Lhs {
+- highlightWriteInExpr(s)
+- }
+- case *ast.GenDecl:
+- if n.Tok == token.CONST || n.Tok == token.VAR {
+- for _, spec := range n.Specs {
+- if spec, ok := spec.(*ast.ValueSpec); ok {
+- for _, ele := range spec.Names {
+- highlightWriteInExpr(ele)
+- }
+- }
+- }
+- }
+- case *ast.IncDecStmt:
+- highlightWriteInExpr(n.X)
+- case *ast.SendStmt:
+- highlightWriteInExpr(n.Chan)
+- case *ast.CompositeLit:
+- t := info.TypeOf(n)
+- if t == nil {
+- t = types.Typ[types.Invalid]
+- }
+- if ptr, ok := t.Underlying().(*types.Pointer); ok {
+- t = ptr.Elem()
+- }
+- if _, ok := t.Underlying().(*types.Struct); ok {
+- for _, expr := range n.Elts {
+- if expr, ok := (expr).(*ast.KeyValueExpr); ok {
+- highlightWriteInExpr(expr.Key)
+- }
+- }
+- }
+- case *ast.RangeStmt:
+- highlightWriteInExpr(n.Key)
+- highlightWriteInExpr(n.Value)
+- case *ast.Field:
+- for _, name := range n.Names {
+- highlightIdent(name, protocol.Text)
+- }
+- case *ast.Ident:
+- // This case is reached for all Idents,
+- // including those also visited by highlightWriteInExpr.
+- if is[*types.Var](info.ObjectOf(n)) {
+- highlightIdent(n, protocol.Read)
+- } else {
+- // kind of idents in PkgName, etc. is Text
+- highlightIdent(n, protocol.Text)
+- }
+- case *ast.ImportSpec:
+- pkgname := info.PkgNameOf(n)
+- if pkgname == obj {
+- if n.Name != nil {
+- highlightNode(result, n.Name, protocol.Text)
+- } else {
+- highlightNode(result, n, protocol.Text)
+- }
+- }
+- }
+- return true
+- })
+-}
+diff -urN a/gopls/internal/golang/hover.go b/gopls/internal/golang/hover.go
+--- a/gopls/internal/golang/hover.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/hover.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,1814 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "bytes"
+- "context"
+- "encoding/json"
+- "fmt"
+- "go/ast"
+- "go/constant"
+- "go/doc"
+- "go/format"
+- "go/printer"
+- "go/token"
+- "go/types"
+- "go/version"
+- "io/fs"
+- "path/filepath"
+- "sort"
+- "strconv"
+- "strings"
+- "text/tabwriter"
+- "time"
+- "unicode/utf8"
+-
+- "golang.org/x/text/unicode/runenames"
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/settings"
+- "golang.org/x/tools/gopls/internal/util/bug"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/gopls/internal/util/tokeninternal"
+- gastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/stdlib"
+- "golang.org/x/tools/internal/typeparams"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-// hoverResult contains the (internal) result of a hover query.
+-// It is formatted in one of several formats as determined by the
+-// HoverKind setting.
+-type hoverResult struct {
+- // The fields below are exported to define the JSON hover format.
+- // TODO(golang/go#70233): (re)remove support for JSON hover.
+-
+- // Synopsis is a single sentence Synopsis of the symbol's documentation.
+- //
+- // TODO(adonovan): in what syntax? It (usually) comes from doc.Synopsis,
+- // which produces "Text" form, but it may be fed to
+- // DocCommentToMarkdown, which expects doc comment syntax.
+- Synopsis string `json:"synopsis"`
+-
+- // FullDocumentation is the symbol's full documentation.
+- FullDocumentation string `json:"fullDocumentation"`
+-
+- // Signature is the symbol's Signature.
+- Signature string `json:"signature"`
+-
+- // SingleLine is a single line describing the symbol.
+- // This is recommended only for use in clients that show a single line for hover.
+- SingleLine string `json:"singleLine"`
+-
+- // SymbolName is the human-readable name to use for the symbol in links.
+- SymbolName string `json:"symbolName"`
+-
+- // LinkPath is the path of the package enclosing the given symbol,
+- // with the module portion (if any) replaced by "module@version".
+- //
+- // For example: "github.com/google/go-github/v48@v48.1.0/github".
+- //
+- // Use LinkTarget + "/" + LinkPath + "#" + LinkAnchor to form a pkgsite URL.
+- LinkPath string `json:"linkPath"`
+-
+- // LinkAnchor is the pkg.go.dev link anchor for the given symbol.
+- // For example, the "Node" part of "pkg.go.dev/go/ast#Node".
+- LinkAnchor string `json:"linkAnchor"`
+-
+- // New fields go below, and are unexported. The existing
+- // exported fields are underspecified and have already
+- // constrained our movements too much. A detailed JSON
+- // interface might be nice, but it needs a design and a
+- // precise specification.
+- // TODO(golang/go#70233): (re)deprecate the JSON hover output.
+-
+- // typeDecl is the declaration syntax for a type,
+- // or "" for a non-type.
+- typeDecl string
+-
+- // methods is the list of descriptions of methods of a type,
+- // omitting any that are obvious from typeDecl.
+- // It is "" for a non-type.
+- methods string
+-
+- // promotedFields is the list of descriptions of accessible
+- // fields of a (struct) type that were promoted through an
+- // embedded field.
+- promotedFields string
+-
+- // footer is additional content to insert at the bottom of the hover
+- // documentation, before the pkgdoc link.
+- footer string
+-}
+-
+-// Hover implements the "textDocument/hover" RPC for Go files.
+-// It may return nil even on success.
+-//
+-// If pkgURL is non-nil, it should be used to generate doc links.
+-func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position, pkgURL func(path PackagePath, fragment string) protocol.URI) (*protocol.Hover, error) {
+- ctx, done := event.Start(ctx, "golang.Hover")
+- defer done()
+-
+- rng, h, err := hover(ctx, snapshot, fh, position)
+- if err != nil {
+- return nil, err
+- }
+- if h == nil {
+- return nil, nil
+- }
+- hover, err := formatHover(h, snapshot.Options(), pkgURL)
+- if err != nil {
+- return nil, err
+- }
+- return &protocol.Hover{
+- Contents: protocol.MarkupContent{
+- Kind: snapshot.Options().PreferredContentFormat,
+- Value: hover,
+- },
+- Range: rng,
+- }, nil
+-}
+-
+-// findRhsTypeDecl finds an alias's rhs type and returns its declaration.
+-// The rhs of an alias might be an alias as well, but we feel this is a rare case.
+-// It returns an empty string if the given obj is not an alias.
+-func findRhsTypeDecl(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, obj types.Object) (string, error) {
+- if alias, ok := obj.Type().(*types.Alias); ok {
+- // we choose Rhs instead of types.Unalias to make the connection between original alias
+- // and the corresponding aliased type clearer.
+- // types.Unalias brings confusion because it breaks the connection from A to C given
+- // the alias chain like 'type ( A = B; B = C; )' except we show all transitive alias
+- // from start to the end. As it's rare, we don't do so.
+- if named, ok := alias.Rhs().(*types.Named); ok {
+- obj = named.Obj()
+- declPGF1, declPos1, err := parseFull(ctx, snapshot, pkg.FileSet(), obj)
+- if err != nil {
+- return "", err
+- }
+- realTypeDecl, _, err := typeDeclContent(declPGF1, declPos1, obj.Name())
+- return realTypeDecl, err
+- }
+- }
+-
+- return "", nil
+-}
+-
+-// hover computes hover information at the given position. If we do not support
+-// hovering at the position, it returns _, nil, nil: an error is only returned
+-// if the position is valid but we fail to compute hover information.
+-//
+-// TODO(adonovan): strength-reduce file.Handle to protocol.DocumentURI.
+-func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) (protocol.Range, *hoverResult, error) {
+- // Check for hover inside the builtin file before attempting type checking
+- // below. NarrowestPackageForFile may or may not succeed, depending on
+- // whether this is a GOROOT view, but even if it does succeed the resulting
+- // package will be command-line-arguments package. The user should get a
+- // hover for the builtin object, not the object type checked from the
+- // builtin.go.
+- if snapshot.IsBuiltin(fh.URI()) {
+- pgf, err := snapshot.BuiltinFile(ctx)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
+- if id, ok := path[0].(*ast.Ident); ok {
+- rng, err := pgf.NodeRange(id)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- var obj types.Object
+- if id.Name == "Error" {
+- obj = types.Universe.Lookup("error").Type().Underlying().(*types.Interface).Method(0)
+- } else {
+- obj = types.Universe.Lookup(id.Name)
+- }
+- if obj != nil {
+- h, err := hoverBuiltin(ctx, snapshot, obj)
+- return rng, h, err
+- }
+- }
+- return protocol.Range{}, nil, nil // no object to hover
+- }
+-
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+-
+- // Handle hovering over the package name, which does not have an associated
+- // object.
+- // As with import paths, we allow hovering just after the package name.
+- if pgf.File.Name != nil && gastutil.NodeContains(pgf.File.Name, pos) {
+- return hoverPackageName(pkg, pgf)
+- }
+-
+- // Handle hovering over embed directive argument.
+- pattern, embedRng := parseEmbedDirective(pgf.Mapper, pp)
+- if pattern != "" {
+- return hoverEmbed(fh, embedRng, pattern)
+- }
+-
+- // hoverRange is the range reported to the client (e.g. for highlighting).
+- // It may be an expansion around the selected identifier,
+- // for instance when hovering over a linkname directive or doc link.
+- var hoverRange *protocol.Range
+- // Handle linkname directive by overriding what to look for.
+- if pkgPath, name, offset := parseLinkname(pgf.Mapper, pp); pkgPath != "" && name != "" {
+- // rng covering 2nd linkname argument: pkgPath.name.
+- rng, err := pgf.PosRange(pgf.Tok.Pos(offset), pgf.Tok.Pos(offset+len(pkgPath)+len(".")+len(name)))
+- if err != nil {
+- return protocol.Range{}, nil, fmt.Errorf("range over linkname arg: %w", err)
+- }
+- hoverRange = &rng
+-
+- pkg, pgf, pos, err = findLinkname(ctx, snapshot, PackagePath(pkgPath), name)
+- if err != nil {
+- return protocol.Range{}, nil, fmt.Errorf("find linkname: %w", err)
+- }
+- }
+-
+- // Handle hovering over a doc link
+- if obj, rng, _ := resolveDocLink(pkg, pgf, pos); obj != nil {
+- // Built-ins have no position.
+- if isBuiltin(obj) {
+- h, err := hoverBuiltin(ctx, snapshot, obj)
+- return rng, h, err
+- }
+-
+- // Find position in declaring file.
+- hoverRange = &rng
+- objURI := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
+- pkg, pgf, err = NarrowestPackageForFile(ctx, snapshot, protocol.URIFromPath(objURI.Filename))
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- pos = pgf.Tok.Pos(objURI.Offset)
+- }
+-
+- // Handle hovering over import paths, which do not have an associated
+- // identifier.
+- for _, spec := range pgf.File.Imports {
+- if gastutil.NodeContains(spec, pos) {
+- path := metadata.UnquoteImportPath(spec)
+- hoverRes, err := hoverPackageRef(ctx, snapshot, pkg, path)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- rng, err := pgf.NodeRange(spec.Path)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- if hoverRange == nil {
+- hoverRange = &rng
+- }
+- return *hoverRange, hoverRes, nil // (hoverRes may be nil)
+- }
+- }
+-
+- // Handle hovering over various special kinds of syntax node.
+- if path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos); len(path) > 0 {
+- switch node := path[0].(type) {
+- // Handle hovering over (non-import-path) literals.
+- case *ast.BasicLit:
+- return hoverLit(pgf, node, pos)
+- case *ast.ReturnStmt:
+- return hoverReturnStatement(pgf, path, node)
+- }
+- }
+-
+- // By convention, we qualify hover information relative to the package
+- // from which the request originated.
+- qual := typesinternal.FileQualifier(pgf.File, pkg.Types())
+-
+- // Handle hover over identifier.
+-
+- // The general case: compute hover information for the object referenced by
+- // the identifier at pos.
+- ident, obj, selectedType := referencedObject(pkg, pgf, pos)
+- if obj == nil || ident == nil {
+- return protocol.Range{}, nil, nil // no object to hover
+- }
+-
+- // Unless otherwise specified, rng covers the ident being hovered.
+- if hoverRange == nil {
+- rng, err := pgf.NodeRange(ident)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- hoverRange = &rng
+- }
+-
+- // Handle type switch identifiers as a special case, since they don't have an
+- // object.
+- //
+- // There's not much useful information to provide.
+- if selectedType != nil {
+- v := types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), selectedType)
+- typesinternal.SetVarKind(v, typesinternal.LocalVar)
+- signature := types.ObjectString(v, qual)
+- return *hoverRange, &hoverResult{
+- Signature: signature,
+- SingleLine: signature,
+- SymbolName: v.Name(),
+- }, nil
+- }
+-
+- if isBuiltin(obj) {
+- // Built-ins have no position.
+- h, err := hoverBuiltin(ctx, snapshot, obj)
+- return *hoverRange, h, err
+- }
+-
+- // For all other objects, consider the full syntax of their declaration in
+- // order to correctly compute their documentation, signature, and link.
+- //
+- // Beware: decl{PGF,Pos} are not necessarily associated with pkg.FileSet().
+- declPGF, declPos, err := parseFull(ctx, snapshot, pkg.FileSet(), obj)
+- if err != nil {
+- return protocol.Range{}, nil, fmt.Errorf("re-parsing declaration of %s: %v", obj.Name(), err)
+- }
+- decl, spec, field := findDeclInfo([]*ast.File{declPGF.File}, declPos) // may be nil^3
+- comment := chooseDocComment(decl, spec, field)
+- docText := comment.Text()
+-
+- // By default, types.ObjectString provides a reasonable signature.
+- signature := objectString(obj, qual, declPos, declPGF.Tok, spec)
+-
+- // When hovering over a reference to a promoted struct field,
+- // show the implicitly selected intervening fields.
+- cur, ok := pgf.Cursor.FindByPos(pos, pos)
+- if !ok {
+- return protocol.Range{}, nil, fmt.Errorf("Invalid hover position, failed to get cursor")
+- }
+- if obj, ok := obj.(*types.Var); ok && obj.IsField() {
+- if selExpr, ok := cur.Parent().Node().(*ast.SelectorExpr); ok {
+- sel, ok := pkg.TypesInfo().Selections[selExpr]
+- if ok && len(sel.Index()) > 1 {
+- var buf bytes.Buffer
+- buf.WriteString(" // through ")
+- t := typesinternal.Unpointer(sel.Recv())
+- for i, index := range sel.Index()[:len(sel.Index())-1] {
+- if i > 0 {
+- buf.WriteString(", ")
+- }
+- field := typesinternal.Unpointer(t.Underlying()).(*types.Struct).Field(index)
+- t = field.Type()
+- // Inv: fieldType is N or *N for some NamedOrAlias type N.
+- if ptr, ok := t.(*types.Pointer); ok {
+- buf.WriteString("*")
+- t = ptr.Elem()
+- }
+- // Be defensive in case of ill-typed code:
+- if named, ok := t.(typesinternal.NamedOrAlias); ok {
+- buf.WriteString(named.Obj().Name())
+- }
+- }
+- // Update signature to include embedded struct info.
+- signature += buf.String()
+- }
+- }
+- }
+-
+- singleLineSignature := signature
+-
+- // Display struct tag for struct fields at the end of the signature.
+- if field != nil && field.Tag != nil {
+- signature += " " + field.Tag.Value
+- }
+-
+- // TODO(rfindley): we could do much better for inferred signatures.
+- // TODO(adonovan): fuse the two calls below.
+- if inferred := inferredSignature(pkg.TypesInfo(), ident); inferred != nil {
+- if s := inferredSignatureString(obj, qual, inferred); s != "" {
+- signature = s
+- }
+- }
+-
+- // Compute size information for types,
+- // including allocator size class,
+- // and (size, offset) for struct fields.
+- //
+- // Also, if a struct type's field ordering is significantly
+- // wasteful of space, report its optimal size.
+- //
+- // This information is useful when debugging crashes or
+- // optimizing layout. To reduce distraction, we show it only
+- // when hovering over the declaring identifier,
+- // but not referring identifiers.
+- //
+- // Size and alignment vary across OS/ARCH.
+- // Gopls will select the appropriate build configuration when
+- // viewing a type declaration in a build-tagged file, but will
+- // use the default build config for all other types, even
+- // if they embed platform-variant types.
+- //
+- var sizeOffset string
+-
+- // As painfully learned in golang/go#69362, Defs can contain nil entries.
+- if def, _ := pkg.TypesInfo().Defs[ident]; def != nil && ident.Pos() == def.Pos() {
+- // This is the declaring identifier.
+- // (We can't simply use ident.Pos() == obj.Pos() because
+- // referencedObject prefers the TypeName for an embedded field).
+-
+- // format returns the decimal and hex representation of x.
+- format := func(x int64) string {
+- if x < 10 {
+- return fmt.Sprintf("%d", x)
+- }
+- return fmt.Sprintf("%[1]d (%#[1]x)", x)
+- }
+-
+- path := pathEnclosingObjNode(pgf.File, pos)
+-
+- // Build string of form "size=... (X% wasted), class=..., offset=...".
+- size, wasted, offset := computeSizeOffsetInfo(pkg, path, obj)
+- var buf strings.Builder
+- if size >= 0 {
+- fmt.Fprintf(&buf, "size=%s", format(size))
+- if wasted >= 20 { // >=20% wasted
+- fmt.Fprintf(&buf, " (%d%% wasted)", wasted)
+- }
+-
+- // Include allocator size class, if larger.
+- if class := sizeClass(size); class > size {
+- fmt.Fprintf(&buf, ", class=%s", format(class))
+- }
+- }
+- if offset >= 0 {
+- if buf.Len() > 0 {
+- buf.WriteString(", ")
+- }
+- fmt.Fprintf(&buf, "offset=%s", format(offset))
+- }
+- sizeOffset = buf.String()
+- }
+-
+- var typeDecl, methods, fields string
+-
+- // For "objects defined by a type spec", the signature produced by
+- // objectString is insufficient:
+- // (1) large structs are formatted poorly, with no newlines
+- // (2) we lose inline comments
+- // Furthermore, we include a summary of their method set.
+- _, isTypeName := obj.(*types.TypeName)
+- _, isTypeParam := types.Unalias(obj.Type()).(*types.TypeParam)
+- if isTypeName && !isTypeParam {
+- var spec1 *ast.TypeSpec
+- typeDecl, spec1, err = typeDeclContent(declPGF, declPos, obj.Name())
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+-
+- // Splice in size/offset at end of first line.
+- // "type T struct { // size=..."
+- if sizeOffset != "" {
+- nl := strings.IndexByte(typeDecl, '\n')
+- if nl < 0 {
+- nl = len(typeDecl)
+- }
+- typeDecl = typeDecl[:nl] + " // " + sizeOffset + typeDecl[nl:]
+- }
+-
+- // Promoted fields
+- //
+- // Show a table of accessible fields of the (struct)
+- // type that may not be visible in the syntax (above)
+- // due to promotion through embedded fields.
+- //
+- // Example:
+- //
+- // // Embedded fields:
+- // foo int // through x.y
+- // z string // through x.y
+- if prom := promotedFields(obj.Type(), pkg.Types()); len(prom) > 0 {
+- var b strings.Builder
+- b.WriteString("// Embedded fields:\n")
+- w := tabwriter.NewWriter(&b, 0, 8, 1, ' ', 0)
+- for _, f := range prom {
+- fmt.Fprintf(w, "%s\t%s\t// through %s\t\n",
+- f.field.Name(),
+- types.TypeString(f.field.Type(), qual),
+- f.path)
+- }
+- w.Flush() // ignore error
+- b.WriteByte('\n')
+- fields = b.String()
+- }
+-
+- // -- methods --
+-
+- // For an interface type, explicit methods will have
+- // already been displayed when the node was formatted
+- // above. Don't list these again.
+- var skip map[string]bool
+- if iface, ok := spec1.Type.(*ast.InterfaceType); ok {
+- if iface.Methods.List != nil {
+- for _, m := range iface.Methods.List {
+- if len(m.Names) == 1 {
+- if skip == nil {
+- skip = make(map[string]bool)
+- }
+- skip[m.Names[0].Name] = true
+- }
+- }
+- }
+- }
+-
+- // Display all the type's accessible methods,
+- // including those that require a pointer receiver,
+- // and those promoted from embedded struct fields or
+- // embedded interfaces.
+- var b strings.Builder
+- for _, m := range typeutil.IntuitiveMethodSet(obj.Type(), nil) {
+- if !accessibleTo(m.Obj(), pkg.Types()) {
+- continue // inaccessible
+- }
+- if skip[m.Obj().Name()] {
+- continue // redundant with format.Node above
+- }
+- if b.Len() > 0 {
+- b.WriteByte('\n')
+- }
+-
+- // Use objectString for its prettier rendering of method receivers.
+- b.WriteString(objectString(m.Obj(), qual, token.NoPos, nil, nil))
+- }
+- methods = b.String()
+-
+- signature = typeDecl + "\n" + methods
+- } else {
+- // Non-types
+- if sizeOffset != "" {
+- signature += " // " + sizeOffset
+- }
+- }
+-
+- if isTypeName {
+- // get the real type decl only if current object is a type,
+- // for non-types, we'd better hide the real type decl to avoid possible confusion.
+- //
+- // realTypeDecl is defined to store the underlying definition of an alias.
+- realTypeDecl, _ := findRhsTypeDecl(ctx, snapshot, pkg, obj) // tolerate the error
+- if realTypeDecl != "" {
+- typeDecl += fmt.Sprintf("\n\n%s", realTypeDecl)
+- }
+- }
+-
+- // Compute link data (on pkg.go.dev or other documentation host).
+- //
+- // If linkPath is empty, the symbol is not linkable.
+- var (
+- linkName string // => link title, always non-empty
+- linkPath string // => link path
+- anchor string // link anchor
+- linkMeta *metadata.Package // metadata for the linked package
+- )
+- {
+- linkMeta = findFileInDeps(snapshot, pkg.Metadata(), declPGF.URI)
+- if linkMeta == nil {
+- return protocol.Range{}, nil, bug.Errorf("no package data for %s", declPGF.URI)
+- }
+-
+- // For package names, we simply link to their imported package.
+- if pkgName, ok := obj.(*types.PkgName); ok {
+- linkName = pkgName.Name()
+- linkPath = pkgName.Imported().Path()
+- impID := linkMeta.DepsByPkgPath[PackagePath(pkgName.Imported().Path())]
+- linkMeta = snapshot.Metadata(impID)
+- if linkMeta == nil {
+- // Broken imports have fake package paths, so it is not a bug if we
+- // don't have metadata. As of writing, there is no way to distinguish
+- // broken imports from a true bug where expected metadata is missing.
+- return protocol.Range{}, nil, fmt.Errorf("no package data for %s", declPGF.URI)
+- }
+- } else {
+- // For all others, check whether the object is in the package scope, or
+- // an exported field or method of an object in the package scope.
+- //
+- // We try to match pkgsite's heuristics for what is linkable, and what is
+- // not.
+- var recv types.Object
+- switch obj := obj.(type) {
+- case *types.Func:
+- sig := obj.Signature()
+- if sig.Recv() != nil {
+- tname := typeToObject(sig.Recv().Type())
+- if tname != nil { // beware typed nil
+- recv = tname
+- }
+- }
+- case *types.Var:
+- if obj.IsField() {
+- if spec, ok := spec.(*ast.TypeSpec); ok {
+- typeName := spec.Name
+- scopeObj, _ := obj.Pkg().Scope().Lookup(typeName.Name).(*types.TypeName)
+- if scopeObj != nil {
+- if st, _ := scopeObj.Type().Underlying().(*types.Struct); st != nil {
+- for i := 0; i < st.NumFields(); i++ {
+- if obj == st.Field(i) {
+- recv = scopeObj
+- }
+- }
+- }
+- }
+- }
+- }
+- }
+-
+- // Even if the object is not available in package documentation, it may
+- // be embedded in a documented receiver. Detect this by searching
+- // enclosing selector expressions.
+- //
+- // TODO(rfindley): pkgsite doesn't document fields from embedding, just
+- // methods.
+- if recv == nil || !recv.Exported() {
+- path := pathEnclosingObjNode(pgf.File, pos)
+- if enclosing := searchForEnclosing(pkg.TypesInfo(), path); enclosing != nil {
+- recv = enclosing
+- } else {
+- recv = nil // note: just recv = ... could result in a typed nil.
+- }
+- }
+-
+- pkg := obj.Pkg()
+- if recv != nil {
+- linkName = fmt.Sprintf("(%s.%s).%s", pkg.Name(), recv.Name(), obj.Name())
+- if obj.Exported() && recv.Exported() && typesinternal.IsPackageLevel(recv) {
+- linkPath = pkg.Path()
+- anchor = fmt.Sprintf("%s.%s", recv.Name(), obj.Name())
+- }
+- } else {
+- linkName = fmt.Sprintf("%s.%s", pkg.Name(), obj.Name())
+- if obj.Exported() && typesinternal.IsPackageLevel(obj) {
+- linkPath = pkg.Path()
+- anchor = obj.Name()
+- }
+- }
+- }
+- }
+-
+- if snapshot.IsGoPrivatePath(linkPath) || linkMeta.ForTest != "" {
+- linkPath = ""
+- } else if linkMeta.Module != nil && linkMeta.Module.Version != "" {
+- mod := linkMeta.Module
+- linkPath = strings.Replace(linkPath, mod.Path, cache.ResolvedString(mod), 1)
+- }
+-
+- // Handle hover over an imported package name identifier.
+- if pkgName, ok := obj.(*types.PkgName); ok {
+- hoverRes, err := hoverPackageRef(ctx, snapshot, pkg, metadata.ImportPath(pkgName.Imported().Path()))
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- hoverRange, err := pgf.NodeRange(ident)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- hoverRes.LinkAnchor = anchor
+- hoverRes.LinkPath = linkPath
+- hoverRes.SymbolName = linkName
+- return hoverRange, hoverRes, nil // (hoverRes may be nil)
+- }
+-
+- var footer string
+- if sym := StdSymbolOf(obj); sym != nil && sym.Version > 0 {
+- footer = fmt.Sprintf("Added in %v", sym.Version)
+- }
+-
+- return *hoverRange, &hoverResult{
+- Synopsis: doc.Synopsis(docText),
+- FullDocumentation: docText,
+- SingleLine: singleLineSignature,
+- SymbolName: linkName,
+- Signature: signature,
+- LinkPath: linkPath,
+- LinkAnchor: anchor,
+- typeDecl: typeDecl,
+- methods: methods,
+- promotedFields: fields,
+- footer: footer,
+- }, nil
+-}
+-
+-// typeDeclContent returns a well formatted type definition.
+-func typeDeclContent(declPGF *parsego.File, declPos token.Pos, name string) (string, *ast.TypeSpec, error) {
+- _, spec, _ := findDeclInfo([]*ast.File{declPGF.File}, declPos) // may be nil^3
+- // Don't duplicate comments.
+- spec1, ok := spec.(*ast.TypeSpec)
+- if !ok {
+- // We cannot find a TypeSpec for this type or alias declaration
+- // (that is not a type parameter or a built-in).
+- // This should be impossible even for ill-formed trees;
+- // we suspect that AST repair may be creating inconsistent
+- // positions. Don't report a bug in that case. (#64241)
+- errorf := fmt.Errorf
+- if !declPGF.Fixed() {
+- errorf = bug.Errorf
+- }
+- return "", nil, errorf("type name %q without type spec", name)
+- }
+- spec2 := *spec1
+- spec2.Doc = nil
+- spec2.Comment = nil
+-
+- var b strings.Builder
+- b.WriteString("type ")
+- fset := tokeninternal.FileSetFor(declPGF.Tok)
+- // TODO(adonovan): use a smarter formatter that omits
+- // inaccessible fields (non-exported ones from other packages).
+- if err := format.Node(&b, fset, &spec2); err != nil {
+- return "", nil, err
+- }
+- typeDecl := b.String()
+- return typeDecl, spec1, nil
+-}
+-
+-// hoverBuiltin computes hover information when hovering over a builtin
+-// identifier.
+-func hoverBuiltin(ctx context.Context, snapshot *cache.Snapshot, obj types.Object) (*hoverResult, error) {
+- // Special handling for error.Error, which is the only builtin method.
+- //
+- // TODO(rfindley): can this be unified with the handling below?
+- if obj.Name() == "Error" {
+- signature := obj.String()
+- return &hoverResult{
+- Signature: signature,
+- SingleLine: signature,
+- // TODO(rfindley): these are better than the current behavior.
+- // SymbolName: "(error).Error",
+- // LinkPath: "builtin",
+- // LinkAnchor: "error.Error",
+- }, nil
+- }
+-
+- pgf, ident, err := builtinDecl(ctx, snapshot, obj)
+- if err != nil {
+- return nil, err
+- }
+-
+- var (
+- comment *ast.CommentGroup
+- decl ast.Decl
+- )
+- path, _ := astutil.PathEnclosingInterval(pgf.File, ident.Pos(), ident.Pos())
+- for _, n := range path {
+- switch n := n.(type) {
+- case *ast.GenDecl:
+- // Separate documentation and signature.
+- comment = n.Doc
+- node2 := *n
+- node2.Doc = nil
+- decl = &node2
+- case *ast.FuncDecl:
+- // Ditto.
+- comment = n.Doc
+- node2 := *n
+- node2.Doc = nil
+- decl = &node2
+- }
+- }
+-
+- signature := formatNodeFile(pgf.Tok, decl)
+- // Replace fake types with their common equivalent.
+- // TODO(rfindley): we should instead use obj.Type(), which would have the
+- // *actual* types of the builtin call.
+- signature = replacer.Replace(signature)
+-
+- docText := comment.Text()
+- return &hoverResult{
+- Synopsis: doc.Synopsis(docText),
+- FullDocumentation: docText,
+- Signature: signature,
+- SingleLine: obj.String(),
+- SymbolName: obj.Name(),
+- LinkPath: "builtin",
+- LinkAnchor: obj.Name(),
+- }, nil
+-}
+-
+-// hoverPackageRef computes hover information for the package of the specified
+-// path imported by pkg. If we do not have metadata for the hovered import, it
+-// returns _
+-func hoverPackageRef(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, importPath metadata.ImportPath) (*hoverResult, error) {
+- impID := pkg.Metadata().DepsByImpPath[importPath]
+- if impID == "" {
+- return nil, fmt.Errorf("no package data for import %q", importPath)
+- }
+- impMetadata := snapshot.Metadata(impID)
+- if impMetadata == nil {
+- return nil, bug.Errorf("failed to resolve import ID %q", impID)
+- }
+-
+- // Find the first file with a package doc comment.
+- var comment *ast.CommentGroup
+- for _, f := range impMetadata.CompiledGoFiles {
+- fh, err := snapshot.ReadFile(ctx, f)
+- if err != nil {
+- if ctx.Err() != nil {
+- return nil, ctx.Err()
+- }
+- continue
+- }
+- pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header)
+- if err != nil {
+- if ctx.Err() != nil {
+- return nil, ctx.Err()
+- }
+- continue
+- }
+- if pgf.File.Doc != nil {
+- comment = pgf.File.Doc
+- break
+- }
+- }
+-
+- docText := comment.Text()
+- return &hoverResult{
+- Signature: "package " + string(impMetadata.Name),
+- Synopsis: doc.Synopsis(docText),
+- FullDocumentation: docText,
+- }, nil
+-}
+-
+-// hoverPackageName computes hover information for the package name of the file
+-// pgf in pkg.
+-func hoverPackageName(pkg *cache.Package, pgf *parsego.File) (protocol.Range, *hoverResult, error) {
+- var comment *ast.CommentGroup
+- for _, pgf := range pkg.CompiledGoFiles() {
+- if pgf.File.Doc != nil {
+- comment = pgf.File.Doc
+- break
+- }
+- }
+- rng, err := pgf.NodeRange(pgf.File.Name)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- docText := comment.Text()
+-
+- // List some package attributes at the bottom of the documentation, if
+- // applicable.
+- type attr struct{ title, value string }
+- var attrs []attr
+-
+- if !metadata.IsCommandLineArguments(pkg.Metadata().ID) {
+- attrs = append(attrs, attr{"Package path", string(pkg.Metadata().PkgPath)})
+- }
+-
+- if pkg.Metadata().Module != nil {
+- attrs = append(attrs, attr{"Module", pkg.Metadata().Module.Path})
+- }
+-
+- // Show the effective language version for this package.
+- if v := pkg.TypesInfo().FileVersions[pgf.File]; v != "" {
+- attr := attr{value: version.Lang(v)}
+- if v == pkg.Types().GoVersion() {
+- attr.title = "Language version"
+- } else {
+- attr.title = "Language version (current file)"
+- }
+- attrs = append(attrs, attr)
+- }
+-
+- // TODO(rfindley): consider exec'ing go here to compute DefaultGODEBUG, or
+- // propose adding GODEBUG info to go/packages.
+-
+- var footer strings.Builder
+- for i, attr := range attrs {
+- if i > 0 {
+- footer.WriteString("\n")
+- }
+- fmt.Fprintf(&footer, " - %s: %s", attr.title, attr.value)
+- }
+-
+- return rng, &hoverResult{
+- Signature: "package " + string(pkg.Metadata().Name),
+- Synopsis: doc.Synopsis(docText),
+- FullDocumentation: docText,
+- footer: footer.String(),
+- }, nil
+-}
+-
+-// hoverLit computes hover information when hovering over the basic literal lit
+-// in the file pgf. The provided pos must be the exact position of the cursor,
+-// as it is used to extract the hovered rune in strings.
+-//
+-// For example, hovering over "\u2211" in "foo \u2211 bar" yields:
+-//
+-// '∑', U+2211, N-ARY SUMMATION
+-func hoverLit(pgf *parsego.File, lit *ast.BasicLit, pos token.Pos) (protocol.Range, *hoverResult, error) {
+- var (
+- value string // if non-empty, a constant value to format in hover
+- r rune // if non-zero, format a description of this rune in hover
+- start, end token.Pos // hover span
+- )
+- // Extract a rune from the current position.
+- // 'Ω', "...Ω...", or 0x03A9 => 'Ω', U+03A9, GREEK CAPITAL LETTER OMEGA
+- switch lit.Kind {
+- case token.CHAR:
+- s, err := strconv.Unquote(lit.Value)
+- if err != nil {
+- // If the conversion fails, it's because of an invalid syntax, therefore
+- // there is no rune to be found.
+- return protocol.Range{}, nil, nil
+- }
+- r, _ = utf8.DecodeRuneInString(s)
+- if r == utf8.RuneError {
+- return protocol.Range{}, nil, fmt.Errorf("rune error")
+- }
+- start, end = lit.Pos(), lit.End()
+-
+- case token.INT:
+- // Short literals (e.g. 99 decimal, 07 octal) are uninteresting.
+- if len(lit.Value) < 3 {
+- return protocol.Range{}, nil, nil
+- }
+-
+- v := constant.MakeFromLiteral(lit.Value, lit.Kind, 0)
+- if v.Kind() != constant.Int {
+- return protocol.Range{}, nil, nil
+- }
+-
+- switch lit.Value[:2] {
+- case "0x", "0X":
+- // As a special case, try to recognize hexadecimal literals as runes if
+- // they are within the range of valid unicode values.
+- if v, ok := constant.Int64Val(v); ok && v > 0 && v <= utf8.MaxRune && utf8.ValidRune(rune(v)) {
+- r = rune(v)
+- }
+- fallthrough
+- case "0o", "0O", "0b", "0B":
+- // Format the decimal value of non-decimal literals.
+- value = v.ExactString()
+- start, end = lit.Pos(), lit.End()
+- default:
+- return protocol.Range{}, nil, nil
+- }
+-
+- case token.STRING:
+- // It's a string, scan only if it contains a unicode escape sequence under or before the
+- // current cursor position.
+- litOffset, err := safetoken.Offset(pgf.Tok, lit.Pos())
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- offset, err := safetoken.Offset(pgf.Tok, pos)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- for i := offset - litOffset; i > 0; i-- {
+- // Start at the cursor position and search backward for the beginning of a rune escape sequence.
+- rr, _ := utf8.DecodeRuneInString(lit.Value[i:])
+- if rr == utf8.RuneError {
+- return protocol.Range{}, nil, fmt.Errorf("rune error")
+- }
+- if rr == '\\' {
+- // Got the beginning, decode it.
+- var tail string
+- r, _, tail, err = strconv.UnquoteChar(lit.Value[i:], '"')
+- if err != nil {
+- // If the conversion fails, it's because of an invalid syntax,
+- // therefore is no rune to be found.
+- return protocol.Range{}, nil, nil
+- }
+- // Only the rune escape sequence part of the string has to be highlighted, recompute the range.
+- runeLen := len(lit.Value) - (i + len(tail))
+- start = token.Pos(int(lit.Pos()) + i)
+- end = token.Pos(int(start) + runeLen)
+- break
+- }
+- }
+- }
+-
+- if value == "" && r == 0 { // nothing to format
+- return protocol.Range{}, nil, nil
+- }
+-
+- rng, err := pgf.PosRange(start, end)
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+-
+- var b strings.Builder
+- if value != "" {
+- b.WriteString(value)
+- }
+- if r != 0 {
+- runeName := runenames.Name(r)
+- if len(runeName) > 0 && runeName[0] == '<' {
+- // Check if the rune looks like an HTML tag. If so, trim the surrounding <>
+- // characters to work around https://github.com/microsoft/vscode/issues/124042.
+- runeName = strings.TrimRight(runeName[1:], ">")
+- }
+- if b.Len() > 0 {
+- b.WriteString(", ")
+- }
+- if strconv.IsPrint(r) {
+- fmt.Fprintf(&b, "'%c', ", r)
+- }
+- fmt.Fprintf(&b, "U+%04X, %s", r, runeName)
+- }
+- hover := b.String()
+- return rng, &hoverResult{
+- Synopsis: hover,
+- FullDocumentation: hover,
+- }, nil
+-}
+-
+-func hoverReturnStatement(pgf *parsego.File, path []ast.Node, ret *ast.ReturnStmt) (protocol.Range, *hoverResult, error) {
+- var funcType *ast.FuncType
+- // Find innermost enclosing function.
+- for _, n := range path {
+- switch n := n.(type) {
+- case *ast.FuncLit:
+- funcType = n.Type
+- case *ast.FuncDecl:
+- funcType = n.Type
+- }
+- if funcType != nil {
+- break
+- }
+- }
+- // Inv: funcType != nil because a ReturnStmt is always enclosed by a function.
+- if funcType.Results == nil {
+- return protocol.Range{}, nil, nil // no result variables
+- }
+- rng, err := pgf.PosRange(ret.Pos(), ret.End())
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+- // Format the function's result type.
+- var buf strings.Builder
+- var cfg printer.Config
+- fset := token.NewFileSet()
+- buf.WriteString("returns (")
+- for i, field := range funcType.Results.List {
+- if i > 0 {
+- buf.WriteString(", ")
+- }
+- cfg.Fprint(&buf, fset, field.Type) // ignore error
+- }
+- buf.WriteByte(')')
+- return rng, &hoverResult{
+- Signature: buf.String(),
+- }, nil
+-}
+-
+-// hoverEmbed computes hover information for a filepath.Match pattern.
+-// Assumes that the pattern is relative to the location of fh.
+-func hoverEmbed(fh file.Handle, rng protocol.Range, pattern string) (protocol.Range, *hoverResult, error) {
+- s := &strings.Builder{}
+-
+- dir := fh.URI().DirPath()
+- var matches []string
+- err := filepath.WalkDir(dir, func(abs string, d fs.DirEntry, e error) error {
+- if e != nil {
+- return e
+- }
+- rel, err := filepath.Rel(dir, abs)
+- if err != nil {
+- return err
+- }
+- ok, err := filepath.Match(pattern, rel)
+- if err != nil {
+- return err
+- }
+- if ok && !d.IsDir() {
+- matches = append(matches, rel)
+- }
+- return nil
+- })
+- if err != nil {
+- return protocol.Range{}, nil, err
+- }
+-
+- for _, m := range matches {
+- // TODO: Renders each file as separate markdown paragraphs.
+- // If forcing (a single) newline is possible it might be more clear.
+- fmt.Fprintf(s, "%s\n\n", m)
+- }
+-
+- res := &hoverResult{
+- Signature: fmt.Sprintf("Embedding %q", pattern),
+- Synopsis: s.String(),
+- FullDocumentation: s.String(),
+- }
+- return rng, res, nil
+-}
+-
+-// inferredSignatureString is a wrapper around the types.ObjectString function
+-// that adds more information to inferred signatures. It will return an empty string
+-// if the passed types.Object is not a signature.
+-func inferredSignatureString(obj types.Object, qual types.Qualifier, inferred *types.Signature) string {
+- // If the signature type was inferred, prefer the inferred signature with a
+- // comment showing the generic signature.
+- if sig, _ := obj.Type().Underlying().(*types.Signature); sig != nil && sig.TypeParams().Len() > 0 && inferred != nil {
+- obj2 := types.NewFunc(obj.Pos(), obj.Pkg(), obj.Name(), inferred)
+- str := types.ObjectString(obj2, qual)
+- // Try to avoid overly long lines.
+- if len(str) > 60 {
+- str += "\n"
+- } else {
+- str += " "
+- }
+- str += "// " + types.TypeString(sig, qual)
+- return str
+- }
+- return ""
+-}
+-
+-// objectString is a wrapper around the types.ObjectString function.
+-// It handles adding more information to the object string.
+-// If spec is non-nil, it may be used to format additional declaration
+-// syntax, and file must be the token.File describing its positions.
+-//
+-// Precondition: obj is not a built-in function or method.
+-func objectString(obj types.Object, qual types.Qualifier, declPos token.Pos, file *token.File, spec ast.Spec) string {
+- str := types.ObjectString(obj, qual)
+-
+- switch obj := obj.(type) {
+- case *types.Func:
+- // We fork ObjectString to improve its rendering of methods:
+- // specifically, we show the receiver name,
+- // and replace the period in (T).f by a space (#62190).
+-
+- sig := obj.Signature()
+-
+- var buf bytes.Buffer
+- buf.WriteString("func ")
+- if recv := sig.Recv(); recv != nil {
+- buf.WriteByte('(')
+- if _, ok := recv.Type().(*types.Interface); ok {
+- // gcimporter creates abstract methods of
+- // named interfaces using the interface type
+- // (not the named type) as the receiver.
+- // Don't print it in full.
+- buf.WriteString("interface")
+- } else {
+- // Show receiver name (go/types does not).
+- name := recv.Name()
+- if name != "" && name != "_" {
+- buf.WriteString(name)
+- buf.WriteString(" ")
+- }
+- types.WriteType(&buf, recv.Type(), qual)
+- }
+- buf.WriteByte(')')
+- buf.WriteByte(' ') // space (go/types uses a period)
+- } else if s := qual(obj.Pkg()); s != "" {
+- buf.WriteString(s)
+- buf.WriteString(".")
+- }
+- buf.WriteString(obj.Name())
+- types.WriteSignature(&buf, sig, qual)
+- str = buf.String()
+-
+- case *types.Const:
+- // Show value of a constant.
+- var (
+- declaration = obj.Val().String() // default formatted declaration
+- comment = "" // if non-empty, a clarifying comment
+- )
+-
+- // Try to use the original declaration.
+- switch obj.Val().Kind() {
+- case constant.String:
+- // Usually the original declaration of a string doesn't carry much information.
+- // Also strings can be very long. So, just use the constant's value.
+-
+- default:
+- if spec, _ := spec.(*ast.ValueSpec); spec != nil {
+- for i, name := range spec.Names {
+- if declPos == name.Pos() {
+- if i < len(spec.Values) {
+- originalDeclaration := formatNodeFile(file, spec.Values[i])
+- if originalDeclaration != declaration {
+- comment = declaration
+- declaration = originalDeclaration
+- }
+- }
+- break
+- }
+- }
+- }
+- }
+-
+- // Special formatting cases.
+- switch typ := types.Unalias(obj.Type()).(type) {
+- case *types.Named:
+- // Try to add a formatted duration as an inline comment.
+- pkg := typ.Obj().Pkg()
+- if pkg.Path() == "time" && typ.Obj().Name() == "Duration" && obj.Val().Kind() == constant.Int {
+- if d, ok := constant.Int64Val(obj.Val()); ok {
+- comment = time.Duration(d).String()
+- }
+- }
+- }
+- if comment == declaration {
+- comment = ""
+- }
+-
+- str += " = " + declaration
+- if comment != "" {
+- str += " // " + comment
+- }
+- }
+- return str
+-}
+-
+-// HoverDocForObject returns the best doc comment for obj (for which
+-// fset provides file/line information).
+-//
+-// TODO(rfindley): there appears to be zero(!) tests for this functionality.
+-func HoverDocForObject(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, obj types.Object) (*ast.CommentGroup, error) {
+- if is[*types.TypeName](obj) && is[*types.TypeParam](obj.Type()) {
+- return nil, nil
+- }
+-
+- pgf, pos, err := parseFull(ctx, snapshot, fset, obj)
+- if err != nil {
+- return nil, fmt.Errorf("re-parsing: %v", err)
+- }
+-
+- decl, spec, field := findDeclInfo([]*ast.File{pgf.File}, pos)
+- return chooseDocComment(decl, spec, field), nil
+-}
+-
+-func chooseDocComment(decl ast.Decl, spec ast.Spec, field *ast.Field) *ast.CommentGroup {
+- if field != nil {
+- if field.Doc != nil {
+- return field.Doc
+- }
+- if field.Comment != nil {
+- return field.Comment
+- }
+- return nil
+- }
+- switch decl := decl.(type) {
+- case *ast.FuncDecl:
+- return decl.Doc
+- case *ast.GenDecl:
+- switch spec := spec.(type) {
+- case *ast.ValueSpec:
+- if spec.Doc != nil {
+- return spec.Doc
+- }
+- if decl.Doc != nil {
+- return decl.Doc
+- }
+- return spec.Comment
+- case *ast.TypeSpec:
+- if spec.Doc != nil {
+- return spec.Doc
+- }
+- if decl.Doc != nil {
+- return decl.Doc
+- }
+- return spec.Comment
+- }
+- }
+- return nil
+-}
+-
+-// parseFull fully parses the file containing the declaration of obj
+-// (for which fset provides file/line information). It returns the
+-// parsego.File and the position of the declaration within it.
+-//
+-// BEWARE: the provided FileSet is used only to interpret the provided
+-// pos; the resulting File and Pos may belong to the same or a
+-// different FileSet, such as one synthesized by the parser cache, if
+-// parse-caching is enabled.
+-func parseFull(ctx context.Context, snapshot *cache.Snapshot, fset *token.FileSet, obj types.Object) (*parsego.File, token.Pos, error) {
+- if isBuiltin(obj) {
+- pgf, id, err := builtinDecl(ctx, snapshot, obj)
+- if err != nil {
+- return nil, 0, err
+- }
+- return pgf, id.Pos(), err
+- }
+- pos := obj.Pos()
+- f := fset.File(pos)
+- if f == nil {
+- return nil, 0, bug.Errorf("internal error: no file for position %d", pos)
+- }
+-
+- uri := protocol.URIFromPath(f.Name())
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return nil, 0, err
+- }
+-
+- pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
+- if err != nil {
+- return nil, 0, err
+- }
+-
+- // Translate pos from original file to new file.
+- offset, err := safetoken.Offset(f, pos)
+- if err != nil {
+- return nil, 0, bug.Errorf("offset out of bounds in %q", uri)
+- }
+- fullPos, err := safetoken.Pos(pgf.Tok, offset)
+- if err != nil {
+- return nil, 0, err
+- }
+-
+- return pgf, fullPos, nil
+-}
+-
+-// If pkgURL is non-nil, it should be used to generate doc links.
+-func formatHover(h *hoverResult, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) (string, error) {
+- markdown := options.PreferredContentFormat == protocol.Markdown
+- maybeFenced := func(s string) string {
+- if s != "" && markdown {
+- s = fmt.Sprintf("```go\n%s\n```", strings.Trim(s, "\n"))
+- }
+- return s
+- }
+-
+- switch options.HoverKind {
+- case settings.SingleLine:
+- return h.SingleLine, nil
+-
+- case settings.NoDocumentation:
+- return maybeFenced(h.Signature), nil
+-
+- case settings.Structured:
+- b, err := json.Marshal(h)
+- if err != nil {
+- return "", err
+- }
+- return string(b), nil
+-
+- case settings.SynopsisDocumentation, settings.FullDocumentation:
+- var sections [][]string // assembled below
+-
+- // Signature section.
+- //
+- // For types, we display TypeDecl and Methods,
+- // but not Signature, which is redundant (= TypeDecl + "\n" + Methods).
+- // For all other symbols, we display Signature;
+- // TypeDecl and Methods are empty.
+- // TODO(golang/go#70233): When JSON is no more, we could rationalize this.
+- if h.typeDecl != "" {
+- sections = append(sections, []string{maybeFenced(h.typeDecl)})
+- } else {
+- sections = append(sections, []string{maybeFenced(h.Signature)})
+- }
+-
+- // Doc section.
+- var doc string
+- switch options.HoverKind {
+- case settings.SynopsisDocumentation:
+- doc = h.Synopsis
+- case settings.FullDocumentation:
+- doc = h.FullDocumentation
+- }
+- if options.PreferredContentFormat == protocol.Markdown {
+- doc = DocCommentToMarkdown(doc, options)
+- }
+- sections = append(sections, []string{
+- doc,
+- maybeFenced(h.promotedFields),
+- maybeFenced(h.methods),
+- })
+-
+- // Footer section.
+- sections = append(sections, []string{
+- h.footer,
+- formatLink(h, options, pkgURL),
+- })
+-
+- var b strings.Builder
+- newline := func() {
+- if options.PreferredContentFormat == protocol.Markdown {
+- b.WriteString("\n\n")
+- } else {
+- b.WriteByte('\n')
+- }
+- }
+- for _, section := range sections {
+- start := b.Len()
+- for _, part := range section {
+- if part == "" {
+- continue
+- }
+- // When markdown is a available, insert an hline before the start of
+- // the section, if there is content above.
+- if markdown && b.Len() == start && start > 0 {
+- newline()
+- b.WriteString("---")
+- }
+- if b.Len() > 0 {
+- newline()
+- }
+- b.WriteString(part)
+- }
+- }
+- return b.String(), nil
+-
+- default:
+- return "", fmt.Errorf("invalid HoverKind: %v", options.HoverKind)
+- }
+-}
+-
+-// StdSymbolOf returns the std lib symbol information of the given obj.
+-// It returns nil if the input obj is not an exported standard library symbol.
+-func StdSymbolOf(obj types.Object) *stdlib.Symbol {
+- if !obj.Exported() || obj.Pkg() == nil {
+- return nil
+- }
+-
+- // Symbols that not defined in standard library should return early.
+- // TODO(hxjiang): The returned slices is binary searchable.
+- symbols := stdlib.PackageSymbols[obj.Pkg().Path()]
+- if symbols == nil {
+- return nil
+- }
+-
+- // Handle Function, Type, Const & Var.
+- if obj != nil && typesinternal.IsPackageLevel(obj) {
+- for _, s := range symbols {
+- if s.Kind == stdlib.Method || s.Kind == stdlib.Field {
+- continue
+- }
+- if s.Name == obj.Name() {
+- return &s
+- }
+- }
+- return nil
+- }
+-
+- // Handle Method.
+- if fn, _ := obj.(*types.Func); fn != nil {
+- isPtr, named := typesinternal.ReceiverNamed(fn.Signature().Recv())
+- if named != nil && typesinternal.IsPackageLevel(named.Obj()) {
+- for _, s := range symbols {
+- if s.Kind != stdlib.Method {
+- continue
+- }
+- ptr, recv, name := s.SplitMethod()
+- if ptr == isPtr && recv == named.Obj().Name() && name == fn.Name() {
+- return &s
+- }
+- }
+- return nil
+- }
+- }
+-
+- // Handle Field.
+- if v, _ := obj.(*types.Var); v != nil && v.IsField() {
+- for _, s := range symbols {
+- if s.Kind != stdlib.Field {
+- continue
+- }
+-
+- typeName, fieldName := s.SplitField()
+- if fieldName != v.Name() {
+- continue
+- }
+-
+- typeObj := obj.Pkg().Scope().Lookup(typeName)
+- if typeObj == nil {
+- continue
+- }
+-
+- if fieldObj, _, _ := types.LookupFieldOrMethod(typeObj.Type(), true, obj.Pkg(), fieldName); obj == fieldObj {
+- return &s
+- }
+- }
+- return nil
+- }
+-
+- return nil
+-}
+-
+-// If pkgURL is non-nil, it should be used to generate doc links.
+-func formatLink(h *hoverResult, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string {
+- if options.LinksInHover == settings.LinksInHover_None || h.LinkPath == "" {
+- return ""
+- }
+- var url protocol.URI
+- var caption string
+- if pkgURL != nil { // LinksInHover == "gopls"
+- // Discard optional module version portion.
+- // (Ideally the hoverResult would retain the structure...)
+- path := h.LinkPath
+- if module, versionDir, ok := strings.Cut(h.LinkPath, "@"); ok {
+- // "module@version/dir"
+- path = module
+- if _, dir, ok := strings.Cut(versionDir, "/"); ok {
+- path += "/" + dir
+- }
+- }
+- url = pkgURL(PackagePath(path), h.LinkAnchor)
+- caption = "in gopls doc viewer"
+- } else {
+- if options.LinkTarget == "" {
+- return ""
+- }
+- url = cache.BuildLink(options.LinkTarget, h.LinkPath, h.LinkAnchor)
+- caption = "on " + options.LinkTarget
+- }
+- switch options.PreferredContentFormat {
+- case protocol.Markdown:
+- return fmt.Sprintf("[`%s` %s](%s)", h.SymbolName, caption, url)
+- case protocol.PlainText:
+- return ""
+- default:
+- return url
+- }
+-}
+-
+-// findDeclInfo returns the syntax nodes involved in the declaration of the
+-// types.Object with position pos, searching the given list of file syntax
+-// trees.
+-//
+-// Pos may be the position of the name-defining identifier in a FuncDecl,
+-// ValueSpec, TypeSpec, Field, or as a special case the position of
+-// Ellipsis.Elt in an ellipsis field.
+-//
+-// If found, the resulting decl, spec, and field will be the inner-most
+-// instance of each node type surrounding pos.
+-//
+-// If field is non-nil, pos is the position of a field Var. If field is nil and
+-// spec is non-nil, pos is the position of a Var, Const, or TypeName object. If
+-// both field and spec are nil and decl is non-nil, pos is the position of a
+-// Func object.
+-//
+-// It returns a nil decl if no object-defining node is found at pos.
+-//
+-// TODO(rfindley): this function has tricky semantics, and may be worth unit
+-// testing and/or refactoring.
+-func findDeclInfo(files []*ast.File, pos token.Pos) (decl ast.Decl, spec ast.Spec, field *ast.Field) {
+- found := false
+-
+- // Visit the files in search of the node at pos.
+- stack := make([]ast.Node, 0, 20)
+-
+- // Allocate the closure once, outside the loop.
+- f := func(n ast.Node, stack []ast.Node) bool {
+- if found {
+- return false
+- }
+-
+- // Skip subtrees (incl. files) that don't contain the search point.
+- if !(n.Pos() <= pos && pos < n.End()) {
+- return false
+- }
+-
+- switch n := n.(type) {
+- case *ast.Field:
+- findEnclosingDeclAndSpec := func() {
+- for i := len(stack) - 1; i >= 0; i-- {
+- switch n := stack[i].(type) {
+- case ast.Spec:
+- spec = n
+- case ast.Decl:
+- decl = n
+- return
+- }
+- }
+- }
+-
+- // Check each field name since you can have
+- // multiple names for the same type expression.
+- for _, id := range n.Names {
+- if id.Pos() == pos {
+- field = n
+- findEnclosingDeclAndSpec()
+- found = true
+- return false
+- }
+- }
+-
+- // Check *ast.Field itself. This handles embedded
+- // fields which have no associated *ast.Ident name.
+- if n.Pos() == pos {
+- field = n
+- findEnclosingDeclAndSpec()
+- found = true
+- return false
+- }
+-
+- // Also check "X" in "...X". This makes it easy to format variadic
+- // signature params properly.
+- //
+- // TODO(rfindley): I don't understand this comment. How does finding the
+- // field in this case make it easier to format variadic signature params?
+- if ell, ok := n.Type.(*ast.Ellipsis); ok && ell.Elt != nil && ell.Elt.Pos() == pos {
+- field = n
+- findEnclosingDeclAndSpec()
+- found = true
+- return false
+- }
+-
+- case *ast.FuncDecl:
+- if n.Name.Pos() == pos {
+- decl = n
+- found = true
+- return false
+- }
+-
+- case *ast.GenDecl:
+- for _, s := range n.Specs {
+- switch s := s.(type) {
+- case *ast.TypeSpec:
+- if s.Name.Pos() == pos {
+- decl = n
+- spec = s
+- found = true
+- return false
+- }
+- case *ast.ValueSpec:
+- for _, id := range s.Names {
+- if id.Pos() == pos {
+- decl = n
+- spec = s
+- found = true
+- return false
+- }
+- }
+- }
+- }
+- }
+- return true
+- }
+- for _, file := range files {
+- ast.PreorderStack(file, stack, f)
+- if found {
+- return decl, spec, field
+- }
+- }
+-
+- return nil, nil, nil
+-}
+-
+-type promotedField struct {
+- path string // path (e.g. "x.y" through embedded fields)
+- field *types.Var
+-}
+-
+-// promotedFields returns the list of accessible promoted fields of a struct type t.
+-// (Logic plundered from x/tools/cmd/guru/describe.go.)
+-func promotedFields(t types.Type, from *types.Package) []promotedField {
+- wantField := func(f *types.Var) bool {
+- if !accessibleTo(f, from) {
+- return false
+- }
+- // Check that the field is not shadowed.
+- obj, _, _ := types.LookupFieldOrMethod(t, true, f.Pkg(), f.Name())
+- return obj == f
+- }
+-
+- var fields []promotedField
+- var visit func(t types.Type, stack []*types.Named)
+- visit = func(t types.Type, stack []*types.Named) {
+- tStruct, ok := typesinternal.Unpointer(t).Underlying().(*types.Struct)
+- if !ok {
+- return
+- }
+- fieldloop:
+- for i := 0; i < tStruct.NumFields(); i++ {
+- f := tStruct.Field(i)
+-
+- // Handle recursion through anonymous fields.
+- if f.Anonymous() {
+- if _, named := typesinternal.ReceiverNamed(f); named != nil {
+- // If we've already visited this named type
+- // on this path, break the cycle.
+- for _, x := range stack {
+- if x.Origin() == named.Origin() {
+- continue fieldloop
+- }
+- }
+- visit(f.Type(), append(stack, named))
+- }
+- }
+-
+- // Save accessible promoted fields.
+- if len(stack) > 0 && wantField(f) {
+- var path strings.Builder
+- for i, t := range stack {
+- if i > 0 {
+- path.WriteByte('.')
+- }
+- path.WriteString(t.Obj().Name())
+- }
+- fields = append(fields, promotedField{
+- path: path.String(),
+- field: f,
+- })
+- }
+- }
+- }
+- visit(t, nil)
+-
+- return fields
+-}
+-
+-func accessibleTo(obj types.Object, pkg *types.Package) bool {
+- return obj.Exported() || obj.Pkg() == pkg
+-}
+-
+-// computeSizeOffsetInfo reports the size of obj (if a type or struct
+-// field), its wasted space percentage (if a struct type), and its
+-// offset (if a struct field). It returns -1 for undefined components.
+-func computeSizeOffsetInfo(pkg *cache.Package, path []ast.Node, obj types.Object) (size, wasted, offset int64) {
+- size, wasted, offset = -1, -1, -1
+-
+- var free typeparams.Free
+- sizes := pkg.TypesSizes()
+-
+- // size (types and fields)
+- if v, ok := obj.(*types.Var); ok && v.IsField() || is[*types.TypeName](obj) {
+- // If the field's type has free type parameters,
+- // its size cannot be computed.
+- if !free.Has(obj.Type()) {
+- size = sizes.Sizeof(obj.Type())
+- }
+-
+- // wasted space (struct types)
+- if tStruct, ok := obj.Type().Underlying().(*types.Struct); ok && is[*types.TypeName](obj) && size > 0 {
+- var fields []*types.Var
+- for i := 0; i < tStruct.NumFields(); i++ {
+- fields = append(fields, tStruct.Field(i))
+- }
+- if len(fields) > 0 {
+- // Sort into descending (most compact) order
+- // and recompute size of entire struct.
+- sort.Slice(fields, func(i, j int) bool {
+- return sizes.Sizeof(fields[i].Type()) >
+- sizes.Sizeof(fields[j].Type())
+- })
+- offsets := sizes.Offsetsof(fields)
+- compactSize := offsets[len(offsets)-1] + sizes.Sizeof(fields[len(fields)-1].Type())
+- wasted = 100 * (size - compactSize) / size
+- }
+- }
+- }
+-
+- // offset (fields)
+- if v, ok := obj.(*types.Var); ok && v.IsField() {
+- // Find enclosing struct type.
+- var tStruct *types.Struct
+- for _, n := range path {
+- if n, ok := n.(*ast.StructType); ok {
+- t, ok := pkg.TypesInfo().TypeOf(n).(*types.Struct)
+- if ok {
+- // golang/go#69150: TypeOf(n) was observed not to be a Struct (likely
+- // nil) in some cases.
+- tStruct = t
+- }
+- break
+- }
+- }
+- if tStruct != nil {
+- var fields []*types.Var
+- for i := 0; i < tStruct.NumFields(); i++ {
+- f := tStruct.Field(i)
+- // If any preceding field's type has free type parameters,
+- // its offset cannot be computed.
+- if free.Has(f.Type()) {
+- break
+- }
+- fields = append(fields, f)
+- if f == v {
+- offsets := sizes.Offsetsof(fields)
+- offset = offsets[len(offsets)-1]
+- break
+- }
+- }
+- }
+- }
+-
+- return
+-}
+-
+-// sizeClass reports the size class for a struct of the specified size, or -1 if unknown.f
+-// See GOROOT/src/runtime/msize.go for details.
+-func sizeClass(size int64) int64 {
+- if size > 1<<16 {
+- return -1 // avoid allocation
+- }
+- // We assume that bytes.Clone doesn't trim,
+- // and reports the underlying size class; see TestSizeClass.
+- return int64(cap(bytes.Clone(make([]byte, size))))
+-}
+diff -urN a/gopls/internal/golang/hover_test.go b/gopls/internal/golang/hover_test.go
+--- a/gopls/internal/golang/hover_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/hover_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,22 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import "testing"
+-
+-func TestSizeClass(t *testing.T) {
+- // See GOROOT/src/runtime/msize.go for details.
+- for _, test := range [...]struct{ size, class int64 }{
+- {8, 8},
+- {9, 16},
+- {16, 16},
+- {17, 24},
+- } {
+- got := sizeClass(test.size)
+- if got != test.class {
+- t.Errorf("sizeClass(%d) = %d, want %d", test.size, got, test.class)
+- }
+- }
+-}
+diff -urN a/gopls/internal/golang/identifier.go b/gopls/internal/golang/identifier.go
+--- a/gopls/internal/golang/identifier.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/identifier.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,182 +0,0 @@
+-// Copyright 2018 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "errors"
+- "go/ast"
+- "go/types"
+-
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-// ErrNoIdentFound is error returned when no identifier is found at a particular position
+-var ErrNoIdentFound = errors.New("no identifier found")
+-
+-// inferredSignature determines the resolved non-generic signature for an
+-// identifier in an instantiation expression.
+-//
+-// If no such signature exists, it returns nil.
+-func inferredSignature(info *types.Info, id *ast.Ident) *types.Signature {
+- inst := info.Instances[id]
+- sig, _ := types.Unalias(inst.Type).(*types.Signature)
+- return sig
+-}
+-
+-// searchForEnclosing returns, given the AST path to a SelectorExpr,
+-// the exported named type of the innermost implicit field selection.
+-//
+-// For example, given "new(A).d" where this is (due to embedding) a
+-// shorthand for "new(A).b.c.d", it returns the named type of c,
+-// if it is exported, otherwise the type of b, or A.
+-func searchForEnclosing(info *types.Info, path []ast.Node) *types.TypeName {
+- for _, n := range path {
+- switch n := n.(type) {
+- case *ast.SelectorExpr:
+- if sel, ok := info.Selections[n]; ok {
+- recv := typesinternal.Unpointer(sel.Recv())
+-
+- // Keep track of the last exported type seen.
+- var exported *types.TypeName
+- if named, ok := types.Unalias(recv).(*types.Named); ok && named.Obj().Exported() {
+- exported = named.Obj()
+- }
+- // We don't want the last element, as that's the field or
+- // method itself.
+- for _, index := range sel.Index()[:len(sel.Index())-1] {
+- if r, ok := recv.Underlying().(*types.Struct); ok {
+- recv = typesinternal.Unpointer(r.Field(index).Type())
+- if named, ok := types.Unalias(recv).(*types.Named); ok && named.Obj().Exported() {
+- exported = named.Obj()
+- }
+- }
+- }
+- return exported
+- }
+- }
+- }
+- return nil
+-}
+-
+-// typeToObject returns the relevant type name for the given type, after
+-// unwrapping pointers, arrays, slices, channels, and function signatures with
+-// a single non-error result, and ignoring built-in named types.
+-func typeToObject(typ types.Type) *types.TypeName {
+- switch typ := typ.(type) {
+- case *types.Alias:
+- return typ.Obj()
+- case *types.Named:
+- // TODO(rfindley): this should use typeparams.NamedTypeOrigin.
+- return typ.Obj()
+- case *types.Pointer:
+- return typeToObject(typ.Elem())
+- case *types.Array:
+- return typeToObject(typ.Elem())
+- case *types.Slice:
+- return typeToObject(typ.Elem())
+- case *types.Chan:
+- return typeToObject(typ.Elem())
+- case *types.Signature:
+- // Try to find a return value of a named type. If there's only one
+- // such value, jump to its type definition.
+- var res *types.TypeName
+-
+- results := typ.Results()
+- for i := 0; i < results.Len(); i++ {
+- obj := typeToObject(results.At(i).Type())
+- if obj == nil || hasErrorType(obj) {
+- // Skip builtins. TODO(rfindley): should comparable be handled here as well?
+- continue
+- }
+- if res != nil {
+- // The function/method must have only one return value of a named type.
+- return nil
+- }
+-
+- res = obj
+- }
+- return res
+- default:
+- return nil
+- }
+-}
+-
+-func hasErrorType(obj types.Object) bool {
+- return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error"
+-}
+-
+-// typeSwitchImplicits returns all the implicit type switch objects that
+-// correspond to the leaf *ast.Ident. It also returns the original type
+-// associated with the identifier (outside of a case clause).
+-func typeSwitchImplicits(info *types.Info, path []ast.Node) ([]types.Object, types.Type) {
+- ident, _ := path[0].(*ast.Ident)
+- if ident == nil {
+- return nil, nil
+- }
+-
+- var (
+- ts *ast.TypeSwitchStmt
+- assign *ast.AssignStmt
+- cc *ast.CaseClause
+- obj = info.ObjectOf(ident)
+- )
+-
+- // Walk our ancestors to determine if our leaf ident refers to a
+- // type switch variable, e.g. the "a" from "switch a := b.(type)".
+-Outer:
+- for i := 1; i < len(path); i++ {
+- switch n := path[i].(type) {
+- case *ast.AssignStmt:
+- // Check if ident is the "a" in "a := foo.(type)". The "a" in
+- // this case has no types.Object, so check for ident equality.
+- if len(n.Lhs) == 1 && n.Lhs[0] == ident {
+- assign = n
+- }
+- case *ast.CaseClause:
+- // Check if ident is a use of "a" within a case clause. Each
+- // case clause implicitly maps "a" to a different types.Object,
+- // so check if ident's object is the case clause's implicit
+- // object.
+- if obj != nil && info.Implicits[n] == obj {
+- cc = n
+- }
+- case *ast.TypeSwitchStmt:
+- // Look for the type switch that owns our previously found
+- // *ast.AssignStmt or *ast.CaseClause.
+- if n.Assign == assign {
+- ts = n
+- break Outer
+- }
+-
+- for _, stmt := range n.Body.List {
+- if stmt == cc {
+- ts = n
+- break Outer
+- }
+- }
+- }
+- }
+- if ts == nil {
+- return nil, nil
+- }
+- // Our leaf ident refers to a type switch variable. Fan out to the
+- // type switch's implicit case clause objects.
+- var objs []types.Object
+- for _, cc := range ts.Body.List {
+- if ccObj := info.Implicits[cc]; ccObj != nil {
+- objs = append(objs, ccObj)
+- }
+- }
+- // The right-hand side of a type switch should only have one
+- // element, and we need to track its type in order to generate
+- // hover information for implicit type switch variables.
+- var typ types.Type
+- if assign, ok := ts.Assign.(*ast.AssignStmt); ok && len(assign.Rhs) == 1 {
+- if rhs := assign.Rhs[0].(*ast.TypeAssertExpr); ok {
+- typ = info.TypeOf(rhs.X) // may be nil
+- }
+- }
+- return objs, typ
+-}
+diff -urN a/gopls/internal/golang/identifier_test.go b/gopls/internal/golang/identifier_test.go
+--- a/gopls/internal/golang/identifier_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/identifier_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,104 +0,0 @@
+-// Copyright 2020 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "bytes"
+- "go/ast"
+- "go/parser"
+- "go/token"
+- "go/types"
+- "testing"
+-)
+-
+-func TestSearchForEnclosing(t *testing.T) {
+- tests := []struct {
+- desc string
+- // For convenience, consider the first occurrence of the identifier "X" in
+- // src.
+- src string
+- // By convention, "" means no type found.
+- wantTypeName string
+- }{
+- {
+- // TODO(rFindley): is this correct, or do we want to resolve I2 here?
+- desc: "embedded interface in interface",
+- src: `package a; var y = i1.X; type i1 interface {I2}; type I2 interface{X()}`,
+- wantTypeName: "",
+- },
+- {
+- desc: "embedded interface in struct",
+- src: `package a; var y = t.X; type t struct {I}; type I interface{X()}`,
+- wantTypeName: "I",
+- },
+- {
+- desc: "double embedding",
+- src: `package a; var y = t1.X; type t1 struct {t2}; type t2 struct {I}; type I interface{X()}`,
+- wantTypeName: "I",
+- },
+- }
+-
+- for _, test := range tests {
+- t.Run(test.desc, func(t *testing.T) {
+- fset := token.NewFileSet()
+- file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors|parser.SkipObjectResolution)
+- if err != nil {
+- t.Fatal(err)
+- }
+- column := 1 + bytes.IndexRune([]byte(test.src), 'X')
+- pos := posAt(1, column, fset, "a.go")
+- path := pathEnclosingObjNode(file, pos)
+- if path == nil {
+- t.Fatalf("no ident found at (1, %d)", column)
+- }
+- info := newInfo()
+- if _, err = (*types.Config)(nil).Check("p", fset, []*ast.File{file}, info); err != nil {
+- t.Fatal(err)
+- }
+- obj := searchForEnclosing(info, path)
+- if obj == nil {
+- if test.wantTypeName != "" {
+- t.Errorf("searchForEnclosing(...) = , want %q", test.wantTypeName)
+- }
+- return
+- }
+- if got := obj.Name(); got != test.wantTypeName {
+- t.Errorf("searchForEnclosing(...) = %q, want %q", got, test.wantTypeName)
+- }
+- })
+- }
+-}
+-
+-// posAt returns the token.Pos corresponding to the 1-based (line, column)
+-// coordinates in the file fname of fset.
+-func posAt(line, column int, fset *token.FileSet, fname string) token.Pos {
+- var tok *token.File
+- fset.Iterate(func(tf *token.File) bool {
+- if tf.Name() == fname {
+- tok = tf
+- return false
+- }
+- return true
+- })
+- if tok == nil {
+- return token.NoPos
+- }
+- start := tok.LineStart(line)
+- return start + token.Pos(column-1)
+-}
+-
+-// newInfo returns a types.Info with all maps populated.
+-func newInfo() *types.Info {
+- info := &types.Info{
+- Types: make(map[ast.Expr]types.TypeAndValue),
+- Defs: make(map[*ast.Ident]types.Object),
+- Uses: make(map[*ast.Ident]types.Object),
+- Implicits: make(map[ast.Node]types.Object),
+- Selections: make(map[*ast.SelectorExpr]*types.Selection),
+- Scopes: make(map[ast.Node]*types.Scope),
+- FileVersions: make(map[*ast.File]string),
+- }
+- return info
+-}
+diff -urN a/gopls/internal/golang/implementation.go b/gopls/internal/golang/implementation.go
+--- a/gopls/internal/golang/implementation.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/implementation.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,1133 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "errors"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "iter"
+- "reflect"
+- "slices"
+- "strings"
+- "sync"
+-
+- "golang.org/x/sync/errgroup"
+- "golang.org/x/tools/go/ast/edge"
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/methodsets"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/bug"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/moreiters"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-// This file defines the new implementation of the 'implementation'
+-// operator that does not require type-checker data structures for an
+-// unbounded number of packages.
+-//
+-// TODO(adonovan):
+-// - Audit to ensure robustness in face of type errors.
+-// - Eliminate false positives due to 'tricky' cases of the global algorithm.
+-// - Ensure we have test coverage of:
+-// type aliases
+-// nil, PkgName, Builtin (all errors)
+-// any (empty result)
+-// method of unnamed interface type (e.g. var x interface { f() })
+-// (the global algorithm may find implementations of this type
+-// but will not include it in the index.)
+-
+-// Implementation returns a new sorted array of locations of
+-// declarations of types that implement (or are implemented by) the
+-// type referred to at the given position.
+-//
+-// If the position denotes a method, the computation is applied to its
+-// receiver type and then its corresponding methods are returned.
+-func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) ([]protocol.Location, error) {
+- ctx, done := event.Start(ctx, "golang.Implementation")
+- defer done()
+-
+- locs, err := implementations(ctx, snapshot, f, pp)
+- if err != nil {
+- return nil, err
+- }
+- slices.SortFunc(locs, protocol.CompareLocation)
+- locs = slices.Compact(locs) // de-duplicate
+- return locs, nil
+-}
+-
+-func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.Location, error) {
+- // Type check the current package.
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+- if err != nil {
+- return nil, err
+- }
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Find implementations based on func signatures.
+- if locs, err := implFuncs(pkg, pgf, pos); err != errNotHandled {
+- return locs, err
+- }
+-
+- // Find implementations based on method sets.
+- var (
+- locsMu sync.Mutex
+- locs []protocol.Location
+- )
+- // relation=0 here means infer direction of the relation
+- // (Supertypes/Subtypes) from concreteness of query type/method.
+- // (Ideally the implementations request would provide directionality
+- // so that one could ask for, say, the superinterfaces of io.ReadCloser;
+- // see https://github.com/golang/go/issues/68641#issuecomment-2269293762.)
+- const relation = methodsets.TypeRelation(0)
+- err = implementationsMsets(ctx, snapshot, pkg, pgf, pos, relation, func(_ metadata.PackagePath, _ string, _ bool, loc protocol.Location) {
+- locsMu.Lock()
+- locs = append(locs, loc)
+- locsMu.Unlock()
+- })
+- return locs, err
+-}
+-
+-// An implYieldFunc is a callback called for each match produced by the implementation machinery.
+-// - name describes the type or method.
+-// - abstract indicates that the result is an interface type or interface method.
+-//
+-// implYieldFunc implementations must be concurrency-safe.
+-type implYieldFunc func(pkgpath metadata.PackagePath, name string, abstract bool, loc protocol.Location)
+-
+-// implementationsMsets computes implementations of the type at the
+-// specified position, by method sets.
+-//
+-// rel specifies the desired direction of the relation: Subtype,
+-// Supertype, or both. As a special case, zero means infer the
+-// direction from the concreteness of the query object: Supertype for
+-// a concrete type, Subtype for an interface.
+-//
+-// It is shared by Implementations and TypeHierarchy.
+-func implementationsMsets(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, pos token.Pos, rel methodsets.TypeRelation, yield implYieldFunc) error {
+- // First, find the object referenced at the cursor.
+- // The object may be declared in a different package.
+- obj, err := implementsObj(pkg.TypesInfo(), pgf.File, pos)
+- if err != nil {
+- return err
+- }
+-
+- // If the resulting object has a position, we can expand the search to types
+- // in the declaring package(s). In this case, we must re-type check these
+- // packages in the same realm.
+- var (
+- declOffset int
+- declURI protocol.DocumentURI
+- localPkgs []*cache.Package
+- )
+- if obj.Pos().IsValid() { // no local package for error or error.Error
+- declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
+- declOffset = declPosn.Offset
+- // Type-check the declaring package (incl. variants) for use
+- // by the "local" search, which uses type information to
+- // enumerate all types within the package that satisfy the
+- // query type, even those defined local to a function.
+- declURI = protocol.URIFromPath(declPosn.Filename)
+- declMPs, err := snapshot.MetadataForFile(ctx, declURI, true)
+- if err != nil {
+- return err
+- }
+- if len(declMPs) == 0 {
+- return fmt.Errorf("no packages for file %s", declURI)
+- }
+- ids := make([]PackageID, len(declMPs))
+- for i, mp := range declMPs {
+- ids[i] = mp.ID
+- }
+- localPkgs, err = snapshot.TypeCheck(ctx, ids...)
+- if err != nil {
+- return err
+- }
+- }
+-
+- pkg = nil // no longer used
+-
+- // Is the selected identifier a type name or method?
+- // (For methods, report the corresponding method names.)
+- queryType, queryMethod := typeOrMethod(obj)
+- if queryType == nil {
+- return bug.Errorf("%s is not a type or method", obj.Name()) // should have been handled by implementsObj
+- }
+-
+- // Compute the method-set fingerprint used as a key to the global search.
+- key, hasMethods := methodsets.KeyOf(queryType)
+- if !hasMethods {
+- // A type with no methods yields an empty result.
+- // (No point reporting that every type satisfies 'any'.)
+- return nil
+- }
+-
+- // If the client specified no relation, infer it
+- // from the concreteness of the query type.
+- if rel == 0 {
+- rel = cond(types.IsInterface(queryType),
+- methodsets.Subtype,
+- methodsets.Supertype)
+- }
+-
+- // The global search needs to look at every package in the
+- // forward transitive closure of the workspace; see package
+- // ./methodsets.
+- //
+- // For now we do all the type checking before beginning the search.
+- // TODO(adonovan): opt: search in parallel topological order
+- // so that we can overlap index lookup with typechecking.
+- // I suspect a number of algorithms on the result of TypeCheck could
+- // be optimized by being applied as soon as each package is available.
+- globalMetas, err := snapshot.AllMetadata(ctx)
+- if err != nil {
+- return err
+- }
+- metadata.RemoveIntermediateTestVariants(&globalMetas)
+- globalIDs := make([]PackageID, 0, len(globalMetas))
+-
+- var pkgPath PackagePath
+- if obj.Pkg() != nil { // nil for error
+- pkgPath = PackagePath(obj.Pkg().Path())
+- }
+- for _, mp := range globalMetas {
+- if mp.PkgPath == pkgPath {
+- continue // declaring package is handled by local implementation
+- }
+- globalIDs = append(globalIDs, mp.ID)
+- }
+- indexes, err := snapshot.MethodSets(ctx, globalIDs...)
+- if err != nil {
+- return fmt.Errorf("querying method sets: %v", err)
+- }
+-
+- // Search local and global packages in parallel.
+- var group errgroup.Group
+-
+- // local search
+- for _, pkg := range localPkgs {
+- // The localImplementations algorithm assumes needle and haystack
+- // belong to a single package (="realm" of types symbol identities),
+- // so we need to recompute obj for each local package.
+- // (By contrast the global algorithm is name-based.)
+- group.Go(func() error {
+- pkgID := pkg.Metadata().ID
+-
+- // Find declaring identifier based on (URI, offset)
+- // so that localImplementations can locate the
+- // corresponding obj/queryType/queryMethod in pkg.
+- declFile, err := pkg.File(declURI)
+- if err != nil {
+- return err // "can't happen"
+- }
+- pos, err := safetoken.Pos(declFile.Tok, declOffset)
+- if err != nil {
+- return err // also "can't happen"
+- }
+- path := pathEnclosingObjNode(declFile.File, pos)
+- if path == nil {
+- return ErrNoIdentFound // checked earlier
+- }
+- id, ok := path[0].(*ast.Ident)
+- if !ok {
+- return ErrNoIdentFound // checked earlier
+- }
+- if err := localImplementations(ctx, snapshot, pkg, id, rel, yield); err != nil {
+- return fmt.Errorf("querying local implementations %q: %v", pkgID, err)
+- }
+- return nil
+- })
+- }
+- // global search
+- for _, index := range indexes {
+- group.Go(func() error {
+- for _, res := range index.Search(key, rel, queryMethod) {
+- loc := res.Location
+- // Map offsets to protocol.Locations in parallel (may involve I/O).
+- group.Go(func() error {
+- ploc, err := offsetToLocation(ctx, snapshot, loc.Filename, loc.Start, loc.End)
+- if err != nil {
+- return err
+- }
+- yield(index.PkgPath, res.TypeName, res.IsInterface, ploc)
+- return nil
+- })
+- }
+- return nil
+- })
+- }
+- return group.Wait()
+-}
+-
+-// typeOrMethod returns the type and optional method to use in an
+-// Implementations operation on the specified symbol.
+-// It returns a nil type to indicate that the query should not proceed.
+-//
+-// (It is factored out to allow it to be used both in the query package
+-// then (in [localImplementations]) again in the declaring package.)
+-func typeOrMethod(obj types.Object) (types.Type, *types.Func) {
+- switch obj := obj.(type) {
+- case *types.TypeName:
+- return obj.Type(), nil
+- case *types.Func:
+- // For methods, use the receiver type, which may be anonymous.
+- if recv := obj.Signature().Recv(); recv != nil {
+- return recv.Type(), obj
+- }
+- }
+- return nil, nil
+-}
+-
+-// offsetToLocation converts an offset-based position to a protocol.Location,
+-// which requires reading the file.
+-func offsetToLocation(ctx context.Context, snapshot *cache.Snapshot, filename string, start, end int) (protocol.Location, error) {
+- uri := protocol.URIFromPath(filename)
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return protocol.Location{}, err // cancelled, perhaps
+- }
+- content, err := fh.Content()
+- if err != nil {
+- return protocol.Location{}, err // nonexistent or deleted ("can't happen")
+- }
+- m := protocol.NewMapper(uri, content)
+- return m.OffsetLocation(start, end)
+-}
+-
+-// implementsObj returns the object to query for implementations,
+-// which is a type name or method.
+-func implementsObj(info *types.Info, file *ast.File, pos token.Pos) (types.Object, error) {
+- // This function inherits the limitation of its predecessor in
+- // requiring the selection to be an identifier (of a type or
+- // method). But there's no fundamental reason why one could
+- // not pose this query about any selected piece of syntax that
+- // has a type and thus a method set.
+- // (If LSP was more thorough about passing text selections as
+- // intervals to queries, you could ask about the method set of a
+- // subexpression such as x.f().)
+-
+- // TODO(adonovan): simplify: use objectsAt?
+- path := pathEnclosingObjNode(file, pos)
+- if path == nil {
+- return nil, ErrNoIdentFound
+- }
+- id, ok := path[0].(*ast.Ident)
+- if !ok {
+- return nil, ErrNoIdentFound
+- }
+-
+- // Is the object a type or method? Reject other kinds.
+- obj := info.Uses[id]
+- if obj == nil {
+- // Check uses first (unlike ObjectOf) so that T in
+- // struct{T} is treated as a reference to a type,
+- // not a declaration of a field.
+- obj = info.Defs[id]
+- }
+- switch obj := obj.(type) {
+- case *types.TypeName:
+- // ok
+- case *types.Func:
+- if obj.Signature().Recv() == nil {
+- return nil, fmt.Errorf("%s is a function, not a method (query at 'func' token to find matching signatures)", id.Name)
+- }
+- case nil:
+- return nil, fmt.Errorf("%s denotes unknown object", id.Name)
+- default:
+- // e.g. *types.Var -> "var".
+- kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
+- // TODO(adonovan): improve upon "nil is a nil, not a type".
+- return nil, fmt.Errorf("%s is a %s, not a type", id.Name, kind)
+- }
+-
+- return obj, nil
+-}
+-
+-// localImplementations searches within pkg for declarations of all
+-// supertypes (if rel contains Supertype) or subtypes (if rel contains
+-// Subtype) of the type or method declared by id within the same
+-// package, and returns a new unordered array of their locations.
+-//
+-// If method is non-nil, the function instead returns the location
+-// of each type's method (if any) of that ID.
+-//
+-// ("Local" refers to the search within the same package, but this
+-// function's results may include type declarations that are local to
+-// a function body. The global search index excludes such types
+-// because reliably naming such types is hard.)
+-//
+-// Results are reported via the yield function.
+-func localImplementations(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, id *ast.Ident, rel methodsets.TypeRelation, yield implYieldFunc) error {
+- queryType, queryMethod := typeOrMethod(pkg.TypesInfo().Defs[id])
+- if queryType == nil {
+- return bug.Errorf("can't find corresponding symbol for %q in package %q", id.Name, pkg)
+- }
+- queryType = methodsets.EnsurePointer(queryType)
+-
+- var msets typeutil.MethodSetCache
+-
+- matches := func(candidateType types.Type) bool {
+- // Test the direction of the relation.
+- // The client may request either direction or both
+- // (e.g. when the client is References),
+- // and the Result reports each test independently;
+- // both tests succeed when comparing identical
+- // interface types.
+- var got methodsets.TypeRelation
+- if rel&methodsets.Supertype != 0 && implements(&msets, queryType, candidateType) {
+- got |= methodsets.Supertype
+- }
+- if rel&methodsets.Subtype != 0 && implements(&msets, candidateType, queryType) {
+- got |= methodsets.Subtype
+- }
+- return got != 0
+- }
+-
+- // Scan through all type declarations in the syntax.
+- for _, pgf := range pkg.CompiledGoFiles() {
+- for cur := range pgf.Cursor.Preorder((*ast.TypeSpec)(nil)) {
+- spec := cur.Node().(*ast.TypeSpec)
+- if spec.Name == id {
+- continue // avoid self-comparison of query type
+- }
+- def := pkg.TypesInfo().Defs[spec.Name]
+- if def == nil {
+- continue // "can't happen" for types
+- }
+- if def.(*types.TypeName).IsAlias() {
+- continue // skip type aliases to avoid duplicate reporting
+- }
+- candidateType := methodsets.EnsurePointer(def.Type())
+- if !matches(candidateType) {
+- continue
+- }
+-
+- // Ignore types with empty method sets.
+- // (No point reporting that every type satisfies 'any'.)
+- mset := msets.MethodSet(candidateType)
+- if mset.Len() == 0 {
+- continue
+- }
+-
+- isInterface := types.IsInterface(def.Type())
+-
+- if queryMethod == nil {
+- // Found matching type.
+- loc := mustLocation(pgf, spec.Name)
+- yield(pkg.Metadata().PkgPath, spec.Name.Name, isInterface, loc)
+- continue
+- }
+-
+- // Find corresponding method.
+- //
+- // We can't use LookupFieldOrMethod because it requires
+- // the methodID's types.Package, which we don't know.
+- // We could recursively search pkg.Imports for it,
+- // but it's easier to walk the method set.
+- for i := 0; i < mset.Len(); i++ {
+- m := mset.At(i).Obj()
+- if m.Pos() == id.Pos() {
+- continue // avoid self-comparison of query method
+- }
+- if m.Id() == queryMethod.Id() {
+- posn := safetoken.StartPosition(pkg.FileSet(), m.Pos())
+- loc, err := offsetToLocation(ctx, snapshot, posn.Filename, posn.Offset, posn.Offset+len(m.Name()))
+- if err != nil {
+- return err
+- }
+- yield(pkg.Metadata().PkgPath, m.Name(), isInterface, loc)
+- break
+- }
+- }
+- }
+- }
+-
+- // Special case: for types that satisfy error,
+- // report error in builtin.go (see #59527).
+- //
+- // (An inconsistency: we always report the type error
+- // even when the query was for the method error.Error.)
+- if matches(errorType) {
+- loc, err := errorLocation(ctx, snapshot)
+- if err != nil {
+- return err
+- }
+- yield("", "error", true, loc)
+- }
+-
+- return nil
+-}
+-
+-var errorType = types.Universe.Lookup("error").Type()
+-
+-// errorLocation returns the location of the 'error' type in builtin.go.
+-func errorLocation(ctx context.Context, snapshot *cache.Snapshot) (protocol.Location, error) {
+- pgf, err := snapshot.BuiltinFile(ctx)
+- if err != nil {
+- return protocol.Location{}, err
+- }
+- for _, decl := range pgf.File.Decls {
+- if decl, ok := decl.(*ast.GenDecl); ok {
+- for _, spec := range decl.Specs {
+- if spec, ok := spec.(*ast.TypeSpec); ok && spec.Name.Name == "error" {
+- return pgf.NodeLocation(spec.Name)
+- }
+- }
+- }
+- }
+- return protocol.Location{}, fmt.Errorf("built-in error type not found")
+-}
+-
+-// implements reports whether x implements y.
+-// If one or both types are generic, the result indicates whether the
+-// interface may be implemented under some instantiation.
+-func implements(msets *typeutil.MethodSetCache, x, y types.Type) bool {
+- if !types.IsInterface(y) {
+- return false
+- }
+-
+- // For each interface method of y, check that x has it too.
+- // It is not necessary to compute x's complete method set.
+- //
+- // If y is a constraint interface (!y.IsMethodSet()), we
+- // ignore non-interface terms, leading to occasional spurious
+- // matches. We could in future filter based on them, but it
+- // would lead to divergence with the global (fingerprint-based)
+- // algorithm, which operates only on methodsets.
+- ymset := msets.MethodSet(y)
+- for i := range ymset.Len() {
+- ym := ymset.At(i).Obj().(*types.Func)
+-
+- xobj, _, _ := types.LookupFieldOrMethod(x, false, ym.Pkg(), ym.Name())
+- xm, ok := xobj.(*types.Func)
+- if !ok {
+- return false // x lacks a method of y
+- }
+- if !unify(xm.Signature(), ym.Signature(), nil) {
+- return false // signatures do not match
+- }
+- }
+- return true // all methods found
+-}
+-
+-// unify reports whether the types of x and y match.
+-//
+-// If unifier is nil, unify reports only whether it succeeded.
+-// If unifier is non-nil, it is populated with the values
+-// of type parameters determined during a successful unification.
+-// If unification succeeds without binding a type parameter, that parameter
+-// will not be present in the map.
+-//
+-// On entry, the unifier's contents are treated as the values of already-bound type
+-// parameters, constraining the unification.
+-//
+-// For example, if unifier is an empty (not nil) map on entry, then the types
+-//
+-// func[T any](T, int)
+-//
+-// and
+-//
+-// func[U any](bool, U)
+-//
+-// will unify, with T=bool and U=int.
+-// That is, the contents of unifier after unify returns will be
+-//
+-// {T: bool, U: int}
+-//
+-// where "T" is the type parameter T and "bool" is the basic type for bool.
+-//
+-// But if unifier is {T: int} is int on entry, then unification will fail, because T
+-// does not unify with bool.
+-//
+-// Unify does not preserve aliases. For example, given the following:
+-//
+-// type String = string
+-// type A[T] = T
+-//
+-// unification succeeds with T bound to string, not String.
+-//
+-// See also: unify in cache/methodsets/fingerprint, which implements
+-// unification for type fingerprints, for the global index.
+-//
+-// BUG: literal interfaces are not handled properly. But this function is currently
+-// used only for signatures, where such types are very rare.
+-func unify(x, y types.Type, unifier map[*types.TypeParam]types.Type) bool {
+- // bindings[tp] is the binding for type parameter tp.
+- // Although type parameters are nominally bound to types, each bindings[tp]
+- // is a pointer to a type, so unbound variables that unify can share a binding.
+- bindings := map[*types.TypeParam]*types.Type{}
+-
+- // Bindings is initialized with pointers to the provided types.
+- for tp, t := range unifier {
+- bindings[tp] = &t
+- }
+-
+- // bindingFor returns the *types.Type in bindings for tp if tp is not nil,
+- // creating one if needed.
+- bindingFor := func(tp *types.TypeParam) *types.Type {
+- if tp == nil {
+- return nil
+- }
+- b := bindings[tp]
+- if b == nil {
+- b = new(types.Type)
+- bindings[tp] = b
+- }
+- return b
+- }
+-
+- // bind sets b to t if b does not occur in t.
+- bind := func(b *types.Type, t types.Type) bool {
+- for tp := range typeParams(t) {
+- if b == bindings[tp] {
+- return false // failed "occurs" check
+- }
+- }
+- *b = t
+- return true
+- }
+-
+- // uni performs the actual unification.
+- depth := 0
+- var uni func(x, y types.Type) bool
+- uni = func(x, y types.Type) bool {
+- // Panic if recursion gets too deep, to detect bugs before
+- // overflowing the stack.
+- depth++
+- defer func() { depth-- }()
+- if depth > 100 {
+- panic("unify: max depth exceeded")
+- }
+-
+- x = types.Unalias(x)
+- y = types.Unalias(y)
+-
+- tpx, _ := x.(*types.TypeParam)
+- tpy, _ := y.(*types.TypeParam)
+- if tpx != nil || tpy != nil {
+- // Identical type params unify.
+- if tpx == tpy {
+- return true
+- }
+- bx := bindingFor(tpx)
+- by := bindingFor(tpy)
+-
+- // If both args are type params and neither is bound, have them share a binding.
+- if bx != nil && by != nil && *bx == nil && *by == nil {
+- // Arbitrarily give y's binding to x.
+- bindings[tpx] = by
+- return true
+- }
+- // Treat param bindings like original args in what follows.
+- if bx != nil && *bx != nil {
+- x = *bx
+- }
+- if by != nil && *by != nil {
+- y = *by
+- }
+- // If the x param is unbound, bind it to y.
+- if bx != nil && *bx == nil {
+- return bind(bx, y)
+- }
+- // If the y param is unbound, bind it to x.
+- if by != nil && *by == nil {
+- return bind(by, x)
+- }
+- // Unify the binding of a bound parameter.
+- return uni(x, y)
+- }
+-
+- // Neither arg is a type param.
+-
+- if reflect.TypeOf(x) != reflect.TypeOf(y) {
+- return false // mismatched types
+- }
+-
+- switch x := x.(type) {
+- case *types.Array:
+- y := y.(*types.Array)
+- return x.Len() == y.Len() &&
+- uni(x.Elem(), y.Elem())
+-
+- case *types.Basic:
+- y := y.(*types.Basic)
+- return x.Kind() == y.Kind()
+-
+- case *types.Chan:
+- y := y.(*types.Chan)
+- return x.Dir() == y.Dir() &&
+- uni(x.Elem(), y.Elem())
+-
+- case *types.Interface:
+- y := y.(*types.Interface)
+- // TODO(adonovan,jba): fix: for correctness, we must check
+- // that both interfaces have the same set of methods
+- // modulo type parameters, while avoiding the risk of
+- // unbounded interface recursion.
+- //
+- // Since non-empty interface literals are vanishingly
+- // rare in methods signatures, we ignore this for now.
+- // If more precision is needed we could compare method
+- // names and arities, still without full recursion.
+- return x.NumMethods() == y.NumMethods()
+-
+- case *types.Map:
+- y := y.(*types.Map)
+- return uni(x.Key(), y.Key()) &&
+- uni(x.Elem(), y.Elem())
+-
+- case *types.Named:
+- y := y.(*types.Named)
+- if x.Origin() != y.Origin() {
+- return false // different named types
+- }
+- xtargs := x.TypeArgs()
+- ytargs := y.TypeArgs()
+- if xtargs.Len() != ytargs.Len() {
+- return false // arity error (ill-typed)
+- }
+- for i := range xtargs.Len() {
+- if !uni(xtargs.At(i), ytargs.At(i)) {
+- return false // mismatched type args
+- }
+- }
+- return true
+-
+- case *types.Pointer:
+- y := y.(*types.Pointer)
+- return uni(x.Elem(), y.Elem())
+-
+- case *types.Signature:
+- y := y.(*types.Signature)
+- return x.Variadic() == y.Variadic() &&
+- uni(x.Params(), y.Params()) &&
+- uni(x.Results(), y.Results())
+-
+- case *types.Slice:
+- y := y.(*types.Slice)
+- return uni(x.Elem(), y.Elem())
+-
+- case *types.Struct:
+- y := y.(*types.Struct)
+- if x.NumFields() != y.NumFields() {
+- return false
+- }
+- for i := range x.NumFields() {
+- xf := x.Field(i)
+- yf := y.Field(i)
+- if xf.Embedded() != yf.Embedded() ||
+- xf.Name() != yf.Name() ||
+- x.Tag(i) != y.Tag(i) ||
+- !xf.Exported() && xf.Pkg() != yf.Pkg() ||
+- !uni(xf.Type(), yf.Type()) {
+- return false
+- }
+- }
+- return true
+-
+- case *types.Tuple:
+- y := y.(*types.Tuple)
+- if x.Len() != y.Len() {
+- return false
+- }
+- for i := range x.Len() {
+- if !uni(x.At(i).Type(), y.At(i).Type()) {
+- return false
+- }
+- }
+- return true
+-
+- default: // incl. *Union, *TypeParam
+- panic(fmt.Sprintf("unexpected Type %#v", x))
+- }
+- }
+-
+- if !uni(x, y) {
+- clear(unifier)
+- return false
+- }
+-
+- // Populate the input map with the resulting types.
+- if unifier != nil {
+- for tparam, tptr := range bindings {
+- unifier[tparam] = *tptr
+- }
+- }
+- return true
+-}
+-
+-// typeParams yields all the free type parameters within t that are relevant for
+-// unification.
+-//
+-// Note: this function is tailored for the specific needs of the unification algorithm.
+-// Don't try to use it for other purposes, see [typeparams.Free] instead.
+-func typeParams(t types.Type) iter.Seq[*types.TypeParam] {
+-
+- return func(yield func(*types.TypeParam) bool) {
+- seen := map[*types.TypeParam]bool{} // yield each type param only once
+-
+- // tps(t) yields each TypeParam in t and returns false to stop.
+- var tps func(types.Type) bool
+- tps = func(t types.Type) bool {
+- t = types.Unalias(t)
+-
+- switch t := t.(type) {
+- case *types.TypeParam:
+- if seen[t] {
+- return true
+- }
+- seen[t] = true
+- return yield(t)
+-
+- case *types.Basic:
+- return true
+-
+- case *types.Array:
+- return tps(t.Elem())
+-
+- case *types.Chan:
+- return tps(t.Elem())
+-
+- case *types.Interface:
+- // TODO(jba): implement.
+- return true
+-
+- case *types.Map:
+- return tps(t.Key()) && tps(t.Elem())
+-
+- case *types.Named:
+- if t.Origin() == t {
+- // generic type: look at type params
+- return moreiters.Every(t.TypeParams().TypeParams(),
+- func(tp *types.TypeParam) bool { return tps(tp) })
+- }
+- // instantiated type: look at type args
+- return moreiters.Every(t.TypeArgs().Types(), tps)
+-
+- case *types.Pointer:
+- return tps(t.Elem())
+-
+- case *types.Signature:
+- return tps(t.Params()) && tps(t.Results())
+-
+- case *types.Slice:
+- return tps(t.Elem())
+-
+- case *types.Struct:
+- return moreiters.Every(t.Fields(),
+- func(v *types.Var) bool { return tps(v.Type()) })
+-
+- case *types.Tuple:
+- return moreiters.Every(t.Variables(),
+- func(v *types.Var) bool { return tps(v.Type()) })
+-
+- default: // incl. *Union
+- panic(fmt.Sprintf("unexpected Type %#v", t))
+- }
+- }
+-
+- tps(t)
+- }
+-}
+-
+-var (
+- // TODO(adonovan): why do various RPC handlers related to
+- // IncomingCalls return (nil, nil) on the protocol in response
+- // to this error? That seems like a violation of the protocol.
+- // Is it perhaps a workaround for VSCode behavior?
+- errNoObjectFound = errors.New("no object found")
+-)
+-
+-// pathEnclosingObjNode returns the AST path to the object-defining
+-// node associated with pos. "Object-defining" means either an
+-// *ast.Ident mapped directly to a types.Object or an ast.Node mapped
+-// implicitly to a types.Object.
+-func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node {
+- var (
+- path []ast.Node
+- found bool
+- )
+-
+- ast.Inspect(f, func(n ast.Node) bool {
+- if found {
+- return false
+- }
+-
+- if n == nil {
+- path = path[:len(path)-1]
+- return false
+- }
+-
+- path = append(path, n)
+-
+- switch n := n.(type) {
+- case *ast.Ident:
+- // Include the position directly after identifier. This handles
+- // the common case where the cursor is right after the
+- // identifier the user is currently typing. Previously we
+- // handled this by calling astutil.PathEnclosingInterval twice,
+- // once for "pos" and once for "pos-1".
+- found = n.Pos() <= pos && pos <= n.End()
+-
+- case *ast.ImportSpec:
+- if n.Path.Pos() <= pos && pos < n.Path.End() {
+- found = true
+- // If import spec has a name, add name to path even though
+- // position isn't in the name.
+- if n.Name != nil {
+- path = append(path, n.Name)
+- }
+- }
+-
+- case *ast.StarExpr:
+- // Follow star expressions to the inner identifier.
+- if pos == n.Star {
+- pos = n.X.Pos()
+- }
+- }
+-
+- return !found
+- })
+-
+- if len(path) == 0 {
+- return nil
+- }
+-
+- // Reverse path so leaf is first element.
+- slices.Reverse(path)
+- return path
+-}
+-
+-// --- Implementations based on signature types --
+-
+-// implFuncs finds Implementations based on func types.
+-//
+-// Just as an interface type abstracts a set of concrete methods, a
+-// function type abstracts a set of concrete functions. Gopls provides
+-// analogous operations for navigating from abstract to concrete and
+-// back in the domain of function types.
+-//
+-// A single type (for example http.HandlerFunc) can have both an
+-// underlying type of function (types.Signature) and have methods that
+-// cause it to implement an interface. To avoid a confusing user
+-// interface we want to separate the two operations so that the user
+-// can unambiguously specify the query they want.
+-//
+-// So, whereas Implementations queries on interface types are usually
+-// keyed by an identifier of a named type, Implementations queries on
+-// function types are keyed by the "func" keyword, or by the "(" of a
+-// call expression. The query relates two sets of locations:
+-//
+-// 1. the "func" token of each function declaration (FuncDecl or
+-// FuncLit). These are analogous to declarations of concrete
+-// methods.
+-//
+-// 2. uses of abstract functions:
+-//
+-// (a) the "func" token of each FuncType that is not part of
+-// Func{Decl,Lit}. These are analogous to interface{...} types.
+-//
+-// (b) the "(" paren of each dynamic call on a value of an
+-// abstract function type. These are analogous to references to
+-// interface method names, but no names are involved, which has
+-// historically made them hard to search for.
+-//
+-// An Implementations query on a location in set 1 returns set 2,
+-// and vice versa.
+-//
+-// implFuncs returns errNotHandled to indicate that we should try the
+-// regular method-sets algorithm.
+-func implFuncs(pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol.Location, error) {
+- curSel, ok := pgf.Cursor.FindByPos(pos, pos)
+- if !ok {
+- return nil, fmt.Errorf("no code selected")
+- }
+-
+- info := pkg.TypesInfo()
+- if info.Types == nil || info.Defs == nil || info.Uses == nil {
+- panic("one of info.Types, .Defs or .Uses is nil")
+- }
+-
+- // Find innermost enclosing FuncType or CallExpr.
+- //
+- // We are looking for specific tokens (FuncType.Func and
+- // CallExpr.Lparen), but FindPos prefers an adjoining
+- // subexpression: given f(x) without additional spaces between
+- // tokens, FindPos always returns either f or x, never the
+- // CallExpr itself. Thus we must ascend the tree.
+- //
+- // Another subtlety: due to an edge case in go/ast, FindPos at
+- // FuncDecl.Type.Func does not return FuncDecl.Type, only the
+- // FuncDecl, because the orders of tree positions and tokens
+- // are inconsistent. Consequently, the ancestors for a "func"
+- // token of Func{Lit,Decl} do not include FuncType, hence the
+- // explicit cases below.
+- for cur := range curSel.Enclosing(
+- (*ast.FuncDecl)(nil),
+- (*ast.FuncLit)(nil),
+- (*ast.FuncType)(nil),
+- (*ast.CallExpr)(nil),
+- ) {
+- switch n := cur.Node().(type) {
+- case *ast.FuncDecl, *ast.FuncLit:
+- if inToken(n.Pos(), "func", pos) {
+- // Case 1: concrete function declaration.
+- // Report uses of corresponding function types.
+- switch n := n.(type) {
+- case *ast.FuncDecl:
+- return funcUses(pkg, info.Defs[n.Name].Type())
+- case *ast.FuncLit:
+- return funcUses(pkg, info.TypeOf(n.Type))
+- }
+- }
+-
+- case *ast.FuncType:
+- if n.Func.IsValid() && inToken(n.Func, "func", pos) && !beneathFuncDef(cur) {
+- // Case 2a: function type.
+- // Report declarations of corresponding concrete functions.
+- return funcDefs(pkg, info.TypeOf(n))
+- }
+-
+- case *ast.CallExpr:
+- if inToken(n.Lparen, "(", pos) {
+- t := dynamicFuncCallType(info, n)
+- if t == nil {
+- return nil, fmt.Errorf("not a dynamic function call")
+- }
+- // Case 2b: dynamic call of function value.
+- // Report declarations of corresponding concrete functions.
+- return funcDefs(pkg, t)
+- }
+- }
+- }
+-
+- // It's probably a query of a named type or method.
+- // Fall back to the method-sets computation.
+- return nil, errNotHandled
+-}
+-
+-var errNotHandled = errors.New("not handled")
+-
+-// funcUses returns all locations in the workspace that are dynamic
+-// uses of the specified function type.
+-func funcUses(pkg *cache.Package, t types.Type) ([]protocol.Location, error) {
+- var locs []protocol.Location
+-
+- // local search
+- for _, pgf := range pkg.CompiledGoFiles() {
+- for cur := range pgf.Cursor.Preorder((*ast.CallExpr)(nil), (*ast.FuncType)(nil)) {
+- var pos, end token.Pos
+- var ftyp types.Type
+- switch n := cur.Node().(type) {
+- case *ast.CallExpr:
+- ftyp = dynamicFuncCallType(pkg.TypesInfo(), n)
+- pos, end = n.Lparen, n.Lparen+token.Pos(len("("))
+-
+- case *ast.FuncType:
+- if !beneathFuncDef(cur) {
+- // func type (not def)
+- ftyp = pkg.TypesInfo().TypeOf(n)
+- pos, end = n.Func, n.Func+token.Pos(len("func"))
+- }
+- }
+- if ftyp == nil {
+- continue // missing type information
+- }
+- if unify(t, ftyp, nil) {
+- loc, err := pgf.PosLocation(pos, end)
+- if err != nil {
+- return nil, err
+- }
+- locs = append(locs, loc)
+- }
+- }
+- }
+-
+- // TODO(adonovan): implement global search
+-
+- return locs, nil
+-}
+-
+-// funcDefs returns all locations in the workspace that define
+-// functions of the specified type.
+-func funcDefs(pkg *cache.Package, t types.Type) ([]protocol.Location, error) {
+- var locs []protocol.Location
+-
+- // local search
+- for _, pgf := range pkg.CompiledGoFiles() {
+- for curFn := range pgf.Cursor.Preorder((*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) {
+- fn := curFn.Node()
+- var ftyp types.Type
+- switch fn := fn.(type) {
+- case *ast.FuncDecl:
+- ftyp = pkg.TypesInfo().Defs[fn.Name].Type()
+- case *ast.FuncLit:
+- ftyp = pkg.TypesInfo().TypeOf(fn)
+- }
+- if ftyp == nil {
+- continue // missing type information
+- }
+- if unify(t, ftyp, nil) {
+- pos := fn.Pos()
+- loc, err := pgf.PosLocation(pos, pos+token.Pos(len("func")))
+- if err != nil {
+- return nil, err
+- }
+- locs = append(locs, loc)
+- }
+- }
+- }
+-
+- // TODO(adonovan): implement global search, by analogy with
+- // methodsets algorithm.
+- //
+- // One optimization: if any signature type has free package
+- // names, look for matches only in packages among the rdeps of
+- // those packages.
+-
+- return locs, nil
+-}
+-
+-// beneathFuncDef reports whether the specified FuncType cursor is a
+-// child of Func{Decl,Lit}.
+-func beneathFuncDef(cur inspector.Cursor) bool {
+- switch ek, _ := cur.ParentEdge(); ek {
+- case edge.FuncDecl_Type, edge.FuncLit_Type:
+- return true
+- }
+- return false
+-}
+-
+-// dynamicFuncCallType reports whether call is a dynamic (non-method) function call.
+-// If so, it returns the function type, otherwise nil.
+-//
+-// Tested via ../test/marker/testdata/implementation/signature.txt.
+-func dynamicFuncCallType(info *types.Info, call *ast.CallExpr) types.Type {
+- if typesinternal.ClassifyCall(info, call) == typesinternal.CallDynamic {
+- if tv, ok := info.Types[call.Fun]; ok {
+- return tv.Type.Underlying()
+- }
+- }
+- return nil
+-}
+-
+-// inToken reports whether pos is within the token of
+-// the specified position and string.
+-func inToken(tokPos token.Pos, tokStr string, pos token.Pos) bool {
+- return tokPos <= pos && pos <= tokPos+token.Pos(len(tokStr))
+-}
+diff -urN a/gopls/internal/golang/implementation_test.go b/gopls/internal/golang/implementation_test.go
+--- a/gopls/internal/golang/implementation_test.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/implementation_test.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,303 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "go/types"
+- "maps"
+- "testing"
+-
+- "golang.org/x/tools/internal/testfiles"
+- "golang.org/x/tools/txtar"
+-)
+-
+-func TestUnify(t *testing.T) {
+- // Most cases from TestMatches in gopls/internal/util/fingerprint/fingerprint_test.go.
+- const src = `
+--- go.mod --
+-module example.com
+-go 1.24
+-
+--- a/a.go --
+-package a
+-
+-type Int = int
+-type String = string
+-
+-// Eq.Equal matches casefold.Equal.
+-type Eq[T any] interface { Equal(T, T) bool }
+-type casefold struct{}
+-func (casefold) Equal(x, y string) bool
+-
+-// A matches AString.
+-type A[T any] = struct { x T }
+-type AString = struct { x string }
+-
+-// B matches anything!
+-type B[T any] = T
+-
+-func C1[T any](int, T, ...string) T { panic(0) }
+-func C2[U any](int, int, ...U) bool { panic(0) }
+-func C3(int, bool, ...string) rune
+-func C4(int, bool, ...string)
+-func C5(int, float64, bool, string) bool
+-func C6(int, bool, ...string) bool
+-
+-func DAny[T any](Named[T]) { panic(0) }
+-func DString(Named[string])
+-func DInt(Named[int])
+-
+-type Named[T any] struct { x T }
+-
+-func E1(byte) rune
+-func E2(uint8) int32
+-func E3(int8) uint32
+-
+-// generic vs. generic
+-func F1[T any](T) { panic(0) }
+-func F2[T any](*T) { panic(0) }
+-func F3[T any](T, T) { panic(0) }
+-func F4[U any](U, *U) {panic(0) }
+-func F4a[U any](U, Named[U]) {panic(0) }
+-func F5[T, U any](T, U, U) { panic(0) }
+-func F6[T any](T, int, T) { panic(0) }
+-func F7[T any](bool, T, T) { panic(0) }
+-func F8[V any](*V, int, int) { panic(0) }
+-func F9[V any](V, *V, V) { panic(0) }
+-`
+- type tmap = map[*types.TypeParam]types.Type
+-
+- var (
+- boolType = types.Typ[types.Bool]
+- intType = types.Typ[types.Int]
+- stringType = types.Typ[types.String]
+- )
+-
+- pkg := testfiles.LoadPackages(t, txtar.Parse([]byte(src)), "./a")[0]
+- scope := pkg.Types.Scope()
+-
+- tparam := func(name string, index int) *types.TypeParam {
+- obj := scope.Lookup(name)
+- var tps *types.TypeParamList
+- switch obj := obj.(type) {
+- case *types.Func:
+- tps = obj.Signature().TypeParams()
+- case *types.TypeName:
+- if n, ok := obj.Type().(*types.Named); ok {
+- tps = n.TypeParams()
+- } else {
+- tps = obj.Type().(*types.Alias).TypeParams()
+- }
+- default:
+- t.Fatalf("unsupported object of type %T", obj)
+- }
+- return tps.At(index)
+- }
+-
+- for _, test := range []struct {
+- x, y string // the symbols in the above source code whose types to unify
+- method string // optional field or method
+- params tmap // initial values of type params
+- want bool // success or failure
+- wantParams tmap // expected output
+- }{
+- {
+- // In Eq[T], T is bound to string.
+- x: "Eq",
+- y: "casefold",
+- method: "Equal",
+- want: true,
+- wantParams: tmap{tparam("Eq", 0): stringType},
+- },
+- {
+- // If we unify A[T] and A[string], T should be bound to string.
+- x: "A",
+- y: "AString",
+- want: true,
+- wantParams: tmap{tparam("A", 0): stringType},
+- },
+- {x: "A", y: "Eq", want: false}, // completely unrelated
+- {
+- x: "B",
+- y: "String",
+- want: true,
+- wantParams: tmap{tparam("B", 0): stringType},
+- },
+- {
+- x: "B",
+- y: "Int",
+- want: true,
+- wantParams: tmap{tparam("B", 0): intType},
+- },
+- {
+- x: "B",
+- y: "A",
+- want: true,
+- // B's T is bound to A's struct { x T }
+- wantParams: tmap{tparam("B", 0): scope.Lookup("A").Type().Underlying()},
+- },
+- {
+- // C1's U unifies with C6's bool.
+- x: "C1",
+- y: "C6",
+- wantParams: tmap{tparam("C1", 0): boolType},
+- want: true,
+- },
+- // C1 fails to unify with C2 because C1's T must be bound to both int and bool.
+- {x: "C1", y: "C2", want: false},
+- // The remaining "C" cases fail for less interesting reasons, usually different numbers
+- // or types of parameters or results.
+- {x: "C1", y: "C3", want: false},
+- {x: "C1", y: "C4", want: false},
+- {x: "C1", y: "C5", want: false},
+- {x: "C2", y: "C3", want: false},
+- {x: "C2", y: "C4", want: false},
+- {x: "C3", y: "C4", want: false},
+- {
+- x: "DAny",
+- y: "DString",
+- want: true,
+- wantParams: tmap{tparam("DAny", 0): stringType},
+- },
+- {x: "DString", y: "DInt", want: false}, // different instantiations of Named
+- {x: "E1", y: "E2", want: true}, // byte and rune are just aliases
+- {x: "E2", y: "E3", want: false},
+-
+- // The following tests cover all of the type param cases of unify.
+- {
+- // F1[*int] = F2[int], for example
+- // F1's T is bound to a pointer to F2's T.
+- x: "F1",
+- // F2's T is unbound: any instantiation works.
+- y: "F2",
+- want: true,
+- wantParams: tmap{tparam("F1", 0): types.NewPointer(tparam("F2", 0))},
+- },
+- {x: "F3", y: "F4", want: false}, // would require U identical to *U, prevented by occur check
+- {x: "F3", y: "F4a", want: false}, // occur check through Named[T]
+- {
+- x: "F5",
+- y: "F6",
+- want: true,
+- wantParams: tmap{
+- tparam("F5", 0): intType,
+- tparam("F5", 1): intType,
+- tparam("F6", 0): intType,
+- },
+- },
+- {x: "F6", y: "F7", want: false}, // both are bound
+- {
+- x: "F5",
+- y: "F6",
+- params: tmap{tparam("F6", 0): intType}, // consistent with the result
+- want: true,
+- wantParams: tmap{
+- tparam("F5", 0): intType,
+- tparam("F5", 1): intType,
+- tparam("F6", 0): intType,
+- },
+- },
+- {
+- x: "F5",
+- y: "F6",
+- params: tmap{tparam("F6", 0): boolType}, // not consistent
+- want: false,
+- },
+- {x: "F6", y: "F7", want: false}, // both are bound
+- {
+- // T=*V, U=int, V=int
+- x: "F5",
+- y: "F8",
+- want: true,
+- wantParams: tmap{
+- tparam("F5", 0): types.NewPointer(tparam("F8", 0)),
+- tparam("F5", 1): intType,
+- },
+- },
+- {
+- // T=*V, U=int, V=int
+- // Partial initial information is fine, as long as it's consistent.
+- x: "F5",
+- y: "F8",
+- want: true,
+- params: tmap{tparam("F5", 1): intType},
+- wantParams: tmap{
+- tparam("F5", 0): types.NewPointer(tparam("F8", 0)),
+- tparam("F5", 1): intType,
+- },
+- },
+- {
+- // T=*V, U=int, V=int
+- // Partial initial information is fine, as long as it's consistent.
+- x: "F5",
+- y: "F8",
+- want: true,
+- params: tmap{tparam("F5", 0): types.NewPointer(tparam("F8", 0))},
+- wantParams: tmap{
+- tparam("F5", 0): types.NewPointer(tparam("F8", 0)),
+- tparam("F5", 1): intType,
+- },
+- },
+- {x: "F5", y: "F9", want: false}, // T is unbound, V is bound, and T occurs in V
+- {
+- // T bound to Named[T']
+- x: "F1",
+- y: "DAny",
+- want: true,
+- wantParams: tmap{
+- tparam("F1", 0): scope.Lookup("DAny").(*types.Func).Signature().Params().At(0).Type()},
+- },
+- } {
+-
+- lookup := func(name string) types.Type {
+- obj := scope.Lookup(name)
+- if obj == nil {
+- t.Fatalf("Lookup %s failed", name)
+- }
+- if test.method != "" {
+- obj, _, _ = types.LookupFieldOrMethod(obj.Type(), true, pkg.Types, test.method)
+- if obj == nil {
+- t.Fatalf("Lookup %s.%s failed", name, test.method)
+- }
+- }
+- return obj.Type()
+- }
+-
+- check := func(a, b string, want, compareParams bool) {
+- t.Helper()
+-
+- ta := lookup(a)
+- tb := lookup(b)
+-
+- var gotParams tmap
+- if test.params == nil {
+- // Get the unifier even if there are no input params.
+- gotParams = tmap{}
+- } else {
+- gotParams = maps.Clone(test.params)
+- }
+- got := unify(ta, tb, gotParams)
+- if got != want {
+- t.Errorf("a=%s b=%s method=%s: unify returned %t for these inputs:\n- %s\n- %s",
+- a, b, test.method, got, ta, tb)
+- return
+- }
+- if !compareParams {
+- return
+- }
+- if !maps.EqualFunc(gotParams, test.wantParams, types.Identical) {
+- t.Errorf("x=%s y=%s method=%s: params: got %v, want %v",
+- a, b, test.method, gotParams, test.wantParams)
+- }
+- }
+-
+- check(test.x, test.y, test.want, true)
+- // unify is symmetric
+- check(test.y, test.x, test.want, true)
+- // unify is reflexive
+- check(test.x, test.x, true, false)
+- check(test.y, test.y, true, false)
+- }
+-}
+diff -urN a/gopls/internal/golang/inlay_hint.go b/gopls/internal/golang/inlay_hint.go
+--- a/gopls/internal/golang/inlay_hint.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/inlay_hint.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,419 +0,0 @@
+-// Copyright 2022 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "cmp"
+- "context"
+- "fmt"
+- "go/ast"
+- "go/constant"
+- "go/token"
+- "go/types"
+- "slices"
+- "strings"
+-
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/settings"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/typeparams"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pRng protocol.Range) ([]protocol.InlayHint, error) {
+- ctx, done := event.Start(ctx, "golang.InlayHint")
+- defer done()
+-
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+- if err != nil {
+- return nil, fmt.Errorf("getting file for InlayHint: %w", err)
+- }
+-
+- // Collect a list of the inlay hints that are enabled.
+- inlayHintOptions := snapshot.Options().InlayHintOptions
+- var enabledHints []inlayHintFunc
+- for hint, enabled := range inlayHintOptions.Hints {
+- if !enabled {
+- continue
+- }
+- if fn, ok := allInlayHints[hint]; ok {
+- enabledHints = append(enabledHints, fn)
+- }
+- }
+- if len(enabledHints) == 0 {
+- return nil, nil
+- }
+-
+- info := pkg.TypesInfo()
+- qual := typesinternal.FileQualifier(pgf.File, pkg.Types())
+-
+- // Set the range to the full file if the range is not valid.
+- start, end := pgf.File.FileStart, pgf.File.FileEnd
+-
+- // TODO(adonovan): this condition looks completely wrong!
+- if pRng.Start.Line < pRng.End.Line || pRng.Start.Character < pRng.End.Character {
+- // Adjust start and end for the specified range.
+- var err error
+- start, end, err = pgf.RangePos(pRng)
+- if err != nil {
+- return nil, err
+- }
+- }
+-
+- var hints []protocol.InlayHint
+- if curSubrange, ok := pgf.Cursor.FindByPos(start, end); ok {
+- add := func(hint protocol.InlayHint) { hints = append(hints, hint) }
+- for _, fn := range enabledHints {
+- fn(info, pgf, qual, curSubrange, add)
+- }
+- }
+- return hints, nil
+-}
+-
+-type inlayHintFunc func(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint))
+-
+-var allInlayHints = map[settings.InlayHint]inlayHintFunc{
+- settings.AssignVariableTypes: assignVariableTypes,
+- settings.ConstantValues: constantValues,
+- settings.ParameterNames: parameterNames,
+- settings.RangeVariableTypes: rangeVariableTypes,
+- settings.CompositeLiteralTypes: compositeLiteralTypes,
+- settings.CompositeLiteralFieldNames: compositeLiteralFields,
+- settings.FunctionTypeParameters: funcTypeParams,
+- settings.IgnoredError: ignoredError,
+-}
+-
+-func parameterNames(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for curCall := range cur.Preorder((*ast.CallExpr)(nil)) {
+- callExpr := curCall.Node().(*ast.CallExpr)
+- t := info.TypeOf(callExpr.Fun)
+- if t == nil {
+- continue
+- }
+- signature, ok := typeparams.CoreType(t).(*types.Signature)
+- if !ok {
+- continue
+- }
+-
+- for i, v := range callExpr.Args {
+- start, err := pgf.PosPosition(v.Pos())
+- if err != nil {
+- continue
+- }
+- params := signature.Params()
+- // When a function has variadic params, we skip args after
+- // params.Len().
+- if i > params.Len()-1 {
+- break
+- }
+- param := params.At(i)
+- // param.Name is empty for built-ins like append
+- if param.Name() == "" {
+- continue
+- }
+- // Skip the parameter name hint if the arg matches
+- // the parameter name.
+- if i, ok := v.(*ast.Ident); ok && i.Name == param.Name() {
+- continue
+- }
+-
+- label := param.Name()
+- if signature.Variadic() && i == params.Len()-1 {
+- label = label + "..."
+- }
+- add(protocol.InlayHint{
+- Position: start,
+- Label: labelPart(label + ":"),
+- Kind: protocol.Parameter,
+- PaddingRight: true,
+- })
+- }
+- }
+-}
+-
+-func ignoredError(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+-outer:
+- for curCall := range cur.Preorder((*ast.ExprStmt)(nil)) {
+- stmt := curCall.Node().(*ast.ExprStmt)
+- call, ok := stmt.X.(*ast.CallExpr)
+- if !ok {
+- continue // not a call stmt
+- }
+-
+- // Check that type of result (or last component) is error.
+- tv, ok := info.Types[call]
+- if !ok {
+- continue // no type info
+- }
+- t := tv.Type
+- if res, ok := t.(*types.Tuple); ok && res.Len() > 1 {
+- t = res.At(res.Len() - 1).Type()
+- }
+- if !types.Identical(t, errorType) {
+- continue
+- }
+-
+- // Suppress some common false positives.
+- obj := typeutil.Callee(info, call)
+- if typesinternal.IsFunctionNamed(obj, "fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln") ||
+- typesinternal.IsMethodNamed(obj, "bytes", "Buffer", "Write", "WriteByte", "WriteRune", "WriteString") ||
+- typesinternal.IsMethodNamed(obj, "strings", "Builder", "Write", "WriteByte", "WriteRune", "WriteString") ||
+- typesinternal.IsFunctionNamed(obj, "io", "WriteString") {
+- continue
+- }
+-
+- // Suppress if comment on same line contains "// ignore error".
+- line := func(pos token.Pos) int { return safetoken.Line(pgf.Tok, pos) }
+- comments := pgf.File.Comments
+- compare := func(cg *ast.CommentGroup, pos token.Pos) int {
+- return cmp.Compare(cg.Pos(), pos)
+- }
+- i, _ := slices.BinarySearchFunc(comments, stmt.End(), compare)
+- if i >= 0 && i < len(comments) {
+- cg := comments[i]
+- if line(cg.Pos()) == line(stmt.End()) && strings.Contains(cg.Text(), "ignore error") {
+- continue outer // suppress
+- }
+- }
+-
+- // Provide a hint.
+- pos, err := pgf.PosPosition(stmt.End())
+- if err != nil {
+- continue
+- }
+- add(protocol.InlayHint{
+- Position: pos,
+- Label: labelPart(" // ignore error"),
+- })
+- }
+-}
+-
+-func funcTypeParams(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for curCall := range cur.Preorder((*ast.CallExpr)(nil)) {
+- call := curCall.Node().(*ast.CallExpr)
+- id, ok := call.Fun.(*ast.Ident)
+- if !ok {
+- continue
+- }
+- inst := info.Instances[id]
+- if inst.TypeArgs == nil {
+- continue
+- }
+- start, err := pgf.PosPosition(id.End())
+- if err != nil {
+- continue
+- }
+- var args []string
+- for i := 0; i < inst.TypeArgs.Len(); i++ {
+- args = append(args, inst.TypeArgs.At(i).String())
+- }
+- if len(args) == 0 {
+- continue
+- }
+- add(protocol.InlayHint{
+- Position: start,
+- Label: labelPart("[" + strings.Join(args, ", ") + "]"),
+- Kind: protocol.Type,
+- })
+- }
+-}
+-
+-func assignVariableTypes(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for node := range cur.Preorder((*ast.AssignStmt)(nil), (*ast.ValueSpec)(nil)) {
+- switch n := node.Node().(type) {
+- case *ast.AssignStmt:
+- if n.Tok != token.DEFINE {
+- continue
+- }
+- for _, v := range n.Lhs {
+- variableType(info, pgf, qual, v, add)
+- }
+- case *ast.GenDecl:
+- if n.Tok != token.VAR {
+- continue
+- }
+- for _, v := range n.Specs {
+- spec := v.(*ast.ValueSpec)
+- // The type of the variable is written, skip showing type of this var.
+- // ```go
+- // var foo string
+- // ```
+- if spec.Type != nil {
+- continue
+- }
+-
+- for _, v := range spec.Names {
+- variableType(info, pgf, qual, v, add)
+- }
+- }
+- }
+- }
+-}
+-
+-func rangeVariableTypes(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for curRange := range cur.Preorder((*ast.RangeStmt)(nil)) {
+- rStmt := curRange.Node().(*ast.RangeStmt)
+- variableType(info, pgf, qual, rStmt.Key, add)
+- variableType(info, pgf, qual, rStmt.Value, add)
+- }
+-}
+-
+-func variableType(info *types.Info, pgf *parsego.File, qual types.Qualifier, e ast.Expr, add func(protocol.InlayHint)) {
+- typ := info.TypeOf(e)
+- if typ == nil {
+- return
+- }
+- end, err := pgf.PosPosition(e.End())
+- if err != nil {
+- return
+- }
+- add(protocol.InlayHint{
+- Position: end,
+- Label: labelPart(types.TypeString(typ, qual)),
+- Kind: protocol.Type,
+- PaddingLeft: true,
+- })
+-}
+-
+-func constantValues(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for curDecl := range cur.Preorder((*ast.GenDecl)(nil)) {
+- genDecl := curDecl.Node().(*ast.GenDecl)
+- if genDecl.Tok != token.CONST {
+- continue
+- }
+-
+- for _, v := range genDecl.Specs {
+- spec, ok := v.(*ast.ValueSpec)
+- if !ok {
+- continue
+- }
+- end, err := pgf.PosPosition(v.End())
+- if err != nil {
+- continue
+- }
+- // Show hints when values are missing or at least one value is not
+- // a basic literal.
+- showHints := len(spec.Values) == 0
+- checkValues := len(spec.Names) == len(spec.Values)
+- var values []string
+- for i, w := range spec.Names {
+- obj, ok := info.ObjectOf(w).(*types.Const)
+- if !ok || obj.Val().Kind() == constant.Unknown {
+- continue
+- }
+- if checkValues {
+- switch spec.Values[i].(type) {
+- case *ast.BadExpr:
+- continue
+- case *ast.BasicLit:
+- default:
+- if obj.Val().Kind() != constant.Bool {
+- showHints = true
+- }
+- }
+- }
+- values = append(values, fmt.Sprintf("%v", obj.Val()))
+- }
+- if !showHints || len(values) == 0 {
+- continue
+- }
+- add(protocol.InlayHint{
+- Position: end,
+- Label: labelPart("= " + strings.Join(values, ", ")),
+- PaddingLeft: true,
+- })
+- }
+- }
+-}
+-
+-func compositeLiteralFields(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for curCompLit := range cur.Preorder((*ast.CompositeLit)(nil)) {
+- compLit, ok := curCompLit.Node().(*ast.CompositeLit)
+- if !ok {
+- continue
+- }
+- typ := info.TypeOf(compLit)
+- if typ == nil {
+- continue
+- }
+- typ = typesinternal.Unpointer(typ)
+- strct, ok := typeparams.CoreType(typ).(*types.Struct)
+- if !ok {
+- continue
+- }
+-
+- var hints []protocol.InlayHint
+- var allEdits []protocol.TextEdit
+- for i, v := range compLit.Elts {
+- if _, ok := v.(*ast.KeyValueExpr); !ok {
+- start, err := pgf.PosPosition(v.Pos())
+- if err != nil {
+- continue
+- }
+- if i > strct.NumFields()-1 {
+- break
+- }
+- hints = append(hints, protocol.InlayHint{
+- Position: start,
+- Label: labelPart(strct.Field(i).Name() + ":"),
+- Kind: protocol.Parameter,
+- PaddingRight: true,
+- })
+- allEdits = append(allEdits, protocol.TextEdit{
+- Range: protocol.Range{Start: start, End: start},
+- NewText: strct.Field(i).Name() + ": ",
+- })
+- }
+- }
+- // It is not allowed to have a mix of keyed and unkeyed fields, so
+- // have the text edits add keys to all fields.
+- for i := range hints {
+- hints[i].TextEdits = allEdits
+- add(hints[i])
+- }
+- }
+-}
+-
+-func compositeLiteralTypes(info *types.Info, pgf *parsego.File, qual types.Qualifier, cur inspector.Cursor, add func(protocol.InlayHint)) {
+- for curCompLit := range cur.Preorder((*ast.CompositeLit)(nil)) {
+- compLit := curCompLit.Node().(*ast.CompositeLit)
+- typ := info.TypeOf(compLit)
+- if typ == nil {
+- continue
+- }
+- if compLit.Type != nil {
+- continue
+- }
+- prefix := ""
+- if t, ok := typeparams.CoreType(typ).(*types.Pointer); ok {
+- typ = t.Elem()
+- prefix = "&"
+- }
+- // The type for this composite literal is implicit, add an inlay hint.
+- start, err := pgf.PosPosition(compLit.Lbrace)
+- if err != nil {
+- continue
+- }
+- add(protocol.InlayHint{
+- Position: start,
+- Label: labelPart(fmt.Sprintf("%s%s", prefix, types.TypeString(typ, qual))),
+- Kind: protocol.Type,
+- })
+- }
+-}
+-
+-func labelPart(s string) []protocol.InlayHintLabelPart {
+- const maxLabelLength = 28
+- if len(s) > maxLabelLength+len("...") {
+- s = s[:maxLabelLength] + "..."
+- }
+- return []protocol.InlayHintLabelPart{{Value: s}}
+-}
+diff -urN a/gopls/internal/golang/inline_all.go b/gopls/internal/golang/inline_all.go
+--- a/gopls/internal/golang/inline_all.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/inline_all.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,318 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "fmt"
+- "go/ast"
+- "go/parser"
+- "go/types"
+-
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/bug"
+- "golang.org/x/tools/internal/refactor/inline"
+-)
+-
+-// inlineAllCalls inlines all calls to the original function declaration
+-// described by callee, returning the resulting modified file content.
+-//
+-// inlining everything is currently an expensive operation: it involves re-type
+-// checking every package that contains a potential call, as reported by
+-// References. In cases where there are multiple calls per file, inlineAllCalls
+-// must type check repeatedly for each additional call.
+-//
+-// The provided post processing function is applied to the resulting source
+-// after each transformation. This is necessary because we are using this
+-// function to inline synthetic wrappers for the purpose of signature
+-// rewriting. The delegated function has a fake name that doesn't exist in the
+-// snapshot, and so we can't re-type check until we replace this fake name.
+-//
+-// TODO(rfindley): this only works because removing a parameter is a very
+-// narrow operation. A better solution would be to allow for ad-hoc snapshots
+-// that expose the full machinery of real snapshots: minimal invalidation,
+-// batched type checking, etc. Then we could actually rewrite the declaring
+-// package in this snapshot (and so 'post' would not be necessary), and could
+-// robustly re-type check for the purpose of iterative inlining, even if the
+-// inlined code pulls in new imports that weren't present in export data.
+-//
+-// The code below notes where are assumptions are made that only hold true in
+-// the case of parameter removal (annotated with 'Assumption:')
+-func inlineAllCalls(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, origDecl *ast.FuncDecl, callee *inline.Callee, post func([]byte) []byte, opts *inline.Options) (_ map[protocol.DocumentURI][]byte, inlineErr error) {
+- // Collect references.
+- var refs []protocol.Location
+- {
+- funcPos, err := pgf.Mapper.PosPosition(pgf.Tok, origDecl.Name.NamePos)
+- if err != nil {
+- return nil, err
+- }
+- fh, err := snapshot.ReadFile(ctx, pgf.URI)
+- if err != nil {
+- return nil, err
+- }
+- refs, err = References(ctx, snapshot, fh, funcPos, false)
+- if err != nil {
+- return nil, fmt.Errorf("finding references to rewrite: %v", err)
+- }
+- }
+-
+- // Type-check the narrowest package containing each reference.
+- // TODO(rfindley): we should expose forEachPackage in order to operate in
+- // parallel and to reduce peak memory for this operation.
+- var (
+- pkgForRef = make(map[protocol.Location]PackageID)
+- pkgs = make(map[PackageID]*cache.Package)
+- // The inliner assumes that input is well-typed, but that is frequently not
+- // the case within gopls.
+- // Until we're able to harden the inliner, report panics as errors to avoid
+- // crashing the server.
+- badPkg = false
+- )
+- {
+- needPkgs := make(map[PackageID]struct{})
+- for _, ref := range refs {
+- md, err := snapshot.NarrowestMetadataForFile(ctx, ref.URI)
+- if err != nil {
+- return nil, fmt.Errorf("finding ref metadata: %v", err)
+- }
+- pkgForRef[ref] = md.ID
+- needPkgs[md.ID] = struct{}{}
+- }
+- var pkgIDs []PackageID
+- for id := range needPkgs { // TODO: use maps.Keys once it is available to us
+- pkgIDs = append(pkgIDs, id)
+- }
+-
+- refPkgs, err := snapshot.TypeCheck(ctx, pkgIDs...)
+- if err != nil {
+- return nil, fmt.Errorf("type checking reference packages: %v", err)
+- }
+-
+- for _, p := range refPkgs {
+- pkgs[p.Metadata().ID] = p
+- if len(p.ParseErrors())+len(p.TypeErrors()) > 0 {
+- badPkg = true
+- }
+- }
+- }
+-
+- if badPkg {
+- defer func() {
+- if x := recover(); x != nil {
+- inlineErr = fmt.Errorf("inlining failed (%q), likely because inputs were ill-typed", x)
+- }
+- }()
+- }
+-
+- // Organize calls by top file declaration. Calls within a single file may
+- // affect each other, as the inlining edit may affect the surrounding scope
+- // or imports Therefore, when inlining subsequent calls in the same
+- // declaration, we must re-type check.
+-
+- type fileCalls struct {
+- pkg *cache.Package
+- pgf *parsego.File
+- calls []*ast.CallExpr
+- }
+-
+- refsByFile := make(map[protocol.DocumentURI]*fileCalls)
+- for _, ref := range refs {
+- refpkg := pkgs[pkgForRef[ref]]
+- pgf, err := refpkg.File(ref.URI)
+- if err != nil {
+- return nil, bug.Errorf("finding %s in %s: %v", ref.URI, refpkg.Metadata().ID, err)
+- }
+-
+- start, end, err := pgf.RangePos(ref.Range)
+- if err != nil {
+- return nil, err // e.g. invalid range
+- }
+-
+- // Look for the surrounding call expression.
+- var (
+- name *ast.Ident
+- call *ast.CallExpr
+- )
+- path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
+- name, _ = path[0].(*ast.Ident)
+-
+- // TODO(rfindley): handle method expressions correctly.
+- if _, ok := path[1].(*ast.SelectorExpr); ok {
+- call, _ = path[2].(*ast.CallExpr)
+- } else {
+- call, _ = path[1].(*ast.CallExpr)
+- }
+- if name == nil || call == nil {
+- // TODO(rfindley): handle this case with eta-abstraction:
+- // a reference to the target function f in a non-call position
+- // use(f)
+- // is replaced by
+- // use(func(...) { f(...) })
+- return nil, fmt.Errorf("cannot inline: found non-call function reference %v", ref)
+- }
+-
+- // Heuristic: ignore references that overlap with type checker errors, as they may
+- // lead to invalid results (see golang/go#70268).
+- hasTypeErrors := false
+- for _, typeErr := range refpkg.TypeErrors() {
+- if call.Lparen <= typeErr.Pos && typeErr.Pos <= call.Rparen {
+- hasTypeErrors = true
+- }
+- }
+-
+- if hasTypeErrors {
+- continue
+- }
+-
+- if typeutil.StaticCallee(refpkg.TypesInfo(), call) == nil {
+- continue // dynamic call
+- }
+-
+- // Sanity check.
+- if obj := refpkg.TypesInfo().ObjectOf(name); obj == nil ||
+- obj.Name() != origDecl.Name.Name ||
+- obj.Pkg() == nil ||
+- obj.Pkg().Path() != string(pkg.Metadata().PkgPath) {
+-
+- return nil, bug.Errorf("cannot inline: corrupted reference %v", ref)
+- }
+-
+- callInfo, ok := refsByFile[ref.URI]
+- if !ok {
+- callInfo = &fileCalls{
+- pkg: refpkg,
+- pgf: pgf,
+- }
+- refsByFile[ref.URI] = callInfo
+- }
+- callInfo.calls = append(callInfo.calls, call)
+- }
+-
+- // Inline each call within the same decl in sequence, re-typechecking after
+- // each one. If there is only a single call within the decl, we can avoid
+- // additional type checking.
+- //
+- // Assumption: inlining does not affect the package scope, so we can operate
+- // on separate files independently.
+- result := make(map[protocol.DocumentURI][]byte)
+- for uri, callInfo := range refsByFile {
+- var (
+- calls = callInfo.calls
+- fset = callInfo.pkg.FileSet()
+- tpkg = callInfo.pkg.Types()
+- tinfo = callInfo.pkg.TypesInfo()
+- file = callInfo.pgf.File
+- content = callInfo.pgf.Src
+- )
+-
+- // Check for overlapping calls (such as Foo(Foo())). We can't handle these
+- // because inlining may change the source order of the inner call with
+- // respect to the inlined outer call, and so the heuristic we use to find
+- // the next call (counting from top-to-bottom) does not work.
+- for i := range calls {
+- if i > 0 && calls[i-1].End() > calls[i].Pos() {
+- return nil, fmt.Errorf("%s: can't inline overlapping call %s", uri, types.ExprString(calls[i-1]))
+- }
+- }
+-
+- currentCall := 0
+- for currentCall < len(calls) {
+- caller := &inline.Caller{
+- Fset: fset,
+- Types: tpkg,
+- Info: tinfo,
+- File: file,
+- Call: calls[currentCall],
+- Content: content,
+- }
+- res, err := inline.Inline(caller, callee, opts)
+- if err != nil {
+- return nil, fmt.Errorf("inlining failed: %v", err)
+- }
+- content = res.Content
+- if post != nil {
+- content = post(content)
+- }
+- if len(calls) <= 1 {
+- // No need to re-type check, as we've inlined all calls.
+- break
+- }
+-
+- // TODO(rfindley): develop a theory of "trivial" inlining, which are
+- // inlinings that don't require re-type checking.
+- //
+- // In principle, if the inlining only involves replacing one call with
+- // another, the scope of the caller is unchanged and there is no need to
+- // type check again before inlining subsequent calls (edits should not
+- // overlap, and should not affect each other semantically). However, it
+- // feels sufficiently complicated that, to be safe, this optimization is
+- // deferred until later.
+-
+- file, err = parser.ParseFile(fset, uri.Path(), content, parser.ParseComments|parser.SkipObjectResolution)
+- if err != nil {
+- return nil, bug.Errorf("inlined file failed to parse: %v", err)
+- }
+-
+- // After inlining one call with a removed parameter, the package will
+- // fail to type check due to "not enough arguments". Therefore, we must
+- // allow type errors here.
+- //
+- // Assumption: the resulting type errors do not affect the correctness of
+- // subsequent inlining, because invalid arguments to a call do not affect
+- // anything in the surrounding scope.
+- //
+- // TODO(rfindley): improve this.
+- logf := func(string, ...any) {}
+- if opts != nil {
+- logf = opts.Logf
+- }
+- tpkg, tinfo, err = reTypeCheck(logf, callInfo.pkg, map[protocol.DocumentURI]*ast.File{uri: file}, true)
+- if err != nil {
+- return nil, bug.Errorf("type checking after inlining failed: %v", err)
+- }
+-
+- // Collect calls to the target function in the modified declaration.
+- var calls2 []*ast.CallExpr
+- ast.Inspect(file, func(n ast.Node) bool {
+- if call, ok := n.(*ast.CallExpr); ok {
+- fn := typeutil.StaticCallee(tinfo, call)
+- if fn != nil && fn.Pkg().Path() == string(pkg.Metadata().PkgPath) && fn.Name() == origDecl.Name.Name {
+- calls2 = append(calls2, call)
+- }
+- }
+- return true
+- })
+-
+- // If the number of calls has increased, this process will never cease.
+- // If the number of calls has decreased, assume that inlining removed a
+- // call.
+- // If the number of calls didn't change, assume that inlining replaced
+- // a call, and move on to the next.
+- //
+- // Assumption: we're inlining a call that has at most one recursive
+- // reference (which holds for signature rewrites).
+- //
+- // TODO(rfindley): this isn't good enough. We should be able to support
+- // inlining all existing calls even if they increase calls. How do we
+- // correlate the before and after syntax?
+- switch {
+- case len(calls2) > len(calls):
+- return nil, fmt.Errorf("inlining increased calls %d->%d, possible recursive call? content:\n%s", len(calls), len(calls2), content)
+- case len(calls2) < len(calls):
+- calls = calls2
+- case len(calls2) == len(calls):
+- calls = calls2
+- currentCall++
+- }
+- }
+-
+- result[callInfo.pgf.URI] = content
+- }
+- return result, nil
+-}
+diff -urN a/gopls/internal/golang/inline.go b/gopls/internal/golang/inline.go
+--- a/gopls/internal/golang/inline.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/inline.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,264 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file defines the refactor.inline code action.
+-
+-import (
+- "context"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+-
+- "golang.org/x/tools/go/analysis"
+- goastutil "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/go/ast/edge"
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/astutil"
+- internalastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/diff"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/refactor/inline"
+-)
+-
+-// enclosingStaticCall returns the innermost function call enclosing
+-// the selected range, along with the callee.
+-func enclosingStaticCall(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*ast.CallExpr, *types.Func, error) {
+- // TODO(adonovan): simplify using pgf.Cursor
+- path, _ := goastutil.PathEnclosingInterval(pgf.File, start, end)
+-
+- var call *ast.CallExpr
+-loop:
+- for _, n := range path {
+- switch n := n.(type) {
+- case *ast.FuncLit:
+- break loop
+- case *ast.CallExpr:
+- call = n
+- break loop
+- }
+- }
+- if call == nil {
+- return nil, nil, fmt.Errorf("no enclosing call")
+- }
+- if safetoken.Line(pgf.Tok, call.Lparen) != safetoken.Line(pgf.Tok, start) {
+- return nil, nil, fmt.Errorf("enclosing call is not on this line")
+- }
+- fn := typeutil.StaticCallee(pkg.TypesInfo(), call)
+- if fn == nil {
+- return nil, nil, fmt.Errorf("not a static call to a Go function")
+- }
+- return call, fn, nil
+-}
+-
+-func inlineCall(ctx context.Context, snapshot *cache.Snapshot, callerPkg *cache.Package, callerPGF *parsego.File, start, end token.Pos) (_ *token.FileSet, _ *analysis.SuggestedFix, err error) {
+- countInlineCall.Inc()
+- // Find enclosing static call.
+- call, fn, err := enclosingStaticCall(callerPkg, callerPGF, start, end)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- // Locate callee by file/line and analyze it.
+- calleePosn := safetoken.StartPosition(callerPkg.FileSet(), fn.Pos())
+- calleePkg, calleePGF, err := NarrowestPackageForFile(ctx, snapshot, protocol.URIFromPath(calleePosn.Filename))
+- if err != nil {
+- return nil, nil, err
+- }
+- var calleeDecl *ast.FuncDecl
+- for _, decl := range calleePGF.File.Decls {
+- if decl, ok := decl.(*ast.FuncDecl); ok {
+- posn := safetoken.StartPosition(calleePkg.FileSet(), decl.Name.Pos())
+- if posn.Line == calleePosn.Line && posn.Column == calleePosn.Column {
+- calleeDecl = decl
+- break
+- }
+- }
+- }
+- if calleeDecl == nil {
+- return nil, nil, fmt.Errorf("can't find callee")
+- }
+-
+- // The inliner assumes that input is well-typed,
+- // but that is frequently not the case within gopls.
+- // Until we are able to harden the inliner,
+- // report panics as errors to avoid crashing the server.
+- bad := func(p *cache.Package) bool { return len(p.ParseErrors())+len(p.TypeErrors()) > 0 }
+- if bad(calleePkg) || bad(callerPkg) {
+- defer func() {
+- if x := recover(); x != nil {
+- err = fmt.Errorf("inlining failed (%q), likely because inputs were ill-typed", x)
+- }
+- }()
+- }
+-
+- // Users can consult the gopls event log to see
+- // why a particular inlining strategy was chosen.
+- logf := logger(ctx, "inliner", snapshot.Options().VerboseOutput)
+-
+- callee, err := inline.AnalyzeCallee(logf, calleePkg.FileSet(), calleePkg.Types(), calleePkg.TypesInfo(), calleeDecl, calleePGF.Src)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- // Inline the call.
+- caller := &inline.Caller{
+- Fset: callerPkg.FileSet(),
+- Types: callerPkg.Types(),
+- Info: callerPkg.TypesInfo(),
+- File: callerPGF.File,
+- Call: call,
+- Content: callerPGF.Src,
+- }
+-
+- res, err := inline.Inline(caller, callee, &inline.Options{Logf: logf})
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- return callerPkg.FileSet(), &analysis.SuggestedFix{
+- Message: fmt.Sprintf("inline call of %v", callee),
+- TextEdits: diffToTextEdits(callerPGF.Tok, diff.Bytes(callerPGF.Src, res.Content)),
+- }, nil
+-}
+-
+-// TODO(adonovan): change the inliner to instead accept an io.Writer.
+-func logger(ctx context.Context, name string, verbose bool) func(format string, args ...any) {
+- if verbose {
+- return func(format string, args ...any) {
+- event.Log(ctx, name+": "+fmt.Sprintf(format, args...))
+- }
+- } else {
+- return func(string, ...any) {}
+- }
+-}
+-
+-// canInlineVariable reports whether the selection is within an
+-// identifier that is a use of a variable that has an initializer
+-// expression. If so, it returns cursors for the identifier and the
+-// initializer expression.
+-func canInlineVariable(info *types.Info, curFile inspector.Cursor, start, end token.Pos) (_, _ inspector.Cursor, ok bool) {
+- if curUse, ok := curFile.FindByPos(start, end); ok {
+- if id, ok := curUse.Node().(*ast.Ident); ok && !isLvalueUse(curUse, info) {
+- if v, ok := info.Uses[id].(*types.Var); ok && v.Kind() == types.LocalVar {
+- if curIdent, ok := curFile.FindByPos(v.Pos(), v.Pos()); ok {
+- curParent := curIdent.Parent()
+- kind, index := curIdent.ParentEdge()
+- switch kind {
+- case edge.ValueSpec_Names:
+- // var v = expr
+- spec := curParent.Node().(*ast.ValueSpec)
+- if len(spec.Names) == len(spec.Values) {
+- return curUse, curParent.ChildAt(edge.ValueSpec_Values, index), true
+- }
+- case edge.AssignStmt_Lhs:
+- // v := expr
+- stmt := curParent.Node().(*ast.AssignStmt)
+- if len(stmt.Lhs) == len(stmt.Rhs) {
+- return curUse, curParent.ChildAt(edge.AssignStmt_Rhs, index), true
+- }
+- }
+- }
+- }
+- }
+- }
+- return
+-}
+-
+-// isLvalueUse reports whether the "use" identifier represented by cur
+-// appears in an l-value context such as:
+-//
+-// - v=...
+-// - v++
+-// - &v
+-// - v.f(), when this implicitly takes the address of v.
+-func isLvalueUse(cur inspector.Cursor, info *types.Info) bool {
+- cur = unparenEnclosing(cur)
+-
+- kind, _ := cur.ParentEdge()
+- switch kind {
+- case edge.AssignStmt_Lhs, edge.IncDecStmt_X:
+- return true // v=..., v++
+-
+- case edge.UnaryExpr_X:
+- return cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND // &v
+-
+- case edge.SelectorExpr_X:
+- sel := cur.Parent().Node().(*ast.SelectorExpr)
+- isPointer := func(t types.Type) bool {
+- return is[*types.Pointer](t)
+- }
+- if seln, ok := info.Selections[sel]; ok && seln.Kind() == types.MethodVal {
+- // Have: recv.f() method call
+- methodRecv := seln.Obj().(*types.Func).Signature().Recv().Type()
+- return !seln.Indirect() && isPointer(methodRecv) && !isPointer(info.TypeOf(sel.X))
+- }
+- }
+-
+- return false
+-}
+-
+-// unparenEnclosing removes enclosing parens from cur in
+-// preparation for a call to [Cursor.ParentEdge].
+-func unparenEnclosing(cur inspector.Cursor) inspector.Cursor {
+- for astutil.IsChildOf(cur, edge.ParenExpr_X) {
+- cur = cur.Parent()
+- }
+- return cur
+-}
+-
+-// inlineVariableOne computes a fix to replace the selected variable by
+-// its initialization expression.
+-func inlineVariableOne(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
+- countInlineVariable.Inc()
+- info := pkg.TypesInfo()
+- curUse, curRHS, ok := canInlineVariable(info, pgf.Cursor, start, end)
+- if !ok {
+- return nil, nil, fmt.Errorf("cannot inline variable here")
+- }
+- use := curUse.Node().(*ast.Ident)
+-
+- // Check that free symbols of rhs are unshadowed at curUse.
+- var (
+- pos = use.Pos()
+- scope = info.Scopes[pgf.File].Innermost(pos)
+- )
+- for curIdent := range curRHS.Preorder((*ast.Ident)(nil)) {
+- if astutil.IsChildOf(curIdent, edge.SelectorExpr_Sel) {
+- continue // ignore f in x.f
+- }
+- id := curIdent.Node().(*ast.Ident)
+- obj1 := info.Uses[id]
+- if obj1 == nil {
+- continue // undefined; or a def, not a use
+- }
+- if internalastutil.NodeContains(curRHS.Node(), obj1.Pos()) {
+- continue // not free (id is defined within RHS)
+- }
+- _, obj2 := scope.LookupParent(id.Name, pos)
+- if obj1 != obj2 {
+- return nil, nil, fmt.Errorf("cannot inline variable: its initializer expression refers to %q, which is shadowed by the declaration at line %d", id.Name, safetoken.Position(pgf.Tok, obj2.Pos()).Line)
+- }
+- }
+-
+- // TODO(adonovan): also reject variables that are updated by assignments?
+-
+- return pkg.FileSet(), &analysis.SuggestedFix{
+- Message: fmt.Sprintf("Replace variable %q by its initializer expression", use.Name),
+- TextEdits: []analysis.TextEdit{
+- {
+- Pos: use.Pos(),
+- End: use.End(),
+- NewText: []byte(FormatNode(pkg.FileSet(), curRHS.Node())),
+- },
+- },
+- }, nil
+-}
+diff -urN a/gopls/internal/golang/invertifcondition.go b/gopls/internal/golang/invertifcondition.go
+--- a/gopls/internal/golang/invertifcondition.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/invertifcondition.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,266 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "fmt"
+- "go/ast"
+- "go/token"
+- "strings"
+-
+- "golang.org/x/tools/go/analysis"
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/util/cursorutil"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+-)
+-
+-// invertIfCondition is a singleFileFixFunc that inverts an if/else statement
+-func invertIfCondition(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
+- var (
+- fset = pkg.FileSet()
+- src = pgf.Src
+- )
+-
+- ifStatement, _, err := canInvertIfCondition(pgf.Cursor, start, end)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- var replaceElse analysis.TextEdit
+-
+- endsWithReturn, err := endsWithReturn(ifStatement.Else)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- if endsWithReturn {
+- // Replace the whole else part with an empty line and an unindented
+- // version of the original if body
+- sourcePos := safetoken.StartPosition(fset, ifStatement.Pos())
+-
+- indent := max(sourcePos.Column-1, 0)
+-
+- standaloneBodyText := ifBodyToStandaloneCode(fset, ifStatement.Body, src)
+- replaceElse = analysis.TextEdit{
+- Pos: ifStatement.Body.Rbrace + 1, // 1 == len("}")
+- End: ifStatement.End(),
+- NewText: []byte("\n\n" + strings.Repeat("\t", indent) + standaloneBodyText),
+- }
+- } else {
+- // Replace the else body text with the if body text
+- bodyStart := safetoken.StartPosition(fset, ifStatement.Body.Lbrace)
+- bodyEnd := safetoken.EndPosition(fset, ifStatement.Body.Rbrace+1) // 1 == len("}")
+- bodyText := src[bodyStart.Offset:bodyEnd.Offset]
+- replaceElse = analysis.TextEdit{
+- Pos: ifStatement.Else.Pos(),
+- End: ifStatement.Else.End(),
+- NewText: bodyText,
+- }
+- }
+-
+- // Replace the if text with the else text
+- elsePosInSource := safetoken.StartPosition(fset, ifStatement.Else.Pos())
+- elseEndInSource := safetoken.EndPosition(fset, ifStatement.Else.End())
+- elseText := src[elsePosInSource.Offset:elseEndInSource.Offset]
+- replaceBodyWithElse := analysis.TextEdit{
+- Pos: ifStatement.Body.Pos(),
+- End: ifStatement.Body.End(),
+- NewText: elseText,
+- }
+-
+- // Replace the if condition with its inverse
+- inverseCondition, err := invertCondition(fset, ifStatement.Cond, src)
+- if err != nil {
+- return nil, nil, err
+- }
+- replaceConditionWithInverse := analysis.TextEdit{
+- Pos: ifStatement.Cond.Pos(),
+- End: ifStatement.Cond.End(),
+- NewText: inverseCondition,
+- }
+-
+- // Return a SuggestedFix with just that TextEdit in there
+- return fset, &analysis.SuggestedFix{
+- TextEdits: []analysis.TextEdit{
+- replaceConditionWithInverse,
+- replaceBodyWithElse,
+- replaceElse,
+- },
+- }, nil
+-}
+-
+-func endsWithReturn(elseBranch ast.Stmt) (bool, error) {
+- elseBlock, isBlockStatement := elseBranch.(*ast.BlockStmt)
+- if !isBlockStatement {
+- return false, fmt.Errorf("unable to figure out whether this ends with return: %T", elseBranch)
+- }
+-
+- if len(elseBlock.List) == 0 {
+- // Empty blocks don't end in returns
+- return false, nil
+- }
+-
+- lastStatement := elseBlock.List[len(elseBlock.List)-1]
+-
+- _, lastStatementIsReturn := lastStatement.(*ast.ReturnStmt)
+- return lastStatementIsReturn, nil
+-}
+-
+-// Turn { fmt.Println("Hello") } into just fmt.Println("Hello"), with one less
+-// level of indentation.
+-//
+-// The first line of the result will not be indented, but all of the following
+-// lines will.
+-func ifBodyToStandaloneCode(fset *token.FileSet, ifBody *ast.BlockStmt, src []byte) string {
+- // Get the whole body (without the surrounding braces) as a string
+- bodyStart := safetoken.StartPosition(fset, ifBody.Lbrace+1) // 1 == len("}")
+- bodyEnd := safetoken.EndPosition(fset, ifBody.Rbrace)
+- bodyWithoutBraces := string(src[bodyStart.Offset:bodyEnd.Offset])
+- bodyWithoutBraces = strings.TrimSpace(bodyWithoutBraces)
+-
+- // Unindent
+- bodyWithoutBraces = strings.ReplaceAll(bodyWithoutBraces, "\n\t", "\n")
+-
+- return bodyWithoutBraces
+-}
+-
+-func invertCondition(fset *token.FileSet, cond ast.Expr, src []byte) ([]byte, error) {
+- condStart := safetoken.StartPosition(fset, cond.Pos())
+- condEnd := safetoken.EndPosition(fset, cond.End())
+- oldText := string(src[condStart.Offset:condEnd.Offset])
+-
+- switch expr := cond.(type) {
+- case *ast.Ident, *ast.ParenExpr, *ast.CallExpr, *ast.StarExpr, *ast.IndexExpr, *ast.IndexListExpr, *ast.SelectorExpr:
+- newText := "!" + oldText
+- if oldText == "true" {
+- newText = "false"
+- } else if oldText == "false" {
+- newText = "true"
+- }
+-
+- return []byte(newText), nil
+-
+- case *ast.UnaryExpr:
+- if expr.Op != token.NOT {
+- // This should never happen
+- return dumbInvert(fset, cond, src), nil
+- }
+-
+- inverse := expr.X
+- if p, isParen := inverse.(*ast.ParenExpr); isParen {
+- // We got !(x), remove the parentheses with the ! so we get just "x"
+- inverse = p.X
+-
+- start := safetoken.StartPosition(fset, inverse.Pos())
+- end := safetoken.EndPosition(fset, inverse.End())
+- if start.Line != end.Line {
+- // The expression is multi-line, so we can't remove the parentheses
+- inverse = expr.X
+- }
+- }
+-
+- start := safetoken.StartPosition(fset, inverse.Pos())
+- end := safetoken.EndPosition(fset, inverse.End())
+- textWithoutNot := src[start.Offset:end.Offset]
+-
+- return textWithoutNot, nil
+-
+- case *ast.BinaryExpr:
+- // These inversions are unsound for floating point NaN, but that's ok.
+- negations := map[token.Token]string{
+- token.EQL: "!=",
+- token.LSS: ">=",
+- token.GTR: "<=",
+- token.NEQ: "==",
+- token.LEQ: ">",
+- token.GEQ: "<",
+- }
+-
+- negation, negationFound := negations[expr.Op]
+- if !negationFound {
+- return invertAndOr(fset, expr, src)
+- }
+-
+- xPosInSource := safetoken.StartPosition(fset, expr.X.Pos())
+- opPosInSource := safetoken.StartPosition(fset, expr.OpPos)
+- yPosInSource := safetoken.StartPosition(fset, expr.Y.Pos())
+-
+- textBeforeOp := string(src[xPosInSource.Offset:opPosInSource.Offset])
+-
+- oldOpWithTrailingWhitespace := string(src[opPosInSource.Offset:yPosInSource.Offset])
+- newOpWithTrailingWhitespace := negation + oldOpWithTrailingWhitespace[len(expr.Op.String()):]
+-
+- textAfterOp := string(src[yPosInSource.Offset:condEnd.Offset])
+-
+- return []byte(textBeforeOp + newOpWithTrailingWhitespace + textAfterOp), nil
+- }
+-
+- return dumbInvert(fset, cond, src), nil
+-}
+-
+-// dumbInvert is a fallback, inverting cond into !(cond).
+-func dumbInvert(fset *token.FileSet, expr ast.Expr, src []byte) []byte {
+- start := safetoken.StartPosition(fset, expr.Pos())
+- end := safetoken.EndPosition(fset, expr.End())
+- text := string(src[start.Offset:end.Offset])
+- return []byte("!(" + text + ")")
+-}
+-
+-func invertAndOr(fset *token.FileSet, expr *ast.BinaryExpr, src []byte) ([]byte, error) {
+- if expr.Op != token.LAND && expr.Op != token.LOR {
+- // Neither AND nor OR, don't know how to invert this
+- return dumbInvert(fset, expr, src), nil
+- }
+-
+- oppositeOp := "&&"
+- if expr.Op == token.LAND {
+- oppositeOp = "||"
+- }
+-
+- xEndInSource := safetoken.EndPosition(fset, expr.X.End())
+- opPosInSource := safetoken.StartPosition(fset, expr.OpPos)
+- whitespaceAfterBefore := src[xEndInSource.Offset:opPosInSource.Offset]
+-
+- invertedBefore, err := invertCondition(fset, expr.X, src)
+- if err != nil {
+- return nil, err
+- }
+-
+- invertedAfter, err := invertCondition(fset, expr.Y, src)
+- if err != nil {
+- return nil, err
+- }
+-
+- yPosInSource := safetoken.StartPosition(fset, expr.Y.Pos())
+-
+- oldOpWithTrailingWhitespace := string(src[opPosInSource.Offset:yPosInSource.Offset])
+- newOpWithTrailingWhitespace := oppositeOp + oldOpWithTrailingWhitespace[len(expr.Op.String()):]
+-
+- return []byte(string(invertedBefore) + string(whitespaceAfterBefore) + newOpWithTrailingWhitespace + string(invertedAfter)), nil
+-}
+-
+-// canInvertIfCondition reports whether we can do invert-if-condition on the
+-// code in the given range.
+-func canInvertIfCondition(curFile inspector.Cursor, start, end token.Pos) (*ast.IfStmt, bool, error) {
+- curIf, _ := curFile.FindByPos(start, end)
+- stmt, _ := cursorutil.FirstEnclosing[*ast.IfStmt](curIf)
+- if stmt == nil {
+- return nil, false, fmt.Errorf("not an if statement")
+- }
+- if stmt.Else == nil {
+- // Can't invert conditions without else clauses
+- return nil, false, fmt.Errorf("else clause required")
+- }
+-
+- if _, hasElseIf := stmt.Else.(*ast.IfStmt); hasElseIf {
+- // Can't invert conditions with else-if clauses, unclear what that
+- // would look like
+- return nil, false, fmt.Errorf("else-if not supported")
+- }
+-
+- return stmt, true, nil
+-}
+diff -urN a/gopls/internal/golang/known_packages.go b/gopls/internal/golang/known_packages.go
+--- a/gopls/internal/golang/known_packages.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/known_packages.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,137 +0,0 @@
+-// Copyright 2020 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "go/parser"
+- "go/token"
+- "sort"
+- "strings"
+- "sync"
+- "time"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/imports"
+-)
+-
+-// KnownPackagePaths returns a new list of package paths of all known
+-// packages in the package graph that could potentially be imported by
+-// the given file. The list is ordered lexicographically, except that
+-// all dot-free paths (standard packages) appear before dotful ones.
+-//
+-// It is part of the gopls.list_known_packages command.
+-func KnownPackagePaths(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]PackagePath, error) {
+- // This algorithm is expressed in terms of Metadata, not Packages,
+- // so it doesn't cause or wait for type checking.
+-
+- current, err := snapshot.NarrowestMetadataForFile(ctx, fh.URI())
+- if err != nil {
+- return nil, err // e.g. context cancelled
+- }
+-
+- // Parse the file's imports so we can compute which
+- // PackagePaths are imported by this specific file.
+- src, err := fh.Content()
+- if err != nil {
+- return nil, err
+- }
+- file, err := parser.ParseFile(token.NewFileSet(), fh.URI().Path(), src, parser.ImportsOnly)
+- if err != nil {
+- return nil, err
+- }
+- imported := make(map[PackagePath]bool)
+- for _, imp := range file.Imports {
+- if id := current.DepsByImpPath[metadata.UnquoteImportPath(imp)]; id != "" {
+- if mp := snapshot.Metadata(id); mp != nil {
+- imported[mp.PkgPath] = true
+- }
+- }
+- }
+-
+- // Now find candidates among all known packages.
+- knownPkgs, err := snapshot.AllMetadata(ctx)
+- if err != nil {
+- return nil, err
+- }
+- seen := make(map[PackagePath]bool)
+- for _, knownPkg := range knownPkgs {
+- // package main cannot be imported
+- if knownPkg.Name == "main" {
+- continue
+- }
+- // test packages cannot be imported
+- if knownPkg.ForTest != "" {
+- continue
+- }
+- // No need to import what the file already imports.
+- // This check is based on PackagePath, not PackageID,
+- // so that all test variants are filtered out too.
+- if imported[knownPkg.PkgPath] {
+- continue
+- }
+- // make sure internal packages are importable by the file
+- if !metadata.IsValidImport(current.PkgPath, knownPkg.PkgPath, snapshot.View().Type() != cache.GoPackagesDriverView) {
+- continue
+- }
+- // naive check on cyclical imports
+- if isDirectlyCyclical(current, knownPkg) {
+- continue
+- }
+- // AllMetadata may have multiple variants of a pkg.
+- seen[knownPkg.PkgPath] = true
+- }
+-
+- // Augment the set by invoking the goimports algorithm.
+- if err := snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, o *imports.Options) error {
+- ctx, cancel := context.WithTimeout(ctx, time.Millisecond*80)
+- defer cancel()
+- var seenMu sync.Mutex
+- wrapped := func(ifix imports.ImportFix) {
+- seenMu.Lock()
+- defer seenMu.Unlock()
+- // TODO(adonovan): what if the actual package path has a vendor/ prefix?
+- seen[PackagePath(ifix.StmtInfo.ImportPath)] = true
+- }
+- return imports.GetAllCandidates(ctx, wrapped, "", fh.URI().Path(), string(current.Name), o.Env)
+- }); err != nil {
+- // If goimports failed, proceed with just the candidates from the metadata.
+- event.Error(ctx, "imports.GetAllCandidates", err)
+- }
+-
+- // Sort lexicographically, but with std before non-std packages.
+- paths := make([]PackagePath, 0, len(seen))
+- for path := range seen {
+- paths = append(paths, path)
+- }
+- sort.Slice(paths, func(i, j int) bool {
+- importI, importJ := paths[i], paths[j]
+- iHasDot := strings.Contains(string(importI), ".")
+- jHasDot := strings.Contains(string(importJ), ".")
+- if iHasDot != jHasDot {
+- return jHasDot // dot-free paths (standard packages) compare less
+- }
+- return importI < importJ
+- })
+-
+- return paths, nil
+-}
+-
+-// isDirectlyCyclical checks if imported directly imports pkg.
+-// It does not (yet) offer a full cyclical check because showing a user
+-// a list of importable packages already generates a very large list
+-// and having a few false positives in there could be worth the
+-// performance snappiness.
+-//
+-// TODO(adonovan): ensure that metadata graph is always cyclic!
+-// Many algorithms will get confused or even stuck in the
+-// presence of cycles. Then replace this function by 'false'.
+-func isDirectlyCyclical(pkg, imported *metadata.Package) bool {
+- _, ok := imported.DepsByPkgPath[pkg.PkgPath]
+- return ok
+-}
+diff -urN a/gopls/internal/golang/lines.go b/gopls/internal/golang/lines.go
+--- a/gopls/internal/golang/lines.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/lines.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,269 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file defines refactorings for splitting lists of elements
+-// (arguments, literals, etc) across multiple lines, and joining
+-// them into a single line.
+-
+-import (
+- "bytes"
+- "go/ast"
+- "go/token"
+- "slices"
+- "sort"
+- "strings"
+-
+- "golang.org/x/tools/go/analysis"
+- "golang.org/x/tools/go/ast/edge"
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+-)
+-
+-// canSplitLines checks whether we can split lists of elements inside
+-// an enclosing curly bracket/parens into separate lines.
+-func canSplitLines(curFile inspector.Cursor, fset *token.FileSet, start, end token.Pos) (string, bool, error) {
+- itemType, items, comments, _, _, _ := findSplitJoinTarget(fset, curFile, nil, start, end)
+- if itemType == "" {
+- return "", false, nil
+- }
+-
+- if !canSplitJoinLines(items, comments) {
+- return "", false, nil
+- }
+-
+- for i := 1; i < len(items); i++ {
+- prevLine := safetoken.EndPosition(fset, items[i-1].End()).Line
+- curLine := safetoken.StartPosition(fset, items[i].Pos()).Line
+- if prevLine == curLine {
+- return "Split " + itemType + " into separate lines", true, nil
+- }
+- }
+-
+- return "", false, nil
+-}
+-
+-// canJoinLines checks whether we can join lists of elements inside an
+-// enclosing curly bracket/parens into a single line.
+-func canJoinLines(curFile inspector.Cursor, fset *token.FileSet, start, end token.Pos) (string, bool, error) {
+- itemType, items, comments, _, _, _ := findSplitJoinTarget(fset, curFile, nil, start, end)
+- if itemType == "" {
+- return "", false, nil
+- }
+-
+- if !canSplitJoinLines(items, comments) {
+- return "", false, nil
+- }
+-
+- for i := 1; i < len(items); i++ {
+- prevLine := safetoken.EndPosition(fset, items[i-1].End()).Line
+- curLine := safetoken.StartPosition(fset, items[i].Pos()).Line
+- if prevLine != curLine {
+- return "Join " + itemType + " into one line", true, nil
+- }
+- }
+-
+- return "", false, nil
+-}
+-
+-// canSplitJoinLines determines whether we should split/join the lines or not.
+-func canSplitJoinLines(items []ast.Node, comments []*ast.CommentGroup) bool {
+- if len(items) <= 1 {
+- return false
+- }
+-
+- for _, cg := range comments {
+- if !strings.HasPrefix(cg.List[0].Text, "/*") {
+- return false // can't split/join lists containing "//" comments
+- }
+- }
+-
+- return true
+-}
+-
+-// splitLines is a singleFile fixer.
+-func splitLines(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
+- fset := pkg.FileSet()
+- itemType, items, comments, indent, braceOpen, braceClose := findSplitJoinTarget(fset, pgf.Cursor, pgf.Src, start, end)
+- if itemType == "" {
+- return nil, nil, nil // no fix available
+- }
+-
+- return fset, processLines(fset, items, comments, pgf.Src, braceOpen, braceClose, ",\n", "\n", ",\n"+indent, indent+"\t"), nil
+-}
+-
+-// joinLines is a singleFile fixer.
+-func joinLines(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (*token.FileSet, *analysis.SuggestedFix, error) {
+- fset := pkg.FileSet()
+- itemType, items, comments, _, braceOpen, braceClose := findSplitJoinTarget(fset, pgf.Cursor, pgf.Src, start, end)
+- if itemType == "" {
+- return nil, nil, nil // no fix available
+- }
+-
+- return fset, processLines(fset, items, comments, pgf.Src, braceOpen, braceClose, ", ", "", "", ""), nil
+-}
+-
+-// processLines is the common operation for both split and join lines because this split/join operation is
+-// essentially a transformation of the separating whitespace.
+-func processLines(fset *token.FileSet, items []ast.Node, comments []*ast.CommentGroup, src []byte, braceOpen, braceClose token.Pos, sep, prefix, suffix, indent string) *analysis.SuggestedFix {
+- nodes := slices.Clone(items)
+-
+- // box *ast.CommentGroup to ast.Node for easier processing later.
+- for _, cg := range comments {
+- nodes = append(nodes, cg)
+- }
+-
+- // Sort to interleave comments and nodes.
+- sort.Slice(nodes, func(i, j int) bool {
+- return nodes[i].Pos() < nodes[j].Pos()
+- })
+-
+- edits := []analysis.TextEdit{
+- {
+- Pos: token.Pos(int(braceOpen) + len("{")),
+- End: nodes[0].Pos(),
+- NewText: []byte(prefix + indent),
+- },
+- {
+- Pos: nodes[len(nodes)-1].End(),
+- End: braceClose,
+- NewText: []byte(suffix),
+- },
+- }
+-
+- for i := 1; i < len(nodes); i++ {
+- pos, end := nodes[i-1].End(), nodes[i].Pos()
+- if pos > end {
+- // this will happen if we have a /*-style comment inside of a Field
+- // e.g. `a /*comment here */ int`
+- //
+- // we will ignore as we only care about finding the field delimiter.
+- continue
+- }
+-
+- // at this point, the `,` token in between 2 nodes here must be the field delimiter.
+- posOffset := safetoken.EndPosition(fset, pos).Offset
+- endOffset := safetoken.StartPosition(fset, end).Offset
+- if bytes.IndexByte(src[posOffset:endOffset], ',') == -1 {
+- // nodes[i] or nodes[i-1] is a comment hence no delimiter in between
+- // in such case, do nothing.
+- continue
+- }
+-
+- edits = append(edits, analysis.TextEdit{Pos: pos, End: end, NewText: []byte(sep + indent)})
+-
+- // Print the Ellipsis if we synthesized one earlier.
+- if is[*ast.Ellipsis](nodes[i]) {
+- edits = append(edits, analysis.TextEdit{
+- Pos: nodes[i].End(),
+- End: nodes[i].End(),
+- NewText: []byte("..."),
+- })
+- }
+- }
+-
+- return &analysis.SuggestedFix{TextEdits: edits}
+-}
+-
+-// findSplitJoinTarget returns the first curly bracket/parens that encloses the current cursor.
+-func findSplitJoinTarget(fset *token.FileSet, curFile inspector.Cursor, src []byte, start, end token.Pos) (itemType string, items []ast.Node, comments []*ast.CommentGroup, indent string, open, close token.Pos) {
+-
+- findTarget := func() (targetType string, target ast.Node, open, close token.Pos) {
+- cur, _ := curFile.FindByPos(start, end)
+- for cur := range cur.Enclosing() {
+- // TODO: do cur = enclosingUnparen(cur) first, once CL 701035 lands.
+- ek, _ := cur.ParentEdge()
+- switch ek {
+- // params or results of func signature
+- // Note:
+- // - each ast.Field (e.g. "x, y, z int") is considered a single item.
+- // - splitting Params and Results lists is not usually good style.
+- case edge.FuncType_Params:
+- p := cur.Node().(*ast.FieldList)
+- return "parameters", p, p.Opening, p.Closing
+- case edge.FuncType_Results:
+- r := cur.Node().(*ast.FieldList)
+- if !r.Opening.IsValid() {
+- continue
+- }
+- return "results", r, r.Opening, r.Closing
+- case edge.CallExpr_Args: // f(a, b, c)
+- node := cur.Parent().Node().(*ast.CallExpr)
+- return "arguments", node, node.Lparen, node.Rparen
+- case edge.CompositeLit_Elts: // T{a, b, c}
+- node := cur.Parent().Node().(*ast.CompositeLit)
+- return "elements", node, node.Lbrace, node.Rbrace
+- }
+- }
+- return "", nil, 0, 0
+- }
+-
+- targetType, targetNode, open, close := findTarget()
+- if targetType == "" {
+- return "", nil, nil, "", 0, 0
+- }
+-
+- switch node := targetNode.(type) {
+- case *ast.FieldList:
+- for _, field := range node.List {
+- items = append(items, field)
+- }
+- case *ast.CallExpr:
+- for _, arg := range node.Args {
+- items = append(items, arg)
+- }
+-
+- // Preserve "..." by wrapping the last
+- // argument in an Ellipsis node
+- // with the same Pos/End as the argument.
+- // See corresponding logic in processLines.
+- if node.Ellipsis.IsValid() {
+- last := &items[len(items)-1]
+- *last = &ast.Ellipsis{
+- Ellipsis: (*last).Pos(), // determines Ellipsis.Pos()
+- Elt: (*last).(ast.Expr), // determines Ellipsis.End()
+- }
+- }
+- case *ast.CompositeLit:
+- for _, arg := range node.Elts {
+- items = append(items, arg)
+- }
+- }
+-
+- // preserve comments separately as it's not part of the targetNode AST.
+- file := curFile.Node().(*ast.File)
+- for _, cg := range file.Comments {
+- if open <= cg.Pos() && cg.Pos() < close {
+- comments = append(comments, cg)
+- }
+- }
+-
+- // indent is the leading whitespace before the opening curly bracket/paren.
+- //
+- // in case where we don't have access to src yet i.e. src == nil
+- // it's fine to return incorrect indent because we don't need it yet.
+- indent = ""
+- if len(src) > 0 {
+- var pos token.Pos
+- switch node := targetNode.(type) {
+- case *ast.FieldList:
+- pos = node.Opening
+- case *ast.CallExpr:
+- pos = node.Lparen
+- case *ast.CompositeLit:
+- pos = node.Lbrace
+- }
+-
+- split := bytes.Split(src, []byte("\n"))
+- targetLineNumber := safetoken.StartPosition(fset, pos).Line
+- firstLine := string(split[targetLineNumber-1])
+- trimmed := strings.TrimSpace(string(firstLine))
+- indent = firstLine[:strings.Index(firstLine, trimmed)]
+- }
+-
+- return targetType, items, comments, indent, open, close
+-}
+diff -urN a/gopls/internal/golang/linkname.go b/gopls/internal/golang/linkname.go
+--- a/gopls/internal/golang/linkname.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/linkname.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,140 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "errors"
+- "fmt"
+- "go/token"
+- "strings"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/protocol"
+-)
+-
+-// ErrNoLinkname is returned by LinknameDefinition when no linkname
+-// directive is found at a particular position.
+-// As such it indicates that other definitions could be worth checking.
+-var ErrNoLinkname = errors.New("no linkname directive found")
+-
+-// linknameDefinition finds the definition of the linkname directive in m at pos.
+-// If there is no linkname directive at pos, returns ErrNoLinkname.
+-func linknameDefinition(ctx context.Context, snapshot *cache.Snapshot, m *protocol.Mapper, from protocol.Position) ([]protocol.Location, error) {
+- pkgPath, name, _ := parseLinkname(m, from)
+- if pkgPath == "" {
+- return nil, ErrNoLinkname
+- }
+-
+- _, pgf, pos, err := findLinkname(ctx, snapshot, PackagePath(pkgPath), name)
+- if err != nil {
+- return nil, fmt.Errorf("find linkname: %w", err)
+- }
+- loc, err := pgf.PosLocation(pos, pos+token.Pos(len(name)))
+- if err != nil {
+- return nil, fmt.Errorf("location of linkname: %w", err)
+- }
+- return []protocol.Location{loc}, nil
+-}
+-
+-// parseLinkname attempts to parse a go:linkname declaration at the given pos.
+-// If successful, it returns
+-// - package path referenced
+-// - object name referenced
+-// - byte offset in mapped file of the start of the link target
+-// of the linkname directives 2nd argument.
+-//
+-// If the position is not in the second argument of a go:linkname directive,
+-// or parsing fails, it returns "", "", 0.
+-func parseLinkname(m *protocol.Mapper, pos protocol.Position) (pkgPath, name string, targetOffset int) {
+- lineStart, err := m.PositionOffset(protocol.Position{Line: pos.Line, Character: 0})
+- if err != nil {
+- return "", "", 0
+- }
+- lineEnd, err := m.PositionOffset(protocol.Position{Line: pos.Line + 1, Character: 0})
+- if err != nil {
+- return "", "", 0
+- }
+-
+- directive := string(m.Content[lineStart:lineEnd])
+- // (Assumes no leading spaces.)
+- if !strings.HasPrefix(directive, "//go:linkname") {
+- return "", "", 0
+- }
+- // Sometimes source code (typically tests) has another
+- // comment after the directive, trim that away.
+- if i := strings.LastIndex(directive, "//"); i != 0 {
+- directive = strings.TrimSpace(directive[:i])
+- }
+-
+- // Looking for pkgpath in '//go:linkname f pkgpath.g'.
+- // (We ignore 1-arg linkname directives.)
+- parts := strings.Fields(directive)
+- if len(parts) != 3 {
+- return "", "", 0
+- }
+-
+- // Inside 2nd arg [start, end]?
+- // (Assumes no trailing spaces.)
+- offset, err := m.PositionOffset(pos)
+- if err != nil {
+- return "", "", 0
+- }
+- end := lineStart + len(directive)
+- start := end - len(parts[2])
+- if !(start <= offset && offset <= end) {
+- return "", "", 0
+- }
+- linkname := parts[2]
+-
+- // Split the pkg path from the name.
+- dot := strings.LastIndexByte(linkname, '.')
+- if dot < 0 {
+- return "", "", 0
+- }
+-
+- return linkname[:dot], linkname[dot+1:], start
+-}
+-
+-// findLinkname searches dependencies of packages containing fh for an object
+-// with linker name matching the given package path and name.
+-func findLinkname(ctx context.Context, snapshot *cache.Snapshot, pkgPath PackagePath, name string) (*cache.Package, *parsego.File, token.Pos, error) {
+- // Typically the linkname refers to a forward dependency
+- // or a reverse dependency, but in general it may refer
+- // to any package that is linked with this one.
+- var pkgMeta *metadata.Package
+- metas, err := snapshot.AllMetadata(ctx)
+- if err != nil {
+- return nil, nil, token.NoPos, err
+- }
+- metadata.RemoveIntermediateTestVariants(&metas)
+- for _, meta := range metas {
+- if meta.PkgPath == pkgPath {
+- pkgMeta = meta
+- break
+- }
+- }
+- if pkgMeta == nil {
+- return nil, nil, token.NoPos, fmt.Errorf("cannot find package %q", pkgPath)
+- }
+-
+- // When found, type check the desired package (snapshot.TypeCheck in TypecheckFull mode),
+- pkgs, err := snapshot.TypeCheck(ctx, pkgMeta.ID)
+- if err != nil {
+- return nil, nil, token.NoPos, err
+- }
+- pkg := pkgs[0]
+-
+- obj := pkg.Types().Scope().Lookup(name)
+- if obj == nil {
+- return nil, nil, token.NoPos, fmt.Errorf("package %q does not define %s", pkgPath, name)
+- }
+-
+- pos := obj.Pos()
+- pgf, err := pkg.FileEnclosing(pos)
+- return pkg, pgf, pos, err
+-}
+diff -urN a/gopls/internal/golang/modify_tags.go b/gopls/internal/golang/modify_tags.go
+--- a/gopls/internal/golang/modify_tags.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/modify_tags.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,74 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "bytes"
+- "context"
+- "fmt"
+- "go/ast"
+- "go/format"
+-
+- "github.com/fatih/gomodifytags/modifytags"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/protocol/command"
+- "golang.org/x/tools/gopls/internal/util/cursorutil"
+- "golang.org/x/tools/gopls/internal/util/tokeninternal"
+- internalastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/diff"
+-)
+-
+-// ModifyTags applies the given struct tag modifications to the specified struct.
+-func ModifyTags(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, args command.ModifyTagsArgs, m *modifytags.Modification) ([]protocol.DocumentChange, error) {
+- pgf, err := snapshot.ParseGo(ctx, fh, parsego.Full)
+- if err != nil {
+- return nil, fmt.Errorf("error fetching package file: %v", err)
+- }
+- start, end, err := pgf.RangePos(args.Range)
+- if err != nil {
+- return nil, fmt.Errorf("error getting position information: %v", err)
+- }
+- // If the cursor is at a point and not a selection, we should use the entire enclosing struct.
+- if start == end {
+- cur, ok := pgf.Cursor.FindByPos(start, end)
+- if !ok {
+- return nil, fmt.Errorf("error finding start and end positions: %v", err)
+- }
+- structnode, _ := cursorutil.FirstEnclosing[*ast.StructType](cur)
+- if structnode == nil {
+- return nil, fmt.Errorf("no enclosing struct type")
+- }
+- start, end = structnode.Pos(), structnode.End()
+- }
+-
+- // Create a copy of the file node in order to avoid race conditions when we modify the node in Apply.
+- cloned := internalastutil.CloneNode(pgf.File)
+- fset := tokeninternal.FileSetFor(pgf.Tok)
+-
+- if err = m.Apply(fset, cloned, start, end); err != nil {
+- return nil, fmt.Errorf("could not modify tags: %v", err)
+- }
+-
+- // Construct a list of DocumentChanges based on the diff between the formatted node and the
+- // original file content.
+- var after bytes.Buffer
+- if err := format.Node(&after, fset, cloned); err != nil {
+- return nil, err
+- }
+- edits := diff.Bytes(pgf.Src, after.Bytes())
+- if len(edits) == 0 {
+- return nil, nil
+- }
+- textedits, err := protocol.EditsFromDiffEdits(pgf.Mapper, edits)
+- if err != nil {
+- return nil, fmt.Errorf("error computing edits for %s: %v", args.URI, err)
+- }
+- return []protocol.DocumentChange{
+- protocol.DocumentChangeEdit(fh, textedits),
+- }, nil
+-}
+diff -urN a/gopls/internal/golang/origin.go b/gopls/internal/golang/origin.go
+--- a/gopls/internal/golang/origin.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/origin.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,30 +0,0 @@
+-// Copyright 2023 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import "go/types"
+-
+-// containsOrigin reports whether the provided object set contains an object
+-// with the same origin as the provided obj (which may be a synthetic object
+-// created during instantiation).
+-func containsOrigin(objSet map[types.Object]bool, obj types.Object) bool {
+- objOrigin := origin(obj)
+- for target := range objSet {
+- if origin(target) == objOrigin {
+- return true
+- }
+- }
+- return false
+-}
+-
+-func origin(obj types.Object) types.Object {
+- switch obj := obj.(type) {
+- case *types.Var:
+- return obj.Origin()
+- case *types.Func:
+- return obj.Origin()
+- }
+- return obj
+-}
+diff -urN a/gopls/internal/golang/pkgdoc.go b/gopls/internal/golang/pkgdoc.go
+--- a/gopls/internal/golang/pkgdoc.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/pkgdoc.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,858 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file defines a simple HTML rendering of package documentation
+-// in imitation of the style of pkg.go.dev.
+-//
+-// The current implementation is just a starting point and a
+-// placeholder for a more sophisticated one.
+-//
+-// TODO(adonovan):
+-// - rewrite using html/template.
+-// Or factor with golang.org/x/pkgsite/internal/godoc/dochtml.
+-// - emit breadcrumbs for parent + sibling packages.
+-// - list promoted methods---we have type information! (golang/go#67158)
+-// - gather Example tests, following go/doc and pkgsite.
+-// - add option for doc.AllDecls: show non-exported symbols too.
+-// - style the
bullets in the index as invisible.
+-// - add push notifications such as didChange -> reload.
+-// - there appears to be a maximum file size beyond which the
+-// "source.doc" code action is not offered. Remove that.
+-// - modify JS httpGET function to give a transient visual indication
+-// when clicking a source link that the editor is being navigated
+-// (in case it doesn't raise itself, like VS Code).
+-// - move this into a new package, golang/web, and then
+-// split out the various helpers without fear of polluting
+-// the golang package namespace?
+-// - show "Deprecated" chip when appropriate.
+-
+-import (
+- "bytes"
+- "fmt"
+- "go/ast"
+- "go/doc"
+- "go/doc/comment"
+- "go/format"
+- "go/token"
+- "go/types"
+- "html"
+- "path/filepath"
+- "slices"
+- "strings"
+-
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/bug"
+- internalastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/stdlib"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-// DocFragment finds the package and (optionally) symbol identified by
+-// the current selection, and returns the package path and the
+-// optional symbol URL fragment (e.g. "#Buffer.Len") for a symbol,
+-// along with a title for the code action.
+-//
+-// It is called once to offer the code action, and again when the
+-// command is executed. This is slightly inefficient but ensures that
+-// the title and package/symbol logic are consistent in all cases.
+-//
+-// It returns zeroes if there is nothing to see here (e.g. reference to a builtin).
+-func DocFragment(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) (pkgpath PackagePath, fragment, title string) {
+- thing := thingAtPoint(pkg, pgf, start, end)
+-
+- makeTitle := func(kind string, imp *types.Package, name string) string {
+- title := "Browse documentation for " + kind + " "
+- if imp != nil && imp != pkg.Types() {
+- title += imp.Name() + "."
+- }
+- return title + name
+- }
+-
+- wholePackage := func(pkg *types.Package) (PackagePath, string, string) {
+- // External test packages don't have /pkg doc pages,
+- // so instead show the doc for the package under test.
+- // (This named-based heuristic is imperfect.)
+- if forTest := strings.TrimSuffix(pkg.Path(), "_test"); forTest != pkg.Path() {
+- return PackagePath(forTest), "", makeTitle("package", nil, filepath.Base(forTest))
+- }
+-
+- return PackagePath(pkg.Path()), "", makeTitle("package", nil, pkg.Name())
+- }
+-
+- // Conceptually, we check cases in the order:
+- // 1. symbol
+- // 2. package
+- // 3. enclosing
+- // but the logic of cases 1 and 3 are identical, hence the odd factoring.
+-
+- // Imported package?
+- if thing.pkg != nil && thing.symbol == nil {
+- return wholePackage(thing.pkg)
+- }
+-
+- // Symbol?
+- var sym types.Object
+- if thing.symbol != nil {
+- sym = thing.symbol // reference to a symbol
+- } else if thing.enclosing != nil {
+- sym = thing.enclosing // selection is within a declaration of a symbol
+- }
+- if sym == nil {
+- return wholePackage(pkg.Types()) // no symbol
+- }
+-
+- // Built-in (error.Error, append or unsafe).
+- // TODO(adonovan): handle builtins in /pkg viewer.
+- if sym.Pkg() == nil {
+- return "", "", "" // nothing to see here
+- }
+- pkgpath = PackagePath(sym.Pkg().Path())
+-
+- // Unexported? Show enclosing type or package.
+- if !sym.Exported() {
+- // Unexported method of exported type?
+- if fn, ok := sym.(*types.Func); ok {
+- if recv := fn.Signature().Recv(); recv != nil {
+- _, named := typesinternal.ReceiverNamed(recv)
+- if named != nil && named.Obj().Exported() {
+- sym = named.Obj()
+- goto below
+- }
+- }
+- }
+-
+- return wholePackage(sym.Pkg())
+- below:
+- }
+-
+- // Reference to symbol in external test package?
+- // Short-circuit: see comment in wholePackage.
+- if strings.HasSuffix(string(pkgpath), "_test") {
+- return wholePackage(pkg.Types())
+- }
+-
+- // package-level symbol?
+- if typesinternal.IsPackageLevel(sym) {
+- return pkgpath, sym.Name(), makeTitle(objectKind(sym), sym.Pkg(), sym.Name())
+- }
+-
+- // Inv: sym is field or method, or local.
+- switch sym := sym.(type) {
+- case *types.Func: // => method
+- sig := sym.Signature()
+- isPtr, named := typesinternal.ReceiverNamed(sig.Recv())
+- if named != nil {
+- if !named.Obj().Exported() {
+- return wholePackage(sym.Pkg()) // exported method of unexported type
+- }
+- name := fmt.Sprintf("(%s%s).%s",
+- strings.Repeat("*", btoi(isPtr)), // for *T
+- named.Obj().Name(),
+- sym.Name())
+- fragment := named.Obj().Name() + "." + sym.Name()
+- return pkgpath, fragment, makeTitle("method", sym.Pkg(), name)
+- }
+-
+- case *types.Var:
+- if sym.IsField() {
+- // TODO(adonovan): support fields.
+- // The Var symbol doesn't include the struct
+- // type, so we need to use the logic from
+- // Hover. (This isn't important for
+- // DocFragment as fields don't have fragments,
+- // but it matters to the grand unification of
+- // Hover/Definition/DocFragment.
+- }
+- }
+-
+- // Field, non-exported method, or local declaration:
+- // just show current package.
+- return wholePackage(pkg.Types())
+-}
+-
+-// thing describes the package or symbol denoted by a selection.
+-//
+-// TODO(adonovan): Hover, Definition, and References all start by
+-// identifying the selected object. Let's achieve a better factoring
+-// of the common parts using this structure, including uniform
+-// treatment of doc links, linkname, and suchlike.
+-type thing struct {
+- // At most one of these fields is set.
+- // (The 'enclosing' field is a fallback for when neither
+- // of the first two is set.)
+- symbol types.Object // referenced symbol
+- pkg *types.Package // referenced package
+- enclosing types.Object // package-level symbol or method decl enclosing selection
+-}
+-
+-func thingAtPoint(pkg *cache.Package, pgf *parsego.File, start, end token.Pos) thing {
+- path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
+-
+- // In an import spec?
+- if len(path) >= 3 { // [...ImportSpec GenDecl File]
+- if spec, ok := path[len(path)-3].(*ast.ImportSpec); ok {
+- if pkgname := pkg.TypesInfo().PkgNameOf(spec); pkgname != nil {
+- return thing{pkg: pkgname.Imported()}
+- }
+- }
+- }
+-
+- // Definition or reference to symbol?
+- var obj types.Object
+- if id, ok := path[0].(*ast.Ident); ok {
+- obj = pkg.TypesInfo().ObjectOf(id)
+-
+- // Treat use to PkgName like ImportSpec.
+- if pkgname, ok := obj.(*types.PkgName); ok {
+- return thing{pkg: pkgname.Imported()}
+- }
+-
+- } else if sel, ok := path[0].(*ast.SelectorExpr); ok {
+- // e.g. selection is "fmt.Println" or just a portion ("mt.Prin")
+- obj = pkg.TypesInfo().Uses[sel.Sel]
+- }
+- if obj != nil {
+- return thing{symbol: obj}
+- }
+-
+- // Find enclosing declaration.
+- if n := len(path); n > 1 {
+- switch decl := path[n-2].(type) {
+- case *ast.FuncDecl:
+- // method?
+- if fn := pkg.TypesInfo().Defs[decl.Name]; fn != nil {
+- return thing{enclosing: fn}
+- }
+-
+- case *ast.GenDecl:
+- // path=[... Spec? GenDecl File]
+- for _, spec := range decl.Specs {
+- if n > 2 && spec == path[n-3] {
+- var name *ast.Ident
+- switch spec := spec.(type) {
+- case *ast.ValueSpec:
+- // var, const: use first name
+- name = spec.Names[0]
+- case *ast.TypeSpec:
+- name = spec.Name
+- }
+- if name != nil {
+- return thing{enclosing: pkg.TypesInfo().Defs[name]}
+- }
+- break
+- }
+- }
+- }
+- }
+-
+- return thing{} // nothing to see here
+-}
+-
+-// Web is an abstraction of gopls' web server.
+-type Web interface {
+- // PkgURL forms URLs of package or symbol documentation.
+- PkgURL(viewID string, path PackagePath, fragment string) protocol.URI
+-
+- // SrcURL forms URLs that cause the editor to open a file at a specific position.
+- SrcURL(filename string, line, col8 int) protocol.URI
+-}
+-
+-// PackageDocHTML formats the package documentation page.
+-//
+-// The posURL function returns a URL that when visited, has the side
+-// effect of causing gopls to direct the client editor to navigate to
+-// the specified file/line/column position, in UTF-8 coordinates.
+-//
+-// TODO(adonovan): this function could use some unit tests; we
+-// shouldn't have to use integration tests to cover microdetails of
+-// HTML rendering. (It is tempting to abstract this function so that
+-// it depends only on FileSet/File/Types/TypeInfo/etc, but we should
+-// bend the tests to the production interfaces, not the other way
+-// around.)
+-func PackageDocHTML(viewID string, pkg *cache.Package, web Web) ([]byte, error) {
+- // We can't use doc.NewFromFiles (even with doc.PreserveAST
+- // mode) as it calls ast.NewPackage which assumes that each
+- // ast.File has an ast.Scope and resolves identifiers to
+- // (deprecated) ast.Objects. (This is golang/go#66290.)
+- // But doc.New only requires pkg.{Name,Files},
+- // so we just boil it down.
+- //
+- // The only loss is doc.classifyExamples.
+- // TODO(adonovan): simulate that too.
+- fileMap := make(map[string]*ast.File)
+- for _, f := range pkg.Syntax() {
+- fileMap[pkg.FileSet().File(f.FileStart).Name()] = f
+- }
+- astpkg := &ast.Package{
+- Name: pkg.Types().Name(),
+- Files: fileMap,
+- }
+- // PreserveAST mode only half works (golang/go#66449): it still
+- // mutates ASTs when filtering out non-exported symbols.
+- // As a workaround, enable AllDecls to suppress filtering,
+- // and do it ourselves.
+- mode := doc.PreserveAST | doc.AllDecls
+- docpkg := doc.New(astpkg, pkg.Types().Path(), mode)
+-
+- // Discard non-exported symbols.
+- // TODO(adonovan): do this conditionally, and expose option in UI.
+- const showUnexported = false
+- if !showUnexported {
+- var (
+- unexported = func(name string) bool { return !token.IsExported(name) }
+- filterValues = func(slice *[]*doc.Value) {
+- delValue := func(v *doc.Value) bool {
+- v.Names = slices.DeleteFunc(v.Names, unexported)
+- return len(v.Names) == 0
+- }
+- *slice = slices.DeleteFunc(*slice, delValue)
+- }
+- filterFuncs = func(funcs *[]*doc.Func) {
+- *funcs = slices.DeleteFunc(*funcs, func(v *doc.Func) bool {
+- return unexported(v.Name)
+- })
+- }
+- )
+- filterValues(&docpkg.Consts)
+- filterValues(&docpkg.Vars)
+- filterFuncs(&docpkg.Funcs)
+- docpkg.Types = slices.DeleteFunc(docpkg.Types, func(t *doc.Type) bool {
+- filterValues(&t.Consts)
+- filterValues(&t.Vars)
+- filterFuncs(&t.Funcs)
+- filterFuncs(&t.Methods)
+- if unexported(t.Name) {
+- // If an unexported type has an exported constructor function,
+- // treat the constructor as an ordinary standalone function.
+- // We will sort Funcs again below.
+- docpkg.Funcs = append(docpkg.Funcs, t.Funcs...)
+- return true // delete this type
+- }
+- return false // keep this type
+- })
+- slices.SortFunc(docpkg.Funcs, func(x, y *doc.Func) int {
+- return strings.Compare(x.Name, y.Name)
+- })
+- }
+-
+- // docHTML renders the doc comment as Markdown.
+- // The fileNode is used to deduce the enclosing file
+- // for the correct import mapping.
+- //
+- // It is not concurrency-safe.
+- var docHTML func(fileNode ast.Node, comment string) []byte
+- {
+- // Adapt doc comment parser and printer
+- // to our representation of Go packages
+- // so that doc links (e.g. "[fmt.Println]")
+- // become valid links.
+- printer := &comment.Printer{
+- DocLinkURL: func(link *comment.DocLink) string {
+- path := pkg.Metadata().PkgPath
+- if link.ImportPath != "" {
+- path = PackagePath(link.ImportPath)
+- }
+- fragment := link.Name
+- if link.Recv != "" {
+- fragment = link.Recv + "." + link.Name
+- }
+- return web.PkgURL(viewID, path, fragment)
+- },
+- }
+- parse := newDocCommentParser(pkg)
+- docHTML = func(fileNode ast.Node, comment string) []byte {
+- doc := parse(fileNode, comment)
+- return printer.HTML(doc)
+- }
+- }
+-
+- scope := pkg.Types().Scope()
+- escape := html.EscapeString
+-
+- title := fmt.Sprintf("%s package - %s - Gopls packages",
+- pkg.Types().Name(), escape(pkg.Types().Path()))
+-
+- var buf bytes.Buffer
+- buf.WriteString(`
+-
+-
+-
+- ` + title + `
+-
+-
+-
+-
+-
+-
+-
+-\n")
+- fmt.Fprintf(&buf, "\n")
+-
+- // -- main element --
+-
+- // nodeHTML returns HTML markup for a syntax tree.
+- // It replaces referring identifiers with links,
+- // and adds style spans for strings and comments.
+- nodeHTML := func(n ast.Node) string {
+-
+- // linkify returns the appropriate URL (if any) for an identifier.
+- linkify := func(id *ast.Ident) protocol.URI {
+- if obj, ok := pkg.TypesInfo().Uses[id]; ok && obj.Pkg() != nil {
+- // imported package name?
+- if pkgname, ok := obj.(*types.PkgName); ok {
+- // TODO(adonovan): do this for Defs of PkgName too.
+- return web.PkgURL(viewID, PackagePath(pkgname.Imported().Path()), "")
+- }
+-
+- // package-level symbol?
+- if obj.Parent() == obj.Pkg().Scope() {
+- if obj.Pkg() == pkg.Types() {
+- return "#" + obj.Name() // intra-package ref
+- } else {
+- return web.PkgURL(viewID, PackagePath(obj.Pkg().Path()), obj.Name())
+- }
+- }
+-
+- // method of package-level named type?
+- if fn, ok := obj.(*types.Func); ok {
+- sig := fn.Signature()
+- if sig.Recv() != nil {
+- _, named := typesinternal.ReceiverNamed(sig.Recv())
+- if named != nil {
+- fragment := named.Obj().Name() + "." + fn.Name()
+- return web.PkgURL(viewID, PackagePath(fn.Pkg().Path()), fragment)
+- }
+- }
+- return ""
+- }
+-
+- // TODO(adonovan): field of package-level named struct type.
+- // (Requires an index, since there's no way to
+- // get from Var to Named.)
+- }
+- return ""
+- }
+-
+- // Splice spans into HTML-escaped segments of the
+- // original source buffer (which is usually but not
+- // necessarily formatted).
+- //
+- // (For expedience we don't use the more sophisticated
+- // approach taken by cmd/godoc and pkgsite's render
+- // package, which emit the text, spans, and comments
+- // in one traversal of the syntax tree.)
+- //
+- // TODO(adonovan): splice styled spans around comments too.
+- //
+- // TODO(adonovan): pkgsite prints specs from grouped
+- // type decls like "type ( T1; T2 )" to make them
+- // appear as separate decls. We should too.
+- var buf bytes.Buffer
+- for _, file := range pkg.CompiledGoFiles() {
+- if internalastutil.NodeContains(file.File, n.Pos()) {
+- pos := n.Pos()
+-
+- // emit emits source in the interval [pos:to] and updates pos.
+- emit := func(to token.Pos) {
+- // Ident and BasicLit always have a valid pos.
+- // (Failure means the AST has been corrupted.)
+- if !to.IsValid() {
+- bug.Reportf("invalid Pos")
+- }
+- text, err := file.PosText(pos, to)
+- if err != nil {
+- bug.Reportf("invalid pos range: %v", err)
+- return
+- }
+- buf.WriteString(escape(string(text)))
+- pos = to
+- }
+- ast.Inspect(n, func(n ast.Node) bool {
+- switch n := n.(type) {
+- case *ast.Ident:
+- emit(n.Pos())
+- pos = n.End()
+- if url := linkify(n); url != "" {
+- fmt.Fprintf(&buf, "%s", url, escape(n.Name))
+- } else {
+- buf.WriteString(escape(n.Name)) // plain
+- }
+-
+- case *ast.BasicLit:
+- emit(n.Pos())
+- pos = n.End()
+- fmt.Fprintf(&buf, "%s", escape(n.Value))
+- }
+- return true
+- })
+- emit(n.End())
+- return buf.String()
+- }
+- }
+-
+- // Original source not found.
+- // Format the node without adornments.
+- if err := format.Node(&buf, pkg.FileSet(), n); err != nil {
+- // e.g. BadDecl?
+- buf.Reset()
+- fmt.Fprintf(&buf, "formatting error: %v", err)
+- }
+- return escape(buf.String())
+- }
+-
+- // fnString is like fn.String() except that it:
+- // - shows the receiver name;
+- // - uses space "(T) M()" not dot "(T).M()" after receiver;
+- // - doesn't bother with the special case for interface receivers
+- // since it is unreachable for the methods in go/doc.
+- // - elides parameters after the first three: f(a, b, c, ...).
+- fnString := func(fn *types.Func) string {
+- pkgRelative := typesinternal.NameRelativeTo(pkg.Types())
+-
+- sig := fn.Signature()
+-
+- // Emit "func (recv T) F".
+- var buf bytes.Buffer
+- buf.WriteString("func ")
+- if recv := sig.Recv(); recv != nil {
+- buf.WriteByte('(')
+- if recv.Name() != "" {
+- buf.WriteString(recv.Name())
+- buf.WriteByte(' ')
+- }
+- types.WriteType(&buf, recv.Type(), pkgRelative)
+- buf.WriteByte(')')
+- buf.WriteByte(' ') // (ObjectString uses a '.' here)
+- } else if pkg := fn.Pkg(); pkg != nil {
+- if s := pkgRelative(pkg); s != "" {
+- buf.WriteString(s)
+- buf.WriteByte('.')
+- }
+- }
+- buf.WriteString(fn.Name())
+-
+- // Emit signature.
+- //
+- // Elide parameters after the third one.
+- // WriteSignature is too complex to fork, so we replace
+- // parameters 4+ with "invalid type", format,
+- // then post-process the string.
+- if sig.Params().Len() > 3 {
+-
+- // Clone each TypeParam as NewSignatureType modifies them (#67294).
+- cloneTparams := func(seq *types.TypeParamList) []*types.TypeParam {
+- slice := make([]*types.TypeParam, seq.Len())
+- for i := range slice {
+- tparam := seq.At(i)
+- slice[i] = types.NewTypeParam(tparam.Obj(), tparam.Constraint())
+- }
+- return slice
+- }
+-
+- sig = types.NewSignatureType(
+- sig.Recv(),
+- cloneTparams(sig.RecvTypeParams()),
+- cloneTparams(sig.TypeParams()),
+- types.NewTuple(append(
+- slices.Collect(sig.Params().Variables())[:3],
+- types.NewParam(0, nil, "", types.Typ[types.Invalid]))...),
+- sig.Results(),
+- false) // any final ...T parameter is truncated
+- }
+- types.WriteSignature(&buf, sig, pkgRelative)
+- return strings.ReplaceAll(buf.String(), ", invalid type)", ", ...)")
+- }
+-
+- fmt.Fprintf(&buf, "\n")
+-
+- // package name
+- fmt.Fprintf(&buf, "
\n", docHTML(docfn.Decl, docfn.Doc))
+- }
+- }
+- funcs(docpkg.Funcs)
+-
+- // types and their subelements
+- fmt.Fprintf(&buf, "
Types
\n")
+- for _, doctype := range docpkg.Types {
+- tname := scope.Lookup(doctype.Name).(*types.TypeName)
+-
+- // title and source link
+- fmt.Fprintf(&buf, "
\n", docHTML(doctype.Decl, doctype.Doc))
+-
+- // subelements
+- values(doctype.Consts) // constants of type T
+- values(doctype.Vars) // vars of type T
+- funcs(doctype.Funcs) // constructors of T
+-
+- // methods on T
+- for _, docmethod := range doctype.Methods {
+- method, _, _ := types.LookupFieldOrMethod(tname.Type(), true, tname.Pkg(), docmethod.Name)
+- fmt.Fprintf(&buf, "
\n")
+- for _, filename := range docpkg.Filenames {
+- fmt.Fprintf(&buf, "
%s
\n",
+- sourceLink(filepath.Base(filename), web.SrcURL(filename, 1, 1)))
+- }
+-
+- fmt.Fprintf(&buf, "\n")
+- fmt.Fprintf(&buf, "\n")
+- fmt.Fprintf(&buf, "\n")
+-
+- return buf.Bytes(), nil
+-}
+diff -urN a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go
+--- a/gopls/internal/golang/references.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/references.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,688 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file defines the 'references' query based on a serializable
+-// index constructed during type checking, thus avoiding the need to
+-// type-check packages at search time.
+-//
+-// See the ./xrefs/ subpackage for the index construction and lookup.
+-//
+-// This implementation does not intermingle objects from distinct
+-// calls to TypeCheck.
+-
+-import (
+- "context"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "sort"
+- "strings"
+- "sync"
+-
+- "golang.org/x/sync/errgroup"
+- "golang.org/x/tools/go/types/objectpath"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/methodsets"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/event"
+-)
+-
+-// References returns a list of all references (sorted with
+-// definitions before uses) to the object denoted by the identifier at
+-// the given file/position, searching the entire workspace.
+-func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position, includeDeclaration bool) ([]protocol.Location, error) {
+- references, err := references(ctx, snapshot, fh, pp, includeDeclaration)
+- if err != nil {
+- return nil, err
+- }
+- locations := make([]protocol.Location, len(references))
+- for i, ref := range references {
+- locations[i] = ref.location
+- }
+- return locations, nil
+-}
+-
+-// A reference describes an identifier that refers to the same
+-// object as the subject of a References query.
+-type reference struct {
+- isDeclaration bool
+- location protocol.Location
+- pkgPath PackagePath // of declaring package (same for all elements of the slice)
+-}
+-
+-// references returns a list of all references (sorted with
+-// definitions before uses) to the object denoted by the identifier at
+-// the given file/position, searching the entire workspace.
+-func references(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, includeDeclaration bool) ([]reference, error) {
+- ctx, done := event.Start(ctx, "golang.references")
+- defer done()
+-
+- // Is the cursor within the package name declaration?
+- _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
+- if err != nil {
+- return nil, err
+- }
+-
+- var refs []reference
+- if inPackageName {
+- refs, err = packageReferences(ctx, snapshot, f.URI())
+- } else {
+- refs, err = ordinaryReferences(ctx, snapshot, f.URI(), pp)
+- }
+- if err != nil {
+- return nil, err
+- }
+-
+- sort.Slice(refs, func(i, j int) bool {
+- x, y := refs[i], refs[j]
+- if x.isDeclaration != y.isDeclaration {
+- return x.isDeclaration // decls < refs
+- }
+- return protocol.CompareLocation(x.location, y.location) < 0
+- })
+-
+- // De-duplicate by location, and optionally remove declarations.
+- out := refs[:0]
+- for _, ref := range refs {
+- if !includeDeclaration && ref.isDeclaration {
+- continue
+- }
+- if len(out) == 0 || out[len(out)-1].location != ref.location {
+- out = append(out, ref)
+- }
+- }
+- refs = out
+-
+- return refs, nil
+-}
+-
+-// packageReferences returns a list of references to the package
+-// declaration of the specified name and uri by searching among the
+-// import declarations of all packages that directly import the target
+-// package.
+-func packageReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) ([]reference, error) {
+- metas, err := snapshot.MetadataForFile(ctx, uri, false)
+- if err != nil {
+- return nil, err
+- }
+- if len(metas) == 0 {
+- return nil, fmt.Errorf("found no package containing %s", uri)
+- }
+-
+- var refs []reference
+-
+- // Find external references to the package declaration
+- // from each direct import of the package.
+- //
+- // The narrowest package is the most broadly imported,
+- // so we choose it for the external references.
+- //
+- // But if the file ends with _test.go then we need to
+- // find the package it is testing; there's no direct way
+- // to do that, so pick a file from the same package that
+- // doesn't end in _test.go and start over.
+- narrowest := metas[0]
+- if narrowest.ForTest != "" && strings.HasSuffix(string(uri), "_test.go") {
+- for _, f := range narrowest.CompiledGoFiles {
+- if !strings.HasSuffix(string(f), "_test.go") {
+- return packageReferences(ctx, snapshot, f)
+- }
+- }
+- // This package has no non-test files.
+- // Skip the search for external references.
+- // (Conceivably one could blank-import an empty package, but why?)
+- } else {
+- rdeps, err := snapshot.ReverseDependencies(ctx, narrowest.ID, false) // direct
+- if err != nil {
+- return nil, err
+- }
+-
+- // Restrict search to workspace packages.
+- workspace, err := snapshot.WorkspaceMetadata(ctx)
+- if err != nil {
+- return nil, err
+- }
+- workspaceMap := make(map[PackageID]*metadata.Package, len(workspace))
+- for _, mp := range workspace {
+- workspaceMap[mp.ID] = mp
+- }
+-
+- for _, rdep := range rdeps {
+- if _, ok := workspaceMap[rdep.ID]; !ok {
+- continue
+- }
+- for _, uri := range rdep.CompiledGoFiles {
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return nil, err
+- }
+- f, err := snapshot.ParseGo(ctx, fh, parsego.Header)
+- if err != nil {
+- return nil, err
+- }
+- for _, imp := range f.File.Imports {
+- if rdep.DepsByImpPath[metadata.UnquoteImportPath(imp)] == narrowest.ID {
+- refs = append(refs, reference{
+- isDeclaration: false,
+- location: mustLocation(f, imp),
+- pkgPath: narrowest.PkgPath,
+- })
+- }
+- }
+- }
+- }
+- }
+-
+- // Find internal "references" to the package from
+- // of each package declaration in the target package itself.
+- //
+- // The widest package (possibly a test variant) has the
+- // greatest number of files and thus we choose it for the
+- // "internal" references.
+- widest := metas[len(metas)-1] // may include _test.go files
+- for _, uri := range widest.CompiledGoFiles {
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return nil, err
+- }
+- f, err := snapshot.ParseGo(ctx, fh, parsego.Header)
+- if err != nil {
+- return nil, err
+- }
+- // golang/go#66250: don't crash if the package file lacks a name.
+- if f.File.Name.Pos().IsValid() {
+- refs = append(refs, reference{
+- isDeclaration: true, // (one of many)
+- location: mustLocation(f, f.File.Name),
+- pkgPath: widest.PkgPath,
+- })
+- }
+- }
+-
+- return refs, nil
+-}
+-
+-// ordinaryReferences computes references for all ordinary objects (not package declarations).
+-func ordinaryReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, pp protocol.Position) ([]reference, error) {
+- // Strategy: use the reference information computed by the
+- // type checker to find the declaration. First type-check this
+- // package to find the declaration, then type check the
+- // declaring package (which may be different), plus variants,
+- // to find local (in-package) references.
+- // Global references are satisfied by the index.
+-
+- // Strictly speaking, a wider package could provide a different
+- // declaration (e.g. because the _test.go files can change the
+- // meaning of a field or method selection), but the narrower
+- // package reports the more broadly referenced object.
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, uri)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Find the selected object (declaration or reference).
+- // For struct{T}, we choose the field (Def) over the type (Use).
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return nil, err
+- }
+- candidates, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Pick first object arbitrarily.
+- // The case variables of a type switch have different
+- // types but that difference is immaterial here.
+- var obj types.Object
+- for obj = range candidates {
+- break
+- }
+- if obj == nil {
+- return nil, ErrNoIdentFound // can't happen
+- }
+-
+- // nil, error, error.Error, iota, or other built-in?
+- if isBuiltin(obj) {
+- return nil, fmt.Errorf("references to builtin %q are not supported", obj.Name())
+- }
+-
+- // Find metadata of all packages containing the object's defining file.
+- // This may include the query pkg, and possibly other variants.
+- declPosn := safetoken.StartPosition(pkg.FileSet(), obj.Pos())
+- declURI := protocol.URIFromPath(declPosn.Filename)
+- variants, err := snapshot.MetadataForFile(ctx, declURI, false)
+- if err != nil {
+- return nil, err
+- }
+- if len(variants) == 0 {
+- return nil, fmt.Errorf("no packages for file %q", declURI) // can't happen
+- }
+- // (variants must include ITVs for reverse dependency computation below.)
+-
+- // Is object exported?
+- // If so, compute scope and targets of the global search.
+- var (
+- globalScope = make(map[PackageID]*metadata.Package) // (excludes ITVs)
+- globalTargets map[PackagePath]map[objectpath.Path]unit
+- expansions = make(map[PackageID]unit) // packages that caused search expansion
+- )
+- // TODO(adonovan): what about generic functions? Need to consider both
+- // uninstantiated and instantiated. The latter have no objectpath. Use Origin?
+- if path, err := objectpath.For(obj); err == nil && obj.Exported() {
+- pkgPath := variants[0].PkgPath // (all variants have same package path)
+- globalTargets = map[PackagePath]map[objectpath.Path]unit{
+- pkgPath: {path: {}}, // primary target
+- }
+-
+- // Compute set of (non-ITV) workspace packages.
+- // We restrict references to this subset.
+- workspace, err := snapshot.WorkspaceMetadata(ctx)
+- if err != nil {
+- return nil, err
+- }
+- workspaceMap := make(map[PackageID]*metadata.Package, len(workspace))
+- workspaceIDs := make([]PackageID, 0, len(workspace))
+- for _, mp := range workspace {
+- workspaceMap[mp.ID] = mp
+- workspaceIDs = append(workspaceIDs, mp.ID)
+- }
+-
+- // addRdeps expands the global scope to include the
+- // reverse dependencies of the specified package.
+- addRdeps := func(id PackageID, transitive bool) error {
+- rdeps, err := snapshot.ReverseDependencies(ctx, id, transitive)
+- if err != nil {
+- return err
+- }
+- for rdepID, rdep := range rdeps {
+- // Skip non-workspace packages.
+- //
+- // This means we also skip any expansion of the
+- // search that might be caused by a non-workspace
+- // package, possibly causing us to miss references
+- // to the expanded target set from workspace packages.
+- //
+- // TODO(adonovan): don't skip those expansions.
+- // The challenge is how to so without type-checking
+- // a lot of non-workspace packages not covered by
+- // the initial workspace load.
+- if _, ok := workspaceMap[rdepID]; !ok {
+- continue
+- }
+-
+- globalScope[rdepID] = rdep
+- }
+- return nil
+- }
+-
+- // How far need we search?
+- // For package-level objects, we need only search the direct importers.
+- // For fields and methods, we must search transitively.
+- transitive := obj.Pkg().Scope().Lookup(obj.Name()) != obj
+-
+- // The scope is the union of rdeps of each variant.
+- // (Each set is disjoint so there's no benefit to
+- // combining the metadata graph traversals.)
+- for _, mp := range variants {
+- if err := addRdeps(mp.ID, transitive); err != nil {
+- return nil, err
+- }
+- }
+-
+- // Is object a method?
+- //
+- // If so, expand the search so that the targets include
+- // all methods that correspond to it through interface
+- // satisfaction, and the scope includes the rdeps of
+- // the package that declares each corresponding type.
+- //
+- // 'expansions' records the packages that declared
+- // such types.
+- if recv := effectiveReceiver(obj); recv != nil {
+- if err := expandMethodSearch(ctx, snapshot, workspaceIDs, obj.(*types.Func), recv, addRdeps, globalTargets, expansions); err != nil {
+- return nil, err
+- }
+- }
+- }
+-
+- // The search functions will call report(loc) for each hit.
+- var (
+- refsMu sync.Mutex
+- refs []reference
+- )
+- report := func(loc protocol.Location, isDecl bool) {
+- ref := reference{
+- isDeclaration: isDecl,
+- location: loc,
+- pkgPath: pkg.Metadata().PkgPath,
+- }
+- refsMu.Lock()
+- refs = append(refs, ref)
+- refsMu.Unlock()
+- }
+-
+- // Loop over the variants of the declaring package,
+- // and perform both the local (in-package) and global
+- // (cross-package) searches, in parallel.
+- //
+- // TODO(adonovan): opt: support LSP reference streaming. See:
+- // - https://github.com/microsoft/vscode-languageserver-node/pull/164
+- // - https://github.com/microsoft/language-server-protocol/pull/182
+- //
+- // Careful: this goroutine must not return before group.Wait.
+- var group errgroup.Group
+-
+- // Compute local references for each variant.
+- // The target objects are identified by (URI, offset).
+- for _, mp := range variants {
+- // We want the ordinary importable package,
+- // plus any test-augmented variants, since
+- // declarations in _test.go files may change
+- // the reference of a selection, or even a
+- // field into a method or vice versa.
+- //
+- // But we don't need intermediate test variants,
+- // as their local references will be covered
+- // already by other variants.
+- if mp.IsIntermediateTestVariant() {
+- continue
+- }
+- mp := mp
+- group.Go(func() error {
+- // TODO(adonovan): opt: batch these TypeChecks.
+- pkgs, err := snapshot.TypeCheck(ctx, mp.ID)
+- if err != nil {
+- return err
+- }
+- pkg := pkgs[0]
+-
+- // Find the declaration of the corresponding
+- // object in this package based on (URI, offset).
+- pgf, err := pkg.File(declURI)
+- if err != nil {
+- return err
+- }
+- pos, err := safetoken.Pos(pgf.Tok, declPosn.Offset)
+- if err != nil {
+- return err
+- }
+- objects, _, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
+- if err != nil {
+- return err // unreachable? (probably caught earlier)
+- }
+-
+- // Report the locations of the declaration(s).
+- // TODO(adonovan): what about for corresponding methods? Add tests.
+- for _, node := range objects {
+- report(mustLocation(pgf, node), true)
+- }
+-
+- // Convert targets map to set.
+- targets := make(map[types.Object]bool)
+- for obj := range objects {
+- targets[obj] = true
+- }
+-
+- return localReferences(pkg, targets, true, report)
+- })
+- }
+-
+- // Also compute local references within packages that declare
+- // corresponding methods (see above), which expand the global search.
+- // The target objects are identified by (PkgPath, objectpath).
+- for id := range expansions {
+- group.Go(func() error {
+- // TODO(adonovan): opt: batch these TypeChecks.
+- pkgs, err := snapshot.TypeCheck(ctx, id)
+- if err != nil {
+- return err
+- }
+- pkg := pkgs[0]
+-
+- targets := make(map[types.Object]bool)
+- for objpath := range globalTargets[pkg.Metadata().PkgPath] {
+- obj, err := objectpath.Object(pkg.Types(), objpath)
+- if err != nil {
+- // No such object, because it was
+- // declared only in the test variant.
+- continue
+- }
+- targets[obj] = true
+- }
+-
+- // Don't include corresponding types or methods
+- // since expansions did that already, and we don't
+- // want (e.g.) concrete -> interface -> concrete.
+- const correspond = false
+- return localReferences(pkg, targets, correspond, report)
+- })
+- }
+-
+- // Compute global references for selected reverse dependencies.
+- group.Go(func() error {
+- var globalIDs []PackageID
+- for id := range globalScope {
+- globalIDs = append(globalIDs, id)
+- }
+- indexes, err := snapshot.References(ctx, globalIDs...)
+- if err != nil {
+- return err
+- }
+- for _, index := range indexes {
+- for _, loc := range index.Lookup(globalTargets) {
+- report(loc, false)
+- }
+- }
+- return nil
+- })
+-
+- if err := group.Wait(); err != nil {
+- return nil, err
+- }
+- return refs, nil
+-}
+-
+-// expandMethodSearch expands the scope and targets of a global search
+-// for an exported method to include all methods in the workspace
+-// that correspond to it through interface satisfaction.
+-//
+-// Each package that declares a corresponding type is added to
+-// expansions so that we can also find local references to the type
+-// within the package, which of course requires type checking.
+-//
+-// The scope is expanded by a sequence of calls (not concurrent) to addRdeps.
+-//
+-// recv is the method's effective receiver type, for method-set computations.
+-func expandMethodSearch(ctx context.Context, snapshot *cache.Snapshot, workspaceIDs []PackageID, method *types.Func, recv types.Type, addRdeps func(id PackageID, transitive bool) error, targets map[PackagePath]map[objectpath.Path]unit, expansions map[PackageID]unit) error {
+- // Compute the method-set fingerprint used as a key to the global search.
+- key, hasMethods := methodsets.KeyOf(recv)
+- if !hasMethods {
+- // The query object was method T.m, but methodset(T)={}:
+- // this indicates that ill-typed T has conflicting fields and methods.
+- // Rather than bug-report (#67978), treat the empty method set at face value.
+- return nil
+- }
+- // Search the methodset index of each package in the workspace.
+- indexes, err := snapshot.MethodSets(ctx, workspaceIDs...)
+- if err != nil {
+- return err
+- }
+- var mu sync.Mutex // guards addRdeps, targets, expansions
+- var group errgroup.Group
+- for i, index := range indexes {
+- group.Go(func() error {
+- // Consult index for matching (super/sub) methods.
+- const want = methodsets.Supertype | methodsets.Subtype
+- results := index.Search(key, want, method)
+- if len(results) == 0 {
+- return nil
+- }
+-
+- // We have discovered one or more corresponding types.
+- id := workspaceIDs[i]
+-
+- mu.Lock()
+- defer mu.Unlock()
+-
+- // Expand global search scope to include rdeps of this pkg.
+- if err := addRdeps(id, true); err != nil {
+- return err
+- }
+-
+- // Mark this package so that we search within it for
+- // local references to the additional types/methods.
+- expansions[id] = unit{}
+-
+- // Add each corresponding method the to set of global search targets.
+- for _, res := range results {
+- methodPkg := PackagePath(res.PkgPath)
+- opaths, ok := targets[methodPkg]
+- if !ok {
+- opaths = make(map[objectpath.Path]unit)
+- targets[methodPkg] = opaths
+- }
+- opaths[res.ObjectPath] = unit{}
+- }
+- return nil
+- })
+- }
+- return group.Wait()
+-}
+-
+-// localReferences traverses syntax and reports each reference to one
+-// of the target objects, or (if correspond is set) an object that
+-// corresponds to one of them via interface satisfaction.
+-func localReferences(pkg *cache.Package, targets map[types.Object]bool, correspond bool, report func(loc protocol.Location, isDecl bool)) error {
+- // If we're searching for references to a method optionally
+- // broaden the search to include references to corresponding
+- // methods of mutually assignable receiver types.
+- // (We use a slice, but objectsAt never returns >1 methods.)
+- var methodRecvs []types.Type
+- var methodName string // name of an arbitrary target, iff a method
+- if correspond {
+- for obj := range targets {
+- if t := effectiveReceiver(obj); t != nil {
+- methodRecvs = append(methodRecvs, t)
+- methodName = obj.Name()
+- }
+- }
+- }
+-
+- var msets typeutil.MethodSetCache
+-
+- // matches reports whether obj either is or corresponds to a target.
+- // (Correspondence is defined as usual for interface methods: super/subtype.)
+- matches := func(obj types.Object) bool {
+- if containsOrigin(targets, obj) {
+- return true
+- }
+- if methodRecvs != nil && obj.Name() == methodName {
+- if orecv := effectiveReceiver(obj); orecv != nil {
+- for _, mrecv := range methodRecvs {
+- if implements(&msets, orecv, mrecv) ||
+- implements(&msets, mrecv, orecv) {
+- return true
+- }
+- }
+- }
+- }
+- return false
+- }
+-
+- // Scan through syntax looking for uses of one of the target objects.
+- for _, pgf := range pkg.CompiledGoFiles() {
+- for curId := range pgf.Cursor.Preorder((*ast.Ident)(nil)) {
+- id := curId.Node().(*ast.Ident)
+- if obj, ok := pkg.TypesInfo().Uses[id]; ok && matches(obj) {
+- report(mustLocation(pgf, id), false)
+- }
+- }
+- }
+- return nil
+-}
+-
+-// effectiveReceiver returns the effective receiver type for method-set
+-// comparisons for obj, if it is a method, or nil otherwise.
+-func effectiveReceiver(obj types.Object) types.Type {
+- if fn, ok := obj.(*types.Func); ok {
+- if recv := fn.Signature().Recv(); recv != nil {
+- return methodsets.EnsurePointer(recv.Type())
+- }
+- }
+- return nil
+-}
+-
+-// objectsAt returns the non-empty set of objects denoted (def or use)
+-// by the specified position within a file syntax tree, or an error if
+-// none were found.
+-//
+-// The result may contain more than one element because all case
+-// variables of a type switch appear to be declared at the same
+-// position.
+-//
+-// Each object is mapped to the syntax node that was treated as an
+-// identifier, which is not always an ast.Ident. The second component
+-// of the result is the innermost node enclosing pos.
+-//
+-// TODO(adonovan): factor in common with referencedObject.
+-func objectsAt(info *types.Info, file *ast.File, pos token.Pos) (map[types.Object]ast.Node, ast.Node, error) {
+- path := pathEnclosingObjNode(file, pos)
+- if path == nil {
+- return nil, nil, ErrNoIdentFound
+- }
+-
+- targets := make(map[types.Object]ast.Node)
+-
+- switch leaf := path[0].(type) {
+- case *ast.Ident:
+- // If leaf represents an implicit type switch object or the type
+- // switch "assign" variable, expand to all of the type switch's
+- // implicit objects.
+- if implicits, _ := typeSwitchImplicits(info, path); len(implicits) > 0 {
+- for _, obj := range implicits {
+- targets[obj] = leaf
+- }
+- } else {
+- // For struct{T}, we prefer the defined field Var over the used TypeName.
+- obj := info.ObjectOf(leaf)
+- if obj == nil {
+- return nil, nil, fmt.Errorf("%w for %q", errNoObjectFound, leaf.Name)
+- }
+- targets[obj] = leaf
+- }
+- case *ast.ImportSpec:
+- // Look up the implicit *types.PkgName.
+- obj := info.Implicits[leaf]
+- if obj == nil {
+- return nil, nil, fmt.Errorf("%w for import %s", errNoObjectFound, metadata.UnquoteImportPath(leaf))
+- }
+- targets[obj] = leaf
+- }
+-
+- if len(targets) == 0 {
+- return nil, nil, fmt.Errorf("objectAt: internal error: no targets") // can't happen
+- }
+- return targets, path[0], nil
+-}
+-
+-// mustLocation reports the location interval a syntax node,
+-// which must belong to m.File.
+-//
+-// Safe for use only by references and implementations.
+-func mustLocation(pgf *parsego.File, n ast.Node) protocol.Location {
+- loc, err := pgf.NodeLocation(n)
+- if err != nil {
+- panic(err) // can't happen in references or implementations
+- }
+- return loc
+-}
+diff -urN a/gopls/internal/golang/rename_check.go b/gopls/internal/golang/rename_check.go
+--- a/gopls/internal/golang/rename_check.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/rename_check.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,946 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-//
+-// Taken from golang.org/x/tools/refactor/rename.
+-
+-package golang
+-
+-// This file defines the conflict-checking portion of the rename operation.
+-//
+-// The renamer works on a single package of type-checked syntax, and
+-// is called in parallel for all necessary packages in the workspace,
+-// possibly up to the transitive reverse dependencies of the
+-// declaration. Finally the union of all edits and errors is computed.
+-//
+-// Renaming one object may entail renaming of others. For example:
+-//
+-// - An embedded field couples a Var (field) and a TypeName.
+-// So, renaming either one requires renaming the other.
+-// If the initial object is an embedded field, we must add its
+-// TypeName (and its enclosing package) to the renaming set;
+-// this is easily discovered at the outset.
+-//
+-// Conversely, if the initial object is a TypeName, we must observe
+-// whether any of its references (from directly importing packages)
+-// is coincident with an embedded field Var and, if so, initiate a
+-// renaming of it.
+-//
+-// - A method of an interface type is coupled to all corresponding
+-// methods of types that are assigned to the interface (as
+-// discovered by the 'satisfy' pass). As a matter of usability, we
+-// require that such renamings be initiated from the interface
+-// method, not the concrete method.
+-
+-import (
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "path/filepath"
+- "reflect"
+- "strings"
+- "unicode"
+-
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/go/ast/edge"
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/typeparams"
+- "golang.org/x/tools/internal/typesinternal"
+- "golang.org/x/tools/refactor/satisfy"
+-)
+-
+-// errorf reports an error (e.g. conflict) and prevents file modification.
+-func (r *renamer) errorf(pos token.Pos, format string, args ...any) {
+- // Conflict error messages in the old gorename tool (whence this
+- // logic originated) contain rich information associated with
+- // multiple source lines, such as:
+- //
+- // p/a.go:1:2: renaming "x" to "y" here
+- // p/b.go:3:4: \t would cause this reference to "y"
+- // p/c.go:5:5: \t to become shadowed by this intervening declaration.
+- //
+- // Unfortunately LSP provides no means to transmit the
+- // structure of this error, so we format the positions briefly
+- // using dir/file.go where dir is the base name of the parent
+- // directory.
+-
+- var conflict strings.Builder
+-
+- // Add prefix of (truncated) position.
+- if pos != token.NoPos {
+- // TODO(adonovan): skip position of first error if it is
+- // on the same line as the renaming itself.
+- posn := safetoken.StartPosition(r.pkg.FileSet(), pos).String()
+- segments := strings.Split(filepath.ToSlash(posn), "/")
+- if n := len(segments); n > 2 {
+- segments = segments[n-2:]
+- }
+- posn = strings.Join(segments, "/")
+- fmt.Fprintf(&conflict, "%s:", posn)
+-
+- if !strings.HasPrefix(format, "\t") {
+- conflict.WriteByte(' ')
+- }
+- }
+-
+- fmt.Fprintf(&conflict, format, args...)
+- r.conflicts = append(r.conflicts, conflict.String())
+-}
+-
+-// check performs safety checks of the renaming of the 'from' object to r.to.
+-func (r *renamer) check(from types.Object) {
+- if r.objsToUpdate[from] {
+- return
+- }
+- r.objsToUpdate[from] = true
+-
+- // NB: order of conditions is important.
+- if from_, ok := from.(*types.PkgName); ok {
+- r.checkInFileBlock(from_)
+- } else if from_, ok := from.(*types.Label); ok {
+- r.checkLabel(from_)
+- } else if typesinternal.IsPackageLevel(from) {
+- r.checkInPackageBlock(from)
+- } else if v, ok := from.(*types.Var); ok && v.IsField() {
+- r.checkStructField(v)
+- } else if f, ok := from.(*types.Func); ok && recv(f) != nil {
+- r.checkMethod(f)
+- } else if isLocal(from) {
+- r.checkInLexicalScope(from)
+- } else {
+- r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n",
+- objectKind(from), from)
+- }
+-}
+-
+-// checkInFileBlock performs safety checks for renames of objects in the file block,
+-// i.e. imported package names.
+-func (r *renamer) checkInFileBlock(from *types.PkgName) {
+- // Check import name is not "init".
+- if r.to == "init" {
+- r.errorf(from.Pos(), "%q is not a valid imported package name", r.to)
+- }
+-
+- // Check for conflicts between file and package block.
+- if prev := from.Pkg().Scope().Lookup(r.to); prev != nil {
+- r.errorf(from.Pos(), "renaming this %s %q to %q would conflict",
+- objectKind(from), from.Name(), r.to)
+- r.errorf(prev.Pos(), "\twith this package member %s",
+- objectKind(prev))
+- return // since checkInPackageBlock would report redundant errors
+- }
+-
+- // Check for conflicts in lexical scope.
+- r.checkInLexicalScope(from)
+-}
+-
+-// checkInPackageBlock performs safety checks for renames of
+-// func/var/const/type objects in the package block.
+-func (r *renamer) checkInPackageBlock(from types.Object) {
+- // Check that there are no references to the name from another
+- // package if the renaming would make it unexported.
+- if typ := r.pkg.Types(); typ != from.Pkg() && ast.IsExported(r.from) && !ast.IsExported(r.to) {
+- if id := someUse(r.pkg.TypesInfo(), from); id != nil {
+- r.checkExport(id, typ, from)
+- }
+- }
+-
+- // Check that in the package block, "init" is a function, and never referenced.
+- if r.to == "init" {
+- kind := objectKind(from)
+- if kind == "func" {
+- // Reject if intra-package references to it exist.
+- for id, obj := range r.pkg.TypesInfo().Uses {
+- if obj == from {
+- r.errorf(from.Pos(),
+- "renaming this func %q to %q would make it a package initializer",
+- from.Name(), r.to)
+- r.errorf(id.Pos(), "\tbut references to it exist")
+- break
+- }
+- }
+- } else {
+- r.errorf(from.Pos(), "you cannot have a %s at package level named %q",
+- kind, r.to)
+- }
+- }
+-
+- // In the declaring package, check for conflicts between the
+- // package block and all file blocks.
+- if from.Pkg() == r.pkg.Types() {
+- for _, f := range r.pkg.Syntax() {
+- fileScope := r.pkg.TypesInfo().Scopes[f]
+- if fileScope == nil {
+- continue // type error? (golang/go#40835)
+- }
+- b, prev := fileScope.LookupParent(r.to, token.NoPos)
+- if b == fileScope {
+- r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", objectKind(from), from.Name(), r.to)
+- r.errorf(prev.Pos(), "\twith this %s", objectKind(prev))
+- return // since checkInPackageBlock would report redundant errors
+- }
+- }
+- }
+-
+- // Check for conflicts in lexical scope.
+- r.checkInLexicalScope(from)
+-}
+-
+-// checkInLexicalScope performs safety checks that a renaming does not
+-// change the lexical reference structure of the specified package.
+-//
+-// For objects in lexical scope, there are three kinds of conflicts:
+-// same-, sub-, and super-block conflicts. We will illustrate all three
+-// using this example:
+-//
+-// var x int
+-// var z int
+-//
+-// func f(y int) {
+-// print(x)
+-// print(y)
+-// }
+-//
+-// Renaming x to z encounters a "same-block conflict", because an object
+-// with the new name already exists, defined in the same lexical block
+-// as the old object.
+-//
+-// Renaming x to y encounters a "sub-block conflict", because there exists
+-// a reference to x from within (what would become) a hole in its scope.
+-// The definition of y in an (inner) sub-block would cast a shadow in
+-// the scope of the renamed variable.
+-//
+-// Renaming y to x encounters a "super-block conflict". This is the
+-// converse situation: there is an existing definition of the new name
+-// (x) in an (enclosing) super-block, and the renaming would create a
+-// hole in its scope, within which there exist references to it. The
+-// new name shadows the existing definition of x in the super-block.
+-//
+-// Removing the old name (and all references to it) is always safe, and
+-// requires no checks.
+-func (r *renamer) checkInLexicalScope(from types.Object) {
+- b := from.Parent() // the block defining the 'from' object
+- if b != nil {
+- toBlock, to := b.LookupParent(r.to, from.Parent().End())
+- if toBlock == b {
+- // same-block conflict
+- r.errorf(from.Pos(), "renaming this %s %q to %q",
+- objectKind(from), from.Name(), r.to)
+- r.errorf(to.Pos(), "\tconflicts with %s in same block",
+- objectKind(to))
+- return
+- } else if toBlock != nil {
+- // Check for super-block conflict.
+- // The name r.to is defined in a superblock.
+- // Is that name referenced from within this block?
+- forEachLexicalRef(r.pkg, to, func(id *ast.Ident, block *types.Scope) bool {
+- _, obj := block.LookupParent(from.Name(), id.Pos())
+- if obj == from {
+- // super-block conflict
+- r.errorf(from.Pos(), "renaming this %s %q to %q",
+- objectKind(from), from.Name(), r.to)
+- r.errorf(id.Pos(), "\twould shadow this reference")
+- r.errorf(to.Pos(), "\tto the %s declared here",
+- objectKind(to))
+- return false // stop
+- }
+- return true
+- })
+- }
+- }
+- // Check for sub-block conflict.
+- // Is there an intervening definition of r.to between
+- // the block defining 'from' and some reference to it?
+- forEachLexicalRef(r.pkg, from, func(id *ast.Ident, block *types.Scope) bool {
+- // Find the block that defines the found reference.
+- // It may be an ancestor.
+- fromBlock, _ := block.LookupParent(from.Name(), id.Pos())
+- // See what r.to would resolve to in the same scope.
+- toBlock, to := block.LookupParent(r.to, id.Pos())
+- if to != nil {
+- // sub-block conflict
+- if deeper(toBlock, fromBlock) {
+- r.errorf(from.Pos(), "renaming this %s %q to %q",
+- objectKind(from), from.Name(), r.to)
+- r.errorf(id.Pos(), "\twould cause this reference to become shadowed")
+- r.errorf(to.Pos(), "\tby this intervening %s definition",
+- objectKind(to))
+- return false // stop
+- }
+- }
+- return true
+- })
+-
+- // Renaming a type that is used as an embedded field
+- // requires renaming the field too. e.g.
+- // type T int // if we rename this to U..
+- // var s struct {T}
+- // print(s.T) // ...this must change too
+- if _, ok := from.(*types.TypeName); ok {
+- for id, obj := range r.pkg.TypesInfo().Uses {
+- if obj == from {
+- if field := r.pkg.TypesInfo().Defs[id]; field != nil {
+- r.check(field)
+- }
+- }
+- }
+- }
+-}
+-
+-// deeper reports whether block x is lexically deeper than y.
+-func deeper(x, y *types.Scope) bool {
+- if x == y || x == nil {
+- return false
+- } else if y == nil {
+- return true
+- } else {
+- return deeper(x.Parent(), y.Parent())
+- }
+-}
+-
+-// Scope and Position
+-//
+-// Consider a function f declared as:
+-//
+-// func f[T *U, U *T](p, q T) (r, s U) { var ( v T; w = v ); type (t *t; u t) }
+-// ^ ^ ^ ^ ^ ^
+-/// {T,U} {p,q,r,s} v w t u
+-//
+-// All objects {T, U, p, q, r, s, local} belong to the same lexical
+-// block, the function scope, which is found in types.Info.Scopes
+-// for f's FuncType. (A function body's BlockStmt does not have
+-// an associated scope; only nested BlockStmts do.)
+-//
+-// The effective scope of each object is different:
+-//
+-// - The type parameters T and U, whose constraints may refer to each
+-// other, all have a scope that starts at the beginning of the
+-// FuncDecl.Type.Func token.
+-//
+-// - The parameter and result variables {p,q,r,s} can reference the
+-// type parameters but not each other, so their scopes all start at
+-// the end of the FuncType.
+-// (Prior to go1.22 it was--incorrectly--unset; see #64295).
+-// Beware also that Scope.Innermost does not currently work correctly for
+-// type parameters: it returns the scope of the package, not the function.
+-//
+-// - Each const or var {v,w} declared within the function body has a
+-// scope that begins at the end of its ValueSpec, or after the
+-// AssignStmt for a var declared by ":=".
+-//
+-// - Each type {t,u} in the body has a scope that begins at
+-// the start of the TypeSpec, so they can be self-recursive
+-// but--unlike package-level types--not mutually recursive.
+-
+-// forEachLexicalRef calls fn(id, block) for each identifier id in package
+-// pkg that is a reference to obj in lexical scope. block is the
+-// lexical block enclosing the reference. If fn returns false the
+-// iteration is terminated and findLexicalRefs returns false.
+-func forEachLexicalRef(pkg *cache.Package, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool {
+- filter := []ast.Node{
+- (*ast.Ident)(nil),
+- (*ast.SelectorExpr)(nil),
+- (*ast.CompositeLit)(nil),
+- }
+- ok := true
+- var visit func(cur inspector.Cursor) (descend bool)
+- visit = func(cur inspector.Cursor) (descend bool) {
+- if !ok {
+- return false // bail out
+- }
+- switch n := cur.Node().(type) {
+- case *ast.Ident:
+- if pkg.TypesInfo().Uses[n] == obj {
+- block := typesinternal.EnclosingScope(pkg.TypesInfo(), cur)
+- if !fn(n, block) {
+- ok = false
+- }
+- }
+-
+- case *ast.SelectorExpr:
+- // don't visit n.Sel
+- cur.ChildAt(edge.SelectorExpr_X, -1).Inspect(filter, visit)
+- return false // don't descend
+-
+- case *ast.CompositeLit:
+- // Handle recursion ourselves for struct literals
+- // so we don't visit field identifiers.
+- tv, ok := pkg.TypesInfo().Types[n]
+- if !ok {
+- return false // don't descend
+- }
+- if is[*types.Struct](typeparams.CoreType(typeparams.Deref(tv.Type))) {
+- if n.Type != nil {
+- cur.ChildAt(edge.CompositeLit_Type, -1).Inspect(filter, visit)
+- }
+- for i, elt := range n.Elts {
+- curElt := cur.ChildAt(edge.CompositeLit_Elts, i)
+- if _, ok := elt.(*ast.KeyValueExpr); ok {
+- // skip kv.Key
+- curElt = curElt.ChildAt(edge.KeyValueExpr_Value, -1)
+- }
+- curElt.Inspect(filter, visit)
+- }
+- return false // don't descend
+- }
+- }
+- return true
+- }
+-
+- for _, pgf := range pkg.CompiledGoFiles() {
+- pgf.Cursor.Inspect(filter, visit)
+- if !ok {
+- break
+- }
+- }
+- return ok
+-}
+-
+-func (r *renamer) checkLabel(label *types.Label) {
+- // Check there are no identical labels in the function's label block.
+- // (Label blocks don't nest, so this is easy.)
+- if prev := label.Parent().Lookup(r.to); prev != nil {
+- r.errorf(label.Pos(), "renaming this label %q to %q", label.Name(), prev.Name())
+- r.errorf(prev.Pos(), "\twould conflict with this one")
+- }
+-}
+-
+-// checkStructField checks that the field renaming will not cause
+-// conflicts at its declaration, or ambiguity or changes to any selection.
+-func (r *renamer) checkStructField(from *types.Var) {
+- // If this is the declaring package, check that the struct
+- // declaration is free of field conflicts, and field/method
+- // conflicts.
+- //
+- // go/types offers no easy way to get from a field (or interface
+- // method) to its declaring struct (or interface), so we must
+- // ascend the AST.
+- if pgf, err := r.pkg.FileEnclosing(from.Pos()); err == nil {
+- path, _ := astutil.PathEnclosingInterval(pgf.File, from.Pos(), from.Pos())
+- // path matches this pattern:
+- // [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File]
+-
+- // Ascend to FieldList.
+- var i int
+- for {
+- if _, ok := path[i].(*ast.FieldList); ok {
+- break
+- }
+- i++
+- }
+- i++
+- tStruct := path[i].(*ast.StructType)
+- i++
+- // Ascend past parens (unlikely).
+- for {
+- _, ok := path[i].(*ast.ParenExpr)
+- if !ok {
+- break
+- }
+- i++
+- }
+- if spec, ok := path[i].(*ast.TypeSpec); ok {
+- // This struct is also a named type.
+- // We must check for direct (non-promoted) field/field
+- // and method/field conflicts.
+- if tname := r.pkg.TypesInfo().Defs[spec.Name]; tname != nil {
+- prev, indices, _ := types.LookupFieldOrMethod(tname.Type(), true, r.pkg.Types(), r.to)
+- if len(indices) == 1 {
+- r.errorf(from.Pos(), "renaming this field %q to %q",
+- from.Name(), r.to)
+- r.errorf(prev.Pos(), "\twould conflict with this %s",
+- objectKind(prev))
+- return // skip checkSelections to avoid redundant errors
+- }
+- }
+- } else {
+- // This struct is not a named type.
+- // We need only check for direct (non-promoted) field/field conflicts.
+- T := r.pkg.TypesInfo().Types[tStruct].Type.Underlying().(*types.Struct)
+- for i := 0; i < T.NumFields(); i++ {
+- if prev := T.Field(i); prev.Name() == r.to {
+- r.errorf(from.Pos(), "renaming this field %q to %q",
+- from.Name(), r.to)
+- r.errorf(prev.Pos(), "\twould conflict with this field")
+- return // skip checkSelections to avoid redundant errors
+- }
+- }
+- }
+- }
+-
+- // Renaming an anonymous field requires renaming the type too. e.g.
+- // print(s.T) // if we rename T to U,
+- // type T int // this and
+- // var s struct {T} // this must change too.
+- if from.Anonymous() {
+- if named, ok := from.Type().(*types.Named); ok {
+- r.check(named.Obj())
+- } else if named, ok := types.Unalias(typesinternal.Unpointer(from.Type())).(*types.Named); ok {
+- r.check(named.Obj())
+- }
+- }
+-
+- // Check integrity of existing (field and method) selections.
+- r.checkSelections(from)
+-}
+-
+-// checkSelections checks that all uses and selections that resolve to
+-// the specified object would continue to do so after the renaming.
+-func (r *renamer) checkSelections(from types.Object) {
+- pkg := r.pkg
+- typ := pkg.Types()
+- {
+- if id := someUse(pkg.TypesInfo(), from); id != nil {
+- if !r.checkExport(id, typ, from) {
+- return
+- }
+- }
+-
+- for syntax, sel := range pkg.TypesInfo().Selections {
+- // There may be extant selections of only the old
+- // name or only the new name, so we must check both.
+- // (If neither, the renaming is sound.)
+- //
+- // In both cases, we wish to compare the lengths
+- // of the implicit field path (Selection.Index)
+- // to see if the renaming would change it.
+- //
+- // If a selection that resolves to 'from', when renamed,
+- // would yield a path of the same or shorter length,
+- // this indicates ambiguity or a changed referent,
+- // analogous to same- or sub-block lexical conflict.
+- //
+- // If a selection using the name 'to' would
+- // yield a path of the same or shorter length,
+- // this indicates ambiguity or shadowing,
+- // analogous to same- or super-block lexical conflict.
+-
+- // TODO(adonovan): fix: derive from Types[syntax.X].Mode
+- // TODO(adonovan): test with pointer, value, addressable value.
+- isAddressable := true
+-
+- if sel.Obj() == from {
+- if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), r.to); obj != nil {
+- // Renaming this existing selection of
+- // 'from' may block access to an existing
+- // type member named 'to'.
+- delta := len(indices) - len(sel.Index())
+- if delta > 0 {
+- continue // no ambiguity
+- }
+- r.selectionConflict(from, delta, syntax, obj)
+- return
+- }
+- } else if sel.Obj().Name() == r.to {
+- if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), from.Name()); obj == from {
+- // Renaming 'from' may cause this existing
+- // selection of the name 'to' to change
+- // its meaning.
+- delta := len(indices) - len(sel.Index())
+- if delta > 0 {
+- continue // no ambiguity
+- }
+- r.selectionConflict(from, -delta, syntax, sel.Obj())
+- return
+- }
+- }
+- }
+- }
+-}
+-
+-func (r *renamer) selectionConflict(from types.Object, delta int, syntax *ast.SelectorExpr, obj types.Object) {
+- r.errorf(from.Pos(), "renaming this %s %q to %q",
+- objectKind(from), from.Name(), r.to)
+-
+- switch {
+- case delta < 0:
+- // analogous to sub-block conflict
+- r.errorf(syntax.Sel.Pos(),
+- "\twould change the referent of this selection")
+- r.errorf(obj.Pos(), "\tof this %s", objectKind(obj))
+- case delta == 0:
+- // analogous to same-block conflict
+- r.errorf(syntax.Sel.Pos(),
+- "\twould make this reference ambiguous")
+- r.errorf(obj.Pos(), "\twith this %s", objectKind(obj))
+- case delta > 0:
+- // analogous to super-block conflict
+- r.errorf(syntax.Sel.Pos(),
+- "\twould shadow this selection")
+- r.errorf(obj.Pos(), "\tof the %s declared here",
+- objectKind(obj))
+- }
+-}
+-
+-// checkMethod performs safety checks for renaming a method.
+-// There are three hazards:
+-// - declaration conflicts
+-// - selection ambiguity/changes
+-// - entailed renamings of assignable concrete/interface types.
+-//
+-// We reject renamings initiated at concrete methods if it would
+-// change the assignability relation. For renamings of abstract
+-// methods, we rename all methods transitively coupled to it via
+-// assignability.
+-func (r *renamer) checkMethod(from *types.Func) {
+- // e.g. error.Error
+- if from.Pkg() == nil {
+- r.errorf(from.Pos(), "you cannot rename built-in method %s", from)
+- return
+- }
+-
+- // ASSIGNABILITY: We reject renamings of concrete methods that
+- // would break a 'satisfy' constraint; but renamings of abstract
+- // methods are allowed to proceed, and we rename affected
+- // concrete and abstract methods as necessary. It is the
+- // initial method that determines the policy.
+-
+- // Check for conflict at point of declaration.
+- // Check to ensure preservation of assignability requirements.
+- R := recv(from).Type()
+- if types.IsInterface(R) {
+- // Abstract method
+-
+- // declaration
+- prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), r.to)
+- if prev != nil {
+- r.errorf(from.Pos(), "renaming this interface method %q to %q",
+- from.Name(), r.to)
+- r.errorf(prev.Pos(), "\twould conflict with this method")
+- return
+- }
+-
+- // Check all interfaces that embed this one for
+- // declaration conflicts too.
+- {
+- // Start with named interface types (better errors)
+- for _, obj := range r.pkg.TypesInfo().Defs {
+- if obj, ok := obj.(*types.TypeName); ok && types.IsInterface(obj.Type()) {
+- f, _, _ := types.LookupFieldOrMethod(
+- obj.Type(), false, from.Pkg(), from.Name())
+- if f == nil {
+- continue
+- }
+- t, _, _ := types.LookupFieldOrMethod(
+- obj.Type(), false, from.Pkg(), r.to)
+- if t == nil {
+- continue
+- }
+- r.errorf(from.Pos(), "renaming this interface method %q to %q",
+- from.Name(), r.to)
+- r.errorf(t.Pos(), "\twould conflict with this method")
+- r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name())
+- }
+- }
+-
+- // Now look at all literal interface types (includes named ones again).
+- for e, tv := range r.pkg.TypesInfo().Types {
+- if e, ok := e.(*ast.InterfaceType); ok {
+- _ = e
+- _ = tv.Type.(*types.Interface)
+- // TODO(adonovan): implement same check as above.
+- }
+- }
+- }
+-
+- // assignability
+- //
+- // Find the set of concrete or abstract methods directly
+- // coupled to abstract method 'from' by some
+- // satisfy.Constraint, and rename them too.
+- for key := range r.satisfy() {
+- // key = (lhs, rhs) where lhs is always an interface.
+-
+- lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
+- if lsel == nil {
+- continue
+- }
+- rmethods := r.msets.MethodSet(key.RHS)
+- rsel := rmethods.Lookup(from.Pkg(), from.Name())
+- if rsel == nil {
+- continue
+- }
+-
+- // If both sides have a method of this name,
+- // and one of them is m, the other must be coupled.
+- var coupled *types.Func
+- switch from {
+- case lsel.Obj():
+- coupled = rsel.Obj().(*types.Func)
+- case rsel.Obj():
+- coupled = lsel.Obj().(*types.Func)
+- default:
+- continue
+- }
+-
+- // We must treat concrete-to-interface
+- // constraints like an implicit selection C.f of
+- // each interface method I.f, and check that the
+- // renaming leaves the selection unchanged and
+- // unambiguous.
+- //
+- // Fun fact: the implicit selection of C.f
+- // type I interface{f()}
+- // type C struct{I}
+- // func (C) g()
+- // var _ I = C{} // here
+- // yields abstract method I.f. This can make error
+- // messages less than obvious.
+- //
+- if !types.IsInterface(key.RHS) {
+- // The logic below was derived from checkSelections.
+-
+- rtosel := rmethods.Lookup(from.Pkg(), r.to)
+- if rtosel != nil {
+- rto := rtosel.Obj().(*types.Func)
+- delta := len(rsel.Index()) - len(rtosel.Index())
+- if delta < 0 {
+- continue // no ambiguity
+- }
+-
+- // TODO(adonovan): record the constraint's position.
+- keyPos := token.NoPos
+-
+- r.errorf(from.Pos(), "renaming this method %q to %q",
+- from.Name(), r.to)
+- if delta == 0 {
+- // analogous to same-block conflict
+- r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous",
+- r.to, key.RHS, key.LHS)
+- r.errorf(rto.Pos(), "\twith (%s).%s",
+- recv(rto).Type(), r.to)
+- } else {
+- // analogous to super-block conflict
+- r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s",
+- r.to, key.RHS, key.LHS)
+- r.errorf(coupled.Pos(), "\tfrom (%s).%s",
+- recv(coupled).Type(), r.to)
+- r.errorf(rto.Pos(), "\tto (%s).%s",
+- recv(rto).Type(), r.to)
+- }
+- return // one error is enough
+- }
+- }
+-
+- if !r.changeMethods {
+- // This should be unreachable.
+- r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from)
+- r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled)
+- r.errorf(from.Pos(), "\tPlease file a bug report")
+- return
+- }
+-
+- // Rename the coupled method to preserve assignability.
+- r.check(coupled)
+- }
+- } else {
+- // Concrete method
+-
+- // declaration
+- prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), r.to)
+- if prev != nil && len(indices) == 1 {
+- r.errorf(from.Pos(), "renaming this method %q to %q",
+- from.Name(), r.to)
+- r.errorf(prev.Pos(), "\twould conflict with this %s",
+- objectKind(prev))
+- return
+- }
+-
+- // assignability
+- //
+- // Find the set of abstract methods coupled to concrete
+- // method 'from' by some satisfy.Constraint, and rename
+- // them too.
+- //
+- // Coupling may be indirect, e.g. I.f <-> C.f via type D.
+- //
+- // type I interface {f()}
+- // type C int
+- // type (C) f()
+- // type D struct{C}
+- // var _ I = D{}
+- //
+- for key := range r.satisfy() {
+- // key = (lhs, rhs) where lhs is always an interface.
+- if types.IsInterface(key.RHS) {
+- continue
+- }
+- rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name())
+- if rsel == nil || rsel.Obj() != from {
+- continue // rhs does not have the method
+- }
+- lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
+- if lsel == nil {
+- continue
+- }
+- imeth := lsel.Obj().(*types.Func)
+-
+- // imeth is the abstract method (e.g. I.f)
+- // and key.RHS is the concrete coupling type (e.g. D).
+- if !r.changeMethods {
+- r.errorf(from.Pos(), "renaming this method %q to %q",
+- from.Name(), r.to)
+- var pos token.Pos
+- var iface string
+-
+- I := recv(imeth).Type()
+- if named, ok := types.Unalias(I).(*types.Named); ok {
+- pos = named.Obj().Pos()
+- iface = "interface " + named.Obj().Name()
+- } else {
+- pos = from.Pos()
+- iface = I.String()
+- }
+- r.errorf(pos, "\twould make %s no longer assignable to %s",
+- key.RHS, iface)
+- r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)",
+- I, from.Name())
+- return // one error is enough
+- }
+-
+- // Rename the coupled interface method to preserve assignability.
+- r.check(imeth)
+- }
+- }
+-
+- // Check integrity of existing (field and method) selections.
+- // We skip this if there were errors above, to avoid redundant errors.
+- r.checkSelections(from)
+-}
+-
+-func (r *renamer) checkExport(id *ast.Ident, pkg *types.Package, from types.Object) bool {
+- // Reject cross-package references if r.to is unexported.
+- // (Such references may be qualified identifiers or field/method
+- // selections.)
+- if !ast.IsExported(r.to) && pkg != from.Pkg() {
+- r.errorf(from.Pos(),
+- "renaming %q to %q would make it unexported",
+- from.Name(), r.to)
+- r.errorf(id.Pos(), "\tbreaking references from packages such as %q",
+- pkg.Path())
+- return false
+- }
+- return true
+-}
+-
+-// satisfy returns the set of interface satisfaction constraints.
+-func (r *renamer) satisfy() map[satisfy.Constraint]bool {
+- if r.satisfyConstraints == nil {
+- // Compute on demand: it's expensive.
+- var f satisfy.Finder
+- pkg := r.pkg
+- {
+- // From satisfy.Finder documentation:
+- //
+- // The package must be free of type errors, and
+- // info.{Defs,Uses,Selections,Types} must have been populated by the
+- // type-checker.
+- //
+- // Only proceed if all packages have no errors.
+- if len(pkg.ParseErrors()) > 0 || len(pkg.TypeErrors()) > 0 {
+- var filename string
+- if len(pkg.ParseErrors()) > 0 {
+- err := pkg.ParseErrors()[0][0]
+- filename = filepath.Base(err.Pos.Filename)
+- } else if len(pkg.TypeErrors()) > 0 {
+- err := pkg.TypeErrors()[0]
+- filename = filepath.Base(err.Fset.File(err.Pos).Name())
+- }
+- r.errorf(token.NoPos, // we don't have a position for this error.
+- "renaming %q to %q not possible because %q in %q has errors",
+- r.from, r.to, filename, pkg.Metadata().PkgPath)
+- return nil
+- }
+- f.Find(pkg.TypesInfo(), pkg.Syntax())
+- }
+- r.satisfyConstraints = f.Result
+- }
+- return r.satisfyConstraints
+-}
+-
+-// -- helpers ----------------------------------------------------------
+-
+-// recv returns the method's receiver.
+-func recv(meth *types.Func) *types.Var {
+- return meth.Signature().Recv()
+-}
+-
+-// someUse returns an arbitrary use of obj within info.
+-func someUse(info *types.Info, obj types.Object) *ast.Ident {
+- for id, o := range info.Uses {
+- if o == obj {
+- return id
+- }
+- }
+- return nil
+-}
+-
+-func objectKind(obj types.Object) string {
+- if obj == nil {
+- return "nil object"
+- }
+- switch obj := obj.(type) {
+- case *types.PkgName:
+- return "imported package name"
+- case *types.TypeName:
+- return "type"
+- case *types.Var:
+- if obj.IsField() {
+- return "field"
+- }
+- case *types.Func:
+- if recv(obj) != nil {
+- return "method"
+- }
+- }
+- // label, func, var, const
+- return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
+-}
+-
+-// NB: for renamings, blank is not considered valid.
+-func isValidIdentifier(id string) bool {
+- if id == "" || id == "_" {
+- return false
+- }
+- for i, r := range id {
+- if !isLetter(r) && (i == 0 || !isDigit(r)) {
+- return false
+- }
+- }
+- return token.Lookup(id) == token.IDENT
+-}
+-
+-// isValidPackagePath reports whether newPath is a valid new path for the
+-// package currently at oldPath. For now, we only support renames that
+-// do not result in a package move.
+-// TODO(mkalil): support package renames with arbitrary package paths, including
+-// relative paths.
+-func isValidPackagePath(oldPath, newPath string) bool {
+- // We prompt with the full package path, but some users may delete this and
+- // just enter a package identifier, which we should still support.
+- return isValidIdentifier(newPath) || filepath.Dir(oldPath) == filepath.Dir(newPath)
+-}
+-
+-// isLocal reports whether obj is local to some function.
+-// Precondition: not a struct field or interface method.
+-func isLocal(obj types.Object) bool {
+- // [... 5=stmt 4=func 3=file 2=pkg 1=universe]
+- var depth int
+- for scope := obj.Parent(); scope != nil; scope = scope.Parent() {
+- depth++
+- }
+- return depth >= 4
+-}
+-
+-// -- Plundered from go/scanner: ---------------------------------------
+-
+-func isLetter(ch rune) bool {
+- return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
+-}
+-
+-func isDigit(ch rune) bool {
+- return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
+-}
+diff -urN a/gopls/internal/golang/rename.go b/gopls/internal/golang/rename.go
+--- a/gopls/internal/golang/rename.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/rename.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,1787 +0,0 @@
+-// Copyright 2019 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// TODO(adonovan):
+-//
+-// - method of generic concrete type -> arbitrary instances of same
+-//
+-// - make satisfy work across packages.
+-//
+-// - tests, tests, tests:
+-// - play with renamings in the k8s tree.
+-// - generics
+-// - error cases (e.g. conflicts)
+-// - renaming a symbol declared in the module cache
+-// (currently proceeds with half of the renaming!)
+-// - make sure all tests have both a local and a cross-package analogue.
+-// - look at coverage
+-// - special cases: embedded fields, interfaces, test variants,
+-// function-local things with uppercase names;
+-// packages with type errors (currently 'satisfy' rejects them),
+-// package with missing imports;
+-//
+-// - measure performance in k8s.
+-//
+-// - The original gorename tool assumed well-typedness, but the gopls feature
+-// does no such check (which actually makes it much more useful).
+-// Audit to ensure it is safe on ill-typed code.
+-//
+-// - Generics support was no doubt buggy before but incrementalization
+-// may have exacerbated it. If the problem were just about objects,
+-// defs and uses it would be fairly simple, but type assignability
+-// comes into play in the 'satisfy' check for method renamings.
+-// De-instantiating Vector[int] to Vector[T] changes its type.
+-// We need to come up with a theory for the satisfy check that
+-// works with generics, and across packages. We currently have no
+-// simple way to pass types between packages (think: objectpath for
+-// types), though presumably exportdata could be pressed into service.
+-//
+-// - FileID-based de-duplication of edits to different URIs for the same file.
+-
+-import (
+- "bytes"
+- "context"
+- "errors"
+- "fmt"
+- "go/ast"
+- "go/parser"
+- "go/printer"
+- "go/token"
+- "go/types"
+- "maps"
+- "path"
+- "path/filepath"
+- "regexp"
+- "slices"
+- "sort"
+- "strconv"
+- "strings"
+-
+- "golang.org/x/mod/modfile"
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/go/ast/inspector"
+- "golang.org/x/tools/go/types/objectpath"
+- "golang.org/x/tools/go/types/typeutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/bug"
+- "golang.org/x/tools/gopls/internal/util/cursorutil"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- internalastutil "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/diff"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/typesinternal"
+- "golang.org/x/tools/refactor/satisfy"
+-)
+-
+-// A renamer holds state of a single call to renameObj, which renames
+-// an object (or several coupled objects) within a single type-checked
+-// syntax package.
+-type renamer struct {
+- pkg *cache.Package // the syntax package in which the renaming is applied
+- objsToUpdate map[types.Object]bool // records progress of calls to check
+- conflicts []string
+- from, to string
+- satisfyConstraints map[satisfy.Constraint]bool
+- msets typeutil.MethodSetCache
+- changeMethods bool
+-}
+-
+-// A PrepareItem holds the result of a "prepare rename" operation:
+-// the source range and value of a selected identifier.
+-type PrepareItem struct {
+- Range protocol.Range
+- Text string
+-}
+-
+-// PrepareRename searches for a valid renaming at position pp.
+-//
+-// The returned usererr is intended to be displayed to the user to explain why
+-// the prepare fails. Probably we could eliminate the redundancy in returning
+-// two errors, but for now this is done defensively.
+-func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
+- ctx, done := event.Start(ctx, "golang.PrepareRename")
+- defer done()
+-
+- // Is the cursor within the package name declaration?
+- if pgf, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp); err != nil {
+- return nil, err, err
+- } else if inPackageName {
+- item, err := prepareRenamePackageName(ctx, snapshot, pgf)
+- return item, err, err
+- }
+-
+- // Ordinary (non-package) renaming.
+- //
+- // Type-check the current package, locate the reference at the position,
+- // validate the object, and report its name and range.
+- //
+- // TODO(adonovan): in all cases below, we return usererr=nil,
+- // which means we return (nil, nil) at the protocol
+- // layer. This seems like a bug, or at best an exploitation of
+- // knowledge of VSCode-specific behavior. Can we avoid that?
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, f.URI())
+- if err != nil {
+- return nil, nil, err
+- }
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- cur, ok := pgf.Cursor.FindByPos(pos, pos)
+- if !ok {
+- return nil, nil, fmt.Errorf("can't find cursor for selection")
+- }
+-
+- // Check if we're in a 'func' keyword. If so, we hijack the renaming to
+- // change the function signature.
+- if item, err := prepareRenameFuncSignature(pgf, pos, cur); err != nil {
+- return nil, nil, err
+- } else if item != nil {
+- return item, nil, nil
+- }
+-
+- targets, node, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
+- if err != nil {
+- // Check if we are renaming an ident inside its doc comment. The call to
+- // objectsAt will have returned an error in this case.
+- id := docCommentPosToIdent(pgf, pos, cur)
+- if id == nil {
+- return nil, nil, err
+- }
+- obj := pkg.TypesInfo().Defs[id]
+- if obj == nil {
+- return nil, nil, fmt.Errorf("error fetching Object for ident %q", id.Name)
+- }
+- // Change rename target to the ident.
+- targets = map[types.Object]ast.Node{obj: id}
+- node = id
+- }
+- var obj types.Object
+- for obj = range targets {
+- break // pick one arbitrarily
+- }
+- if err := checkRenamable(obj, node); err != nil {
+- return nil, nil, err
+- }
+- rng, err := pgf.NodeRange(node)
+- if err != nil {
+- return nil, nil, err
+- }
+- if _, isImport := node.(*ast.ImportSpec); isImport {
+- // We're not really renaming the import path.
+- rng.End = rng.Start
+- }
+- return &PrepareItem{
+- Range: rng,
+- Text: obj.Name(),
+- }, nil, nil
+-}
+-
+-func prepareRenamePackageName(ctx context.Context, snapshot *cache.Snapshot, pgf *parsego.File) (*PrepareItem, error) {
+- // Does the client support file renaming?
+- if !slices.Contains(snapshot.Options().SupportedResourceOperations, protocol.Rename) {
+- return nil, errors.New("can't rename package: LSP client does not support file renaming")
+- }
+-
+- // Check validity of the metadata for the file's containing package.
+- meta, err := snapshot.NarrowestMetadataForFile(ctx, pgf.URI)
+- if err != nil {
+- return nil, err
+- }
+- if meta.Name == "main" {
+- return nil, fmt.Errorf("can't rename package \"main\"")
+- }
+- if strings.HasSuffix(string(meta.Name), "_test") {
+- return nil, fmt.Errorf("can't rename x_test packages")
+- }
+- if meta.Module == nil {
+- return nil, fmt.Errorf("can't rename package: missing module information for package %q", meta.PkgPath)
+- }
+- if meta.Module.Path == string(meta.PkgPath) {
+- return nil, fmt.Errorf("can't rename package: package path %q is the same as module path %q", meta.PkgPath, meta.Module.Path)
+- }
+-
+- // Return the location of the package declaration.
+- rng, err := pgf.NodeRange(pgf.File.Name)
+- if err != nil {
+- return nil, err
+- }
+-
+- pkgName := string(meta.Name)
+- fullPath := string(meta.PkgPath)
+- text := pkgName
+- // Before displaying the full package path, verify that the PackageMove
+- // setting is enabled and that the package name matches its directory
+- // basename. Checking the value of meta.Module above ensures that the
+- // current view is either a GoMod or a GoWork view, which are the only views
+- // for which we should enable package move.
+- if snapshot.Options().PackageMove && path.Base(fullPath) == pkgName {
+- text = fullPath
+- }
+- return &PrepareItem{
+- Range: rng,
+- Text: text,
+- }, nil
+-}
+-
+-// prepareRenameFuncSignature prepares a change signature refactoring initiated
+-// through invoking a rename request at the 'func' keyword of a function
+-// declaration.
+-//
+-// The resulting text is the signature of the function, which may be edited to
+-// the new signature.
+-func prepareRenameFuncSignature(pgf *parsego.File, pos token.Pos, cursor inspector.Cursor) (*PrepareItem, error) {
+- fdecl := funcKeywordDecl(pos, cursor)
+- if fdecl == nil {
+- return nil, nil
+- }
+- ftyp := nameBlankParams(fdecl.Type)
+- var buf bytes.Buffer
+- if err := printer.Fprint(&buf, token.NewFileSet(), ftyp); err != nil { // use a new fileset so that the signature is formatted on a single line
+- return nil, err
+- }
+- rng, err := pgf.PosRange(ftyp.Func, ftyp.Func+token.Pos(len("func")))
+- if err != nil {
+- return nil, err
+- }
+- text := buf.String()
+- return &PrepareItem{
+- Range: rng,
+- Text: text,
+- }, nil
+-}
+-
+-// nameBlankParams returns a copy of ftype with blank or unnamed params
+-// assigned a unique name.
+-func nameBlankParams(ftype *ast.FuncType) *ast.FuncType {
+- ftype = internalastutil.CloneNode(ftype)
+-
+- // First, collect existing names.
+- scope := make(map[string]bool)
+- for name := range internalastutil.FlatFields(ftype.Params) {
+- if name != nil {
+- scope[name.Name] = true
+- }
+- }
+- blanks := 0
+- for name, field := range internalastutil.FlatFields(ftype.Params) {
+- if name == nil {
+- name = ast.NewIdent("_")
+- field.Names = append(field.Names, name) // ok to append
+- }
+- if name.Name == "" || name.Name == "_" {
+- for {
+- newName := fmt.Sprintf("_%d", blanks)
+- blanks++
+- if !scope[newName] {
+- name.Name = newName
+- break
+- }
+- }
+- }
+- }
+- return ftype
+-}
+-
+-// renameFuncSignature computes and applies the effective change signature
+-// operation resulting from a 'renamed' (=rewritten) signature.
+-func renameFuncSignature(ctx context.Context, pkg *cache.Package, pgf *parsego.File, pos token.Pos, snapshot *cache.Snapshot, cursor inspector.Cursor, f file.Handle, pp protocol.Position, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, error) {
+- fdecl := funcKeywordDecl(pos, cursor)
+- if fdecl == nil {
+- return nil, nil
+- }
+- ftyp := nameBlankParams(fdecl.Type)
+-
+- // Parse the user's requested new signature.
+- parsed, err := parser.ParseExpr(newName)
+- if err != nil {
+- return nil, err
+- }
+- newType, _ := parsed.(*ast.FuncType)
+- if newType == nil {
+- return nil, fmt.Errorf("parsed signature is %T, not a function type", parsed)
+- }
+-
+- // Check results, before we get into handling permutations of parameters.
+- if got, want := newType.Results.NumFields(), ftyp.Results.NumFields(); got != want {
+- return nil, fmt.Errorf("changing results not yet supported (got %d results, want %d)", got, want)
+- }
+- var resultTypes []string
+- for _, field := range internalastutil.FlatFields(ftyp.Results) {
+- resultTypes = append(resultTypes, FormatNode(token.NewFileSet(), field.Type))
+- }
+- resultIndex := 0
+- for _, field := range internalastutil.FlatFields(newType.Results) {
+- if FormatNode(token.NewFileSet(), field.Type) != resultTypes[resultIndex] {
+- return nil, fmt.Errorf("changing results not yet supported")
+- }
+- resultIndex++
+- }
+-
+- type paramInfo struct {
+- idx int
+- typ string
+- }
+- oldParams := make(map[string]paramInfo)
+- for name, field := range internalastutil.FlatFields(ftyp.Params) {
+- oldParams[name.Name] = paramInfo{
+- idx: len(oldParams),
+- typ: types.ExprString(field.Type),
+- }
+- }
+-
+- var newParams []int
+- for name, field := range internalastutil.FlatFields(newType.Params) {
+- if name == nil {
+- return nil, fmt.Errorf("need named fields")
+- }
+- info, ok := oldParams[name.Name]
+- if !ok {
+- return nil, fmt.Errorf("couldn't find name %s: adding parameters not yet supported", name)
+- }
+- if newType := types.ExprString(field.Type); newType != info.typ {
+- return nil, fmt.Errorf("changing types (%s to %s) not yet supported", info.typ, newType)
+- }
+- newParams = append(newParams, info.idx)
+- }
+-
+- rng, err := pgf.PosRange(ftyp.Func, ftyp.Func)
+- if err != nil {
+- return nil, err
+- }
+- changes, err := ChangeSignature(ctx, snapshot, pkg, pgf, rng, newParams)
+- if err != nil {
+- return nil, err
+- }
+- transposed := make(map[protocol.DocumentURI][]protocol.TextEdit)
+- for _, change := range changes {
+- transposed[change.TextDocumentEdit.TextDocument.URI] = protocol.AsTextEdits(change.TextDocumentEdit.Edits)
+- }
+- return transposed, nil
+-}
+-
+-// funcKeywordDecl returns the FuncDecl for which pos is in the 'func' keyword,
+-// if any.
+-func funcKeywordDecl(pos token.Pos, cursor inspector.Cursor) *ast.FuncDecl {
+- fdecl, _ := cursorutil.FirstEnclosing[*ast.FuncDecl](cursor)
+- if fdecl == nil {
+- return nil
+- }
+- ftyp := fdecl.Type
+- if pos < ftyp.Func || pos > ftyp.Func+token.Pos(len("func")) { // tolerate renaming immediately after 'func'
+- return nil
+- }
+- return fdecl
+-}
+-
+-// checkRenamable returns an error if the object cannot be renamed.
+-// node is the name-like syntax node from which the renaming originated.
+-func checkRenamable(obj types.Object, node ast.Node) error {
+- switch obj := obj.(type) {
+- case *types.Var:
+- // Allow renaming an embedded field only at its declaration.
+- if obj.Embedded() && node.Pos() != obj.Pos() {
+- return errors.New("an embedded field must be renamed at its declaration (since it renames the type too)")
+-
+- }
+- case *types.Builtin, *types.Nil:
+- return fmt.Errorf("%s is built in and cannot be renamed", obj.Name())
+- }
+- if obj.Pkg() == nil || obj.Pkg().Path() == "unsafe" {
+- // e.g. error.Error, unsafe.Pointer
+- return fmt.Errorf("%s is built in and cannot be renamed", obj.Name())
+- }
+- if obj.Name() == "_" {
+- return errors.New("can't rename \"_\"")
+- }
+- return nil
+-}
+-
+-// editsToDocChanges converts a map of uris to arrays of text edits to a list of document changes.
+-func editsToDocChanges(ctx context.Context, snapshot *cache.Snapshot, edits map[protocol.DocumentURI][]protocol.TextEdit) ([]protocol.DocumentChange, error) {
+- var changes []protocol.DocumentChange
+- for uri, e := range edits {
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return nil, err
+- }
+- changes = append(changes, protocol.DocumentChangeEdit(fh, e))
+- }
+- return changes, nil
+-}
+-
+-// Rename returns a map of TextEdits for each file modified when renaming a
+-// given identifier within a package and a boolean value of true for renaming
+-// package and false otherwise.
+-func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string) ([]protocol.DocumentChange, error) {
+- ctx, done := event.Start(ctx, "golang.Rename")
+- defer done()
+-
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, f.URI())
+- if err != nil {
+- return nil, err
+- }
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return nil, err
+- }
+-
+- cur, ok := pgf.Cursor.FindByPos(pos, pos)
+- if !ok {
+- return nil, fmt.Errorf("can't find cursor for selection")
+- }
+-
+- if edits, err := renameFuncSignature(ctx, pkg, pgf, pos, snapshot, cur, f, pp, newName); err != nil {
+- return nil, err
+- } else if edits != nil {
+- return editsToDocChanges(ctx, snapshot, edits)
+- }
+-
+- // Cursor within package name declaration?
+- _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
+- if err != nil {
+- return nil, err
+- }
+-
+- var editMap map[protocol.DocumentURI][]diff.Edit
+- if inPackageName {
+- countRenamePackage.Inc()
+- if !isValidPackagePath(pkg.String(), newName) {
+- return nil, fmt.Errorf("invalid package path: %q (package moves are not yet supported, see go.dev/issue/57171)", newName)
+- }
+- // Only the last element of the path is required as input for [renamePackageName].
+- newName = path.Base(newName)
+- editMap, err = renamePackageName(ctx, snapshot, f, PackageName(newName))
+- } else {
+- if !isValidIdentifier(newName) {
+- return nil, fmt.Errorf("invalid identifier to rename: %q", newName)
+- }
+- editMap, err = renameOrdinary(ctx, snapshot, f.URI(), pp, newName)
+- }
+- if err != nil {
+- return nil, err
+- }
+-
+- // Convert edits to protocol form.
+- result := make(map[protocol.DocumentURI][]protocol.TextEdit)
+- for uri, edits := range editMap {
+- // Sort and de-duplicate edits.
+- //
+- // Overlapping edits may arise in local renamings (due
+- // to type switch implicits) and globals ones (due to
+- // processing multiple package variants).
+- //
+- // We assume renaming produces diffs that are all
+- // replacements (no adjacent insertions that might
+- // become reordered) and that are either identical or
+- // non-overlapping.
+- diff.SortEdits(edits)
+- edits = slices.Compact(edits)
+-
+- // TODO(adonovan): the logic above handles repeat edits to the
+- // same file URI (e.g. as a member of package p and p_test) but
+- // is not sufficient to handle file-system level aliasing arising
+- // from symbolic or hard links. For that, we should use a
+- // robustio-FileID-keyed map.
+- // See https://go.dev/cl/457615 for example.
+- // This really occurs in practice, e.g. kubernetes has
+- // vendor/k8s.io/kubectl -> ../../staging/src/k8s.io/kubectl.
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return nil, err
+- }
+- data, err := fh.Content()
+- if err != nil {
+- return nil, err
+- }
+- m := protocol.NewMapper(uri, data)
+- textedits, err := protocol.EditsFromDiffEdits(m, edits)
+- if err != nil {
+- return nil, err
+- }
+- result[uri] = textedits
+- }
+-
+- changes, err := editsToDocChanges(ctx, snapshot, result)
+- if err != nil {
+- return nil, err
+- }
+- // Update the last component of the file's enclosing directory.
+- if inPackageName {
+- oldDir := f.URI().DirPath()
+- newDir := filepath.Join(filepath.Dir(oldDir), path.Base(newName))
+- changes = append(changes, protocol.DocumentChangeRename(
+- protocol.URIFromPath(oldDir),
+- protocol.URIFromPath(newDir)))
+- }
+- return changes, nil
+-}
+-
+-// renameOrdinary renames an ordinary (non-package) name throughout the workspace.
+-func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, pp protocol.Position, newName string) (map[protocol.DocumentURI][]diff.Edit, error) {
+- // Type-check the referring package and locate the object(s).
+- //
+- // Unlike NarrowestPackageForFile, this operation prefers the
+- // widest variant as, for non-exported identifiers, it is the
+- // only package we need. (In case you're wondering why
+- // 'references' doesn't also want the widest variant: it
+- // computes the union across all variants.)
+- mps, err := snapshot.MetadataForFile(ctx, uri, true)
+- if err != nil {
+- return nil, err
+- }
+- if len(mps) == 0 {
+- return nil, fmt.Errorf("no package metadata for file %s", uri)
+- }
+- widest := mps[len(mps)-1] // widest variant may include _test.go files
+- pkgs, err := snapshot.TypeCheck(ctx, widest.ID)
+- if err != nil {
+- return nil, err
+- }
+- pkg := pkgs[0]
+- pgf, err := pkg.File(uri)
+- if err != nil {
+- return nil, err // "can't happen"
+- }
+- pos, err := pgf.PositionPos(pp)
+- if err != nil {
+- return nil, err
+- }
+- var ok bool
+- cur, ok := pgf.Cursor.FindByPos(pos, pos) // of selected Ident or ImportSpec
+- if !ok {
+- return nil, fmt.Errorf("can't find cursor for selection")
+- }
+- targets, node, err := objectsAt(pkg.TypesInfo(), pgf.File, pos)
+- if err != nil {
+- // Check if we are renaming an ident inside its doc comment. The call to
+- // objectsAt will have returned an error in this case.
+- id := docCommentPosToIdent(pgf, pos, cur)
+- if id == nil {
+- return nil, err
+- }
+- obj := pkg.TypesInfo().Defs[id]
+- if obj == nil {
+- return nil, fmt.Errorf("error fetching types.Object for ident %q", id.Name)
+- }
+- // Change rename target to the ident.
+- targets = map[types.Object]ast.Node{obj: id}
+- }
+-
+- // Pick a representative object arbitrarily.
+- // (All share the same name, pos, and kind.)
+- var obj types.Object
+- for obj = range targets {
+- break
+- }
+- if obj.Name() == newName {
+- return nil, fmt.Errorf("old and new names are the same: %s", newName)
+- }
+- if err := checkRenamable(obj, node); err != nil {
+- return nil, err
+- }
+-
+- // This covers the case where we are renaming an embedded field at its
+- // declaration (see golang/go#45199). Perform the rename on the field's type declaration.
+- if is[*types.Var](obj) && obj.(*types.Var).Embedded() {
+- if id, ok := node.(*ast.Ident); ok {
+- // TypesInfo.Uses contains the embedded field's *types.TypeName.
+- if typeName := pkg.TypesInfo().Uses[id]; typeName != nil {
+- loc, err := ObjectLocation(ctx, pkg.FileSet(), snapshot, typeName)
+- if err != nil {
+- return nil, err
+- }
+- return renameOrdinary(ctx, snapshot, loc.URI, loc.Range.Start, newName)
+- }
+- }
+- }
+-
+- // Find objectpath, if object is exported ("" otherwise).
+- var declObjPath objectpath.Path
+- if obj.Exported() {
+- // objectpath.For requires the origin of a generic function or type, not an
+- // instantiation (a bug?).
+- //
+- // Note that unlike Funcs, TypeNames are always canonical (they are "left"
+- // of the type parameters, unlike methods).
+- switch obj0 := obj.(type) { // avoid "obj :=" since cases reassign the var
+- case *types.TypeName:
+- if _, ok := types.Unalias(obj.Type()).(*types.TypeParam); ok {
+- // As with capitalized function parameters below, type parameters are
+- // local.
+- goto skipObjectPath
+- }
+- case *types.Func:
+- obj = obj0.Origin()
+- case *types.Var:
+- // TODO(adonovan): do vars need the origin treatment too? (issue #58462)
+-
+- // Function parameter and result vars that are (unusually)
+- // capitalized are technically exported, even though they
+- // cannot be referenced, because they may affect downstream
+- // error messages. But we can safely treat them as local.
+- //
+- // This is not merely an optimization: the renameExported
+- // operation gets confused by such vars. It finds them from
+- // objectpath, the classifies them as local vars, but as
+- // they came from export data they lack syntax and the
+- // correct scope tree (issue #61294).
+- if !obj0.IsField() && !typesinternal.IsPackageLevel(obj) {
+- goto skipObjectPath
+- }
+- }
+- if path, err := objectpath.For(obj); err == nil {
+- declObjPath = path
+- }
+- skipObjectPath:
+- }
+-
+- // Nonexported? Search locally.
+- if declObjPath == "" {
+- var objects []types.Object
+- for obj := range targets {
+- objects = append(objects, obj)
+- }
+-
+- editMap, _, err := renameObjects(newName, pkg, objects...)
+- if err != nil {
+- return nil, err
+- }
+-
+- // If the selected identifier is a receiver declaration,
+- // also rename receivers of other methods of the same type
+- // that don't already have the desired name.
+- // Quietly discard edits from any that can't be renamed.
+- //
+- // We interpret renaming the receiver declaration as
+- // intent for the broader renaming; renaming a use of
+- // the receiver effects only the local renaming.
+- if id, ok := cur.Node().(*ast.Ident); ok && id.Pos() == obj.Pos() {
+- // enclosing func
+- if decl, _ := cursorutil.FirstEnclosing[*ast.FuncDecl](cur); decl != nil {
+- if decl.Recv != nil &&
+- len(decl.Recv.List) > 0 &&
+- len(decl.Recv.List[0].Names) > 0 {
+- recv := pkg.TypesInfo().Defs[decl.Recv.List[0].Names[0]]
+- if recv == obj {
+- // TODO(adonovan): simplify the above 7 lines to
+- // to "if obj.(*Var).Kind==Recv" in go1.25.
+- renameReceivers(pkg, recv.(*types.Var), newName, editMap)
+- }
+- }
+- }
+- }
+- return editMap, nil
+- }
+-
+- // Exported: search globally.
+- //
+- // For exported package-level var/const/func/type objects, the
+- // search scope is just the direct importers.
+- //
+- // For exported fields and methods, the scope is the
+- // transitive rdeps. (The exportedness of the field's struct
+- // or method's receiver is irrelevant.)
+- transitive := false
+- switch obj := obj.(type) {
+- case *types.TypeName:
+- // Renaming an exported package-level type
+- // requires us to inspect all transitive rdeps
+- // in the event that the type is embedded.
+- //
+- // TODO(adonovan): opt: this is conservative
+- // but inefficient. Instead, expand the scope
+- // of the search only if we actually encounter
+- // an embedding of the type, and only then to
+- // the rdeps of the embedding package.
+- if obj.Parent() == obj.Pkg().Scope() {
+- transitive = true
+- }
+-
+- case *types.Var:
+- if obj.IsField() {
+- transitive = true // field
+- }
+-
+- // TODO(adonovan): opt: process only packages that
+- // contain a reference (xrefs) to the target field.
+-
+- case *types.Func:
+- if obj.Signature().Recv() != nil {
+- transitive = true // method
+- }
+-
+- // It's tempting to optimize by skipping
+- // packages that don't contain a reference to
+- // the method in the xrefs index, but we still
+- // need to apply the satisfy check to those
+- // packages to find assignment statements that
+- // might expands the scope of the renaming.
+- }
+-
+- // Type-check all the packages to inspect.
+- declURI := protocol.URIFromPath(pkg.FileSet().File(obj.Pos()).Name())
+- pkgs, err = typeCheckReverseDependencies(ctx, snapshot, declURI, transitive)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Apply the renaming to the (initial) object.
+- declPkgPath := PackagePath(obj.Pkg().Path())
+- return renameExported(pkgs, declPkgPath, declObjPath, newName)
+-}
+-
+-// renameReceivers renames all receivers of methods of the same named
+-// type as recv. The edits of each successful renaming are added to
+-// editMap; the failed ones are quietly discarded.
+-func renameReceivers(pkg *cache.Package, recv *types.Var, newName string, editMap map[protocol.DocumentURI][]diff.Edit) {
+- _, named := typesinternal.ReceiverNamed(recv)
+- if named == nil {
+- return
+- }
+-
+- // Find receivers of other methods of the same named type.
+- for m := range named.Origin().Methods() {
+- recv2 := m.Signature().Recv()
+- if recv2 == recv {
+- continue // don't re-rename original receiver
+- }
+- if recv2.Name() == newName {
+- continue // no renaming needed
+- }
+- editMap2, _, err := renameObjects(newName, pkg, recv2)
+- if err != nil {
+- continue // ignore secondary failures
+- }
+-
+- // Since all methods (and their comments)
+- // are disjoint, and don't affect imports,
+- // we can safely assume that all edits are
+- // nonconflicting and disjoint.
+- for uri, edits := range editMap2 {
+- editMap[uri] = append(editMap[uri], edits...)
+- }
+- }
+-}
+-
+-// typeCheckReverseDependencies returns the type-checked packages for
+-// the reverse dependencies of all packages variants containing
+-// file declURI. The packages are in some topological order.
+-//
+-// It includes all variants (even intermediate test variants) for the
+-// purposes of computing reverse dependencies, but discards ITVs for
+-// the actual renaming work.
+-//
+-// (This neglects obscure edge cases where a _test.go file changes the
+-// selectors used only in an ITV, but life is short. Also sin must be
+-// punished.)
+-func typeCheckReverseDependencies(ctx context.Context, snapshot *cache.Snapshot, declURI protocol.DocumentURI, transitive bool) ([]*cache.Package, error) {
+- variants, err := snapshot.MetadataForFile(ctx, declURI, false)
+- if err != nil {
+- return nil, err
+- }
+- // variants must include ITVs for the reverse dependency
+- // computation, but they are filtered out before we typecheck.
+- allRdeps := make(map[PackageID]*metadata.Package)
+- for _, variant := range variants {
+- rdeps, err := snapshot.ReverseDependencies(ctx, variant.ID, transitive)
+- if err != nil {
+- return nil, err
+- }
+- allRdeps[variant.ID] = variant // include self
+- maps.Copy(allRdeps, rdeps)
+- }
+- var ids []PackageID
+- for id, meta := range allRdeps {
+- if meta.IsIntermediateTestVariant() {
+- continue
+- }
+- ids = append(ids, id)
+- }
+-
+- // Sort the packages into some topological order of the
+- // (unfiltered) metadata graph.
+- metadata.SortPostOrder(snapshot, ids)
+-
+- // Dependencies must be visited first since they can expand
+- // the search set. Ideally we would process the (filtered) set
+- // of packages in the parallel postorder of the snapshot's
+- // (unfiltered) metadata graph, but this is quite tricky
+- // without a good graph abstraction.
+- //
+- // For now, we visit packages sequentially in order of
+- // ascending height, like an inverted breadth-first search.
+- //
+- // Type checking is by far the dominant cost, so
+- // overlapping it with renaming may not be worthwhile.
+- return snapshot.TypeCheck(ctx, ids...)
+-}
+-
+-// renameExported renames the object denoted by (pkgPath, objPath)
+-// within the specified packages, along with any other objects that
+-// must be renamed as a consequence. The slice of packages must be
+-// topologically ordered.
+-func renameExported(pkgs []*cache.Package, declPkgPath PackagePath, declObjPath objectpath.Path, newName string) (map[protocol.DocumentURI][]diff.Edit, error) {
+-
+- // A target is a name for an object that is stable across types.Packages.
+- type target struct {
+- pkg PackagePath
+- obj objectpath.Path
+- }
+-
+- // Populate the initial set of target objects.
+- // This set may grow as we discover the consequences of each renaming.
+- //
+- // TODO(adonovan): strictly, each cone of reverse dependencies
+- // of a single variant should have its own target map that
+- // monotonically expands as we go up the import graph, because
+- // declarations in test files can alter the set of
+- // package-level names and change the meaning of field and
+- // method selectors. So if we parallelize the graph
+- // visitation (see above), we should also compute the targets
+- // as a union of dependencies.
+- //
+- // Or we could decide that the logic below is fast enough not
+- // to need parallelism. In small measurements so far the
+- // type-checking step is about 95% and the renaming only 5%.
+- targets := map[target]bool{{declPkgPath, declObjPath}: true}
+-
+- // Apply the renaming operation to each package.
+- allEdits := make(map[protocol.DocumentURI][]diff.Edit)
+- for _, pkg := range pkgs {
+-
+- // Resolved target objects within package pkg.
+- var objects []types.Object
+- for t := range targets {
+- p := pkg.DependencyTypes(t.pkg)
+- if p == nil {
+- continue // indirect dependency of no consequence
+- }
+- obj, err := objectpath.Object(p, t.obj)
+- if err != nil {
+- // Possibly a method or an unexported type
+- // that is not reachable through export data?
+- // See https://github.com/golang/go/issues/60789.
+- //
+- // TODO(adonovan): it seems unsatisfactory that Object
+- // should return an error for a "valid" path. Perhaps
+- // we should define such paths as invalid and make
+- // objectpath.For compute reachability?
+- // Would that be a compatible change?
+- continue
+- }
+- objects = append(objects, obj)
+- }
+- if len(objects) == 0 {
+- continue // no targets of consequence to this package
+- }
+-
+- // Apply the renaming.
+- editMap, moreObjects, err := renameObjects(newName, pkg, objects...)
+- if err != nil {
+- return nil, err
+- }
+-
+- // It is safe to concatenate the edits as they are non-overlapping
+- // (or identical, in which case they will be de-duped by Rename).
+- for uri, edits := range editMap {
+- allEdits[uri] = append(allEdits[uri], edits...)
+- }
+-
+- // Expand the search set?
+- for obj := range moreObjects {
+- objpath, err := objectpath.For(obj)
+- if err != nil {
+- continue // not exported
+- }
+- target := target{PackagePath(obj.Pkg().Path()), objpath}
+- targets[target] = true
+-
+- // TODO(adonovan): methods requires dynamic
+- // programming of the product targets x
+- // packages as any package might add a new
+- // target (from a forward dep) as a
+- // consequence, and any target might imply a
+- // new set of rdeps. See golang/go#58461.
+- }
+- }
+-
+- return allEdits, nil
+-}
+-
+-// renamePackageName renames package declarations, imports, and go.mod files.
+-func renamePackageName(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName) (map[protocol.DocumentURI][]diff.Edit, error) {
+- // Rename the package decl and all imports.
+- renamingEdits, err := renamePackage(ctx, s, f, newName)
+- if err != nil {
+- return nil, err
+- }
+-
+- oldBase := f.URI().DirPath()
+- newPkgDir := filepath.Join(filepath.Dir(oldBase), string(newName))
+-
+- // Update any affected replace directives in go.mod files.
+- // TODO(adonovan): extract into its own function.
+- //
+- // Get all workspace modules.
+- // TODO(adonovan): should this operate on all go.mod files,
+- // irrespective of whether they are included in the workspace?
+- modFiles := s.View().ModFiles()
+- for _, m := range modFiles {
+- fh, err := s.ReadFile(ctx, m)
+- if err != nil {
+- return nil, err
+- }
+- pm, err := s.ParseMod(ctx, fh)
+- if err != nil {
+- return nil, err
+- }
+-
+- modFileDir := pm.URI.DirPath()
+- affectedReplaces := []*modfile.Replace{}
+-
+- // Check if any replace directives need to be fixed
+- for _, r := range pm.File.Replace {
+- if !strings.HasPrefix(r.New.Path, "/") && !strings.HasPrefix(r.New.Path, "./") && !strings.HasPrefix(r.New.Path, "../") {
+- continue
+- }
+-
+- replacedPath := r.New.Path
+- if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") {
+- replacedPath = filepath.Join(modFileDir, r.New.Path)
+- }
+-
+- // TODO: Is there a risk of converting a '\' delimited replacement to a '/' delimited replacement?
+- if !strings.HasPrefix(filepath.ToSlash(replacedPath)+"/", filepath.ToSlash(oldBase)+"/") {
+- continue // not affected by the package renaming
+- }
+-
+- affectedReplaces = append(affectedReplaces, r)
+- }
+-
+- if len(affectedReplaces) == 0 {
+- continue
+- }
+- copied, err := modfile.Parse("", pm.Mapper.Content, nil)
+- if err != nil {
+- return nil, err
+- }
+-
+- for _, r := range affectedReplaces {
+- replacedPath := r.New.Path
+- if strings.HasPrefix(r.New.Path, "./") || strings.HasPrefix(r.New.Path, "../") {
+- replacedPath = filepath.Join(modFileDir, r.New.Path)
+- }
+-
+- suffix := strings.TrimPrefix(replacedPath, oldBase)
+-
+- newReplacedPath, err := filepath.Rel(modFileDir, newPkgDir+suffix)
+- if err != nil {
+- return nil, err
+- }
+-
+- newReplacedPath = filepath.ToSlash(newReplacedPath)
+-
+- if !strings.HasPrefix(newReplacedPath, "/") && !strings.HasPrefix(newReplacedPath, "../") {
+- newReplacedPath = "./" + newReplacedPath
+- }
+-
+- if err := copied.AddReplace(r.Old.Path, "", newReplacedPath, ""); err != nil {
+- return nil, err
+- }
+- }
+-
+- copied.Cleanup()
+- newContent, err := copied.Format()
+- if err != nil {
+- return nil, err
+- }
+-
+- // Calculate the edits to be made due to the change.
+- edits := diff.Bytes(pm.Mapper.Content, newContent)
+- renamingEdits[pm.URI] = append(renamingEdits[pm.URI], edits...)
+- }
+-
+- return renamingEdits, nil
+-}
+-
+-// renamePackage computes all workspace edits required to rename the package
+-// described by the given metadata, to newName, by renaming its package
+-// directory.
+-//
+-// It updates package clauses and import paths for the renamed package as well
+-// as any other packages affected by the directory renaming among all packages
+-// known to the snapshot.
+-func renamePackage(ctx context.Context, s *cache.Snapshot, f file.Handle, newName PackageName) (map[protocol.DocumentURI][]diff.Edit, error) {
+- if strings.HasSuffix(string(newName), "_test") {
+- return nil, fmt.Errorf("cannot rename to _test package")
+- }
+-
+- // We need metadata for the relevant package and module paths.
+- // These should be the same for all packages containing the file.
+- meta, err := s.NarrowestMetadataForFile(ctx, f.URI())
+- if err != nil {
+- return nil, err
+- }
+-
+- oldPkgPath := meta.PkgPath
+- if meta.Module == nil {
+- return nil, fmt.Errorf("cannot rename package: missing module information for package %q", meta.PkgPath)
+- }
+- modulePath := PackagePath(meta.Module.Path)
+- if modulePath == oldPkgPath {
+- return nil, fmt.Errorf("cannot rename package: module path %q is the same as the package path, so renaming the package directory would have no effect", modulePath)
+- }
+-
+- newPathPrefix := path.Join(path.Dir(string(oldPkgPath)), string(newName))
+-
+- // We must inspect all packages, not just direct importers,
+- // because we also rename subpackages, which may be unrelated.
+- // (If the renamed package imports a subpackage it may require
+- // edits to both its package and import decls.)
+- allMetadata, err := s.AllMetadata(ctx)
+- if err != nil {
+- return nil, err
+- }
+-
+- // Rename package and import declarations in all relevant packages.
+- edits := make(map[protocol.DocumentURI][]diff.Edit)
+- for _, mp := range allMetadata {
+- // Special case: x_test packages for the renamed package will not have the
+- // package path as a dir prefix, but still need their package clauses
+- // renamed.
+- if mp.PkgPath == oldPkgPath+"_test" {
+- if err := renamePackageClause(ctx, mp, s, newName+"_test", edits); err != nil {
+- return nil, err
+- }
+- continue
+- }
+-
+- // Subtle: check this condition before checking for valid module info
+- // below, because we should not fail this operation if unrelated packages
+- // lack module info.
+- if !strings.HasPrefix(string(mp.PkgPath)+"/", string(oldPkgPath)+"/") {
+- continue // not affected by the package renaming
+- }
+-
+- if mp.Module == nil {
+- // This check will always fail under Bazel.
+- return nil, fmt.Errorf("cannot rename package: missing module information for package %q", mp.PkgPath)
+- }
+-
+- if modulePath != PackagePath(mp.Module.Path) {
+- continue // don't edit imports if nested package and renaming package have different module paths
+- }
+-
+- // Renaming a package consists of changing its import path and package name.
+- suffix := strings.TrimPrefix(string(mp.PkgPath), string(oldPkgPath))
+- newPath := newPathPrefix + suffix
+-
+- pkgName := mp.Name
+- if mp.PkgPath == oldPkgPath {
+- pkgName = newName
+-
+- if err := renamePackageClause(ctx, mp, s, newName, edits); err != nil {
+- return nil, err
+- }
+- }
+-
+- imp := ImportPath(newPath) // TODO(adonovan): what if newPath has vendor/ prefix?
+- if err := renameImports(ctx, s, mp, imp, pkgName, edits); err != nil {
+- return nil, err
+- }
+- }
+-
+- return edits, nil
+-}
+-
+-// renamePackageClause computes edits renaming the package clause of files in
+-// the package described by the given metadata, to newName.
+-//
+-// Edits are written into the edits map.
+-func renamePackageClause(ctx context.Context, mp *metadata.Package, snapshot *cache.Snapshot, newName PackageName, edits map[protocol.DocumentURI][]diff.Edit) error {
+- // Rename internal references to the package in the renaming package.
+- for _, uri := range mp.CompiledGoFiles {
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return err
+- }
+- f, err := snapshot.ParseGo(ctx, fh, parsego.Header)
+- if err != nil {
+- return err
+- }
+- if f.File.Name == nil {
+- continue // no package declaration
+- }
+-
+- edit, err := posEdit(f.Tok, f.File.Name.Pos(), f.File.Name.End(), string(newName))
+- if err != nil {
+- return err
+- }
+- edits[f.URI] = append(edits[f.URI], edit)
+- }
+-
+- return nil
+-}
+-
+-// renameImports computes the set of edits to imports resulting from renaming
+-// the package described by the given metadata, to a package with import path
+-// newPath and name newName.
+-//
+-// Edits are written into the edits map.
+-func renameImports(ctx context.Context, snapshot *cache.Snapshot, mp *metadata.Package, newPath ImportPath, newName PackageName, allEdits map[protocol.DocumentURI][]diff.Edit) error {
+- rdeps, err := snapshot.ReverseDependencies(ctx, mp.ID, false) // find direct importers
+- if err != nil {
+- return err
+- }
+-
+- // Pass 1: rename import paths in import declarations.
+- needsTypeCheck := make(map[PackageID][]protocol.DocumentURI)
+- for _, rdep := range rdeps {
+- if rdep.IsIntermediateTestVariant() {
+- continue // for renaming, these variants are redundant
+- }
+-
+- for _, uri := range rdep.CompiledGoFiles {
+- fh, err := snapshot.ReadFile(ctx, uri)
+- if err != nil {
+- return err
+- }
+- f, err := snapshot.ParseGo(ctx, fh, parsego.Header)
+- if err != nil {
+- return err
+- }
+- if f.File.Name == nil {
+- continue // no package declaration
+- }
+- for _, imp := range f.File.Imports {
+- if rdep.DepsByImpPath[metadata.UnquoteImportPath(imp)] != mp.ID {
+- continue // not the import we're looking for
+- }
+-
+- // If the import does not explicitly specify
+- // a local name, then we need to invoke the
+- // type checker to locate references to update.
+- //
+- // TODO(adonovan): is this actually true?
+- // Renaming an import with a local name can still
+- // cause conflicts: shadowing of built-ins, or of
+- // package-level decls in the same or another file.
+- if imp.Name == nil {
+- needsTypeCheck[rdep.ID] = append(needsTypeCheck[rdep.ID], uri)
+- }
+-
+- // Create text edit for the import path (string literal).
+- edit, err := posEdit(f.Tok, imp.Path.Pos(), imp.Path.End(), strconv.Quote(string(newPath)))
+- if err != nil {
+- return err
+- }
+- allEdits[uri] = append(allEdits[uri], edit)
+- }
+- }
+- }
+-
+- // If the imported package's name hasn't changed,
+- // we don't need to rename references within each file.
+- if newName == mp.Name {
+- return nil
+- }
+-
+- // Pass 2: rename local name (types.PkgName) of imported
+- // package throughout one or more files of the package.
+- ids := make([]PackageID, 0, len(needsTypeCheck))
+- for id := range needsTypeCheck {
+- ids = append(ids, id)
+- }
+- pkgs, err := snapshot.TypeCheck(ctx, ids...)
+- if err != nil {
+- return err
+- }
+- for i, id := range ids {
+- pkg := pkgs[i]
+- for _, uri := range needsTypeCheck[id] {
+- f, err := pkg.File(uri)
+- if err != nil {
+- return err
+- }
+- for _, imp := range f.File.Imports {
+- if imp.Name != nil {
+- continue // has explicit local name
+- }
+- if rdeps[id].DepsByImpPath[metadata.UnquoteImportPath(imp)] != mp.ID {
+- continue // not the import we're looking for
+- }
+-
+- pkgname, ok := pkg.TypesInfo().Implicits[imp].(*types.PkgName)
+- if !ok {
+- // "can't happen", but be defensive (#71656)
+- return fmt.Errorf("internal error: missing type information for %s import at %s",
+- imp.Path.Value, safetoken.StartPosition(pkg.FileSet(), imp.Pos()))
+- }
+-
+- pkgScope := pkg.Types().Scope()
+- fileScope := pkg.TypesInfo().Scopes[f.File]
+-
+- localName := string(newName)
+- try := 0
+-
+- // Keep trying with fresh names until one succeeds.
+- //
+- // TODO(adonovan): fix: this loop is not sufficient to choose a name
+- // that is guaranteed to be conflict-free; renameObj may still fail.
+- // So the retry loop should be around renameObj, and we shouldn't
+- // bother with scopes here.
+- for fileScope.Lookup(localName) != nil || pkgScope.Lookup(localName) != nil {
+- try++
+- localName = fmt.Sprintf("%s%d", newName, try)
+- }
+-
+- // renameObj detects various conflicts, including:
+- // - new name conflicts with a package-level decl in this file;
+- // - new name hides a package-level decl in another file that
+- // is actually referenced in this file;
+- // - new name hides a built-in that is actually referenced
+- // in this file;
+- // - a reference in this file to the old package name would
+- // become shadowed by an intervening declaration that
+- // uses the new name.
+- // It returns the edits if no conflict was detected.
+- editMap, _, err := renameObjects(localName, pkg, pkgname)
+- if err != nil {
+- return err
+- }
+-
+- // If the chosen local package name matches the package's
+- // new name, delete the change that would have inserted
+- // an explicit local name, which is always the lexically
+- // first change.
+- if localName == string(newName) {
+- edits, ok := editMap[uri]
+- if !ok {
+- return fmt.Errorf("internal error: no changes for %s", uri)
+- }
+- diff.SortEdits(edits)
+- editMap[uri] = edits[1:]
+- }
+- for uri, edits := range editMap {
+- allEdits[uri] = append(allEdits[uri], edits...)
+- }
+- }
+- }
+- }
+- return nil
+-}
+-
+-// renameObjects computes the edits to the type-checked syntax package pkg
+-// required to rename a set of target objects to newName.
+-//
+-// It also returns the set of objects that were found (due to
+-// corresponding methods and embedded fields) to require renaming as a
+-// consequence of the requested renamings.
+-//
+-// It returns an error if the renaming would cause a conflict.
+-func renameObjects(newName string, pkg *cache.Package, targets ...types.Object) (map[protocol.DocumentURI][]diff.Edit, map[types.Object]bool, error) {
+- r := renamer{
+- pkg: pkg,
+- objsToUpdate: make(map[types.Object]bool),
+- from: targets[0].Name(),
+- to: newName,
+- }
+-
+- // A renaming initiated at an interface method indicates the
+- // intention to rename abstract and concrete methods as needed
+- // to preserve assignability.
+- // TODO(adonovan): pull this into the caller.
+- for _, obj := range targets {
+- if obj, ok := obj.(*types.Func); ok {
+- recv := obj.Signature().Recv()
+- if recv != nil && types.IsInterface(recv.Type().Underlying()) {
+- r.changeMethods = true
+- break
+- }
+- }
+- }
+-
+- // Check that the renaming of the identifier is ok.
+- for _, obj := range targets {
+- r.check(obj)
+- if len(r.conflicts) > 0 {
+- // Stop at first error.
+- return nil, nil, fmt.Errorf("%s", strings.Join(r.conflicts, "\n"))
+- }
+- }
+-
+- editMap, err := r.update()
+- if err != nil {
+- return nil, nil, err
+- }
+-
+- // Remove initial targets so that only 'consequences' remain.
+- for _, obj := range targets {
+- delete(r.objsToUpdate, obj)
+- }
+- return editMap, r.objsToUpdate, nil
+-}
+-
+-// Rename all references to the target objects.
+-func (r *renamer) update() (map[protocol.DocumentURI][]diff.Edit, error) {
+- result := make(map[protocol.DocumentURI][]diff.Edit)
+-
+- // shouldUpdate reports whether obj is one of (or an
+- // instantiation of one of) the target objects.
+- shouldUpdate := func(obj types.Object) bool {
+- return containsOrigin(r.objsToUpdate, obj)
+- }
+-
+- // Find all identifiers in the package that define or use a
+- // renamed object. We iterate over info as it is more efficient
+- // than calling ast.Inspect for each of r.pkg.CompiledGoFiles().
+- type item struct {
+- node ast.Node // Ident, ImportSpec (obj=PkgName), or CaseClause (obj=Var)
+- obj types.Object
+- isDef bool
+- }
+- var items []item
+- info := r.pkg.TypesInfo()
+- for id, obj := range info.Uses {
+- if shouldUpdate(obj) {
+- items = append(items, item{id, obj, false})
+- }
+- }
+- for id, obj := range info.Defs {
+- if shouldUpdate(obj) {
+- items = append(items, item{id, obj, true})
+- }
+- }
+- for node, obj := range info.Implicits {
+- if shouldUpdate(obj) {
+- switch node.(type) {
+- case *ast.ImportSpec, *ast.CaseClause:
+- items = append(items, item{node, obj, true})
+- }
+- }
+- }
+- sort.Slice(items, func(i, j int) bool {
+- return items[i].node.Pos() < items[j].node.Pos()
+- })
+-
+- // Update each identifier, and its doc comment if it is a declaration.
+- for _, item := range items {
+- pgf, err := r.pkg.FileEnclosing(item.node.Pos())
+- if err != nil {
+- bug.Reportf("edit does not belong to syntax of package %q: %v", r.pkg, err)
+- continue
+- }
+-
+- // Renaming a types.PkgName may result in the addition or removal of an identifier,
+- // so we deal with this separately.
+- if pkgName, ok := item.obj.(*types.PkgName); ok && item.isDef {
+- edit, err := r.updatePkgName(pgf, pkgName)
+- if err != nil {
+- return nil, err
+- }
+- result[pgf.URI] = append(result[pgf.URI], edit)
+- continue
+- }
+-
+- // Workaround the unfortunate lack of a Var object
+- // for x in "switch x := expr.(type) {}" by adjusting
+- // the case clause to the switch ident.
+- // This may result in duplicate edits, but we de-dup later.
+- if _, ok := item.node.(*ast.CaseClause); ok {
+- path, _ := astutil.PathEnclosingInterval(pgf.File, item.obj.Pos(), item.obj.Pos())
+- item.node = path[0].(*ast.Ident)
+- }
+-
+- // Replace the identifier with r.to.
+- edit, err := posEdit(pgf.Tok, item.node.Pos(), item.node.End(), r.to)
+- if err != nil {
+- return nil, err
+- }
+-
+- result[pgf.URI] = append(result[pgf.URI], edit)
+-
+- if !item.isDef { // uses do not have doc comments to update.
+- continue
+- }
+-
+- doc := docComment(pgf, item.node.(*ast.Ident))
+- if doc == nil {
+- continue
+- }
+-
+- // Perform the rename in doc comments declared in the original package.
+- // go/parser strips out \r\n returns from the comment text, so go
+- // line-by-line through the comment text to get the correct positions.
+- docRegexp := regexp.MustCompile(`\b` + r.from + `\b`) // valid identifier => valid regexp
+- for _, comment := range doc.List {
+- if isDirective(comment.Text) {
+- continue
+- }
+- // TODO(adonovan): why are we looping over lines?
+- // Just run the loop body once over the entire multiline comment.
+- lines := strings.Split(comment.Text, "\n")
+- tokFile := pgf.Tok
+- commentLine := safetoken.Line(tokFile, comment.Pos())
+- uri := protocol.URIFromPath(tokFile.Name())
+- for i, line := range lines {
+- lineStart := comment.Pos()
+- if i > 0 {
+- lineStart = tokFile.LineStart(commentLine + i)
+- }
+- for _, locs := range docRegexp.FindAllIndex([]byte(line), -1) {
+- edit, err := posEdit(tokFile, lineStart+token.Pos(locs[0]), lineStart+token.Pos(locs[1]), r.to)
+- if err != nil {
+- return nil, err // can't happen
+- }
+- result[uri] = append(result[uri], edit)
+- }
+- }
+- }
+- }
+-
+- docLinkEdits, err := r.updateCommentDocLinks()
+- if err != nil {
+- return nil, err
+- }
+- for uri, edits := range docLinkEdits {
+- result[uri] = append(result[uri], edits...)
+- }
+-
+- return result, nil
+-}
+-
+-// updateCommentDocLinks updates each doc comment in the package
+-// that refers to one of the renamed objects using a doc link
+-// (https://golang.org/doc/comment#doclinks) such as "[pkg.Type.Method]".
+-func (r *renamer) updateCommentDocLinks() (map[protocol.DocumentURI][]diff.Edit, error) {
+- result := make(map[protocol.DocumentURI][]diff.Edit)
+- var docRenamers []*docLinkRenamer
+- for obj := range r.objsToUpdate {
+- if _, ok := obj.(*types.PkgName); ok {
+- // The dot package name will not be referenced
+- if obj.Name() == "." {
+- continue
+- }
+-
+- docRenamers = append(docRenamers, &docLinkRenamer{
+- isDep: false,
+- isPkgOrType: true,
+- file: r.pkg.FileSet().File(obj.Pos()),
+- regexp: docLinkPattern("", "", obj.Name(), true),
+- to: r.to,
+- })
+- continue
+- }
+- if !obj.Exported() {
+- continue
+- }
+- recvName := ""
+- // Doc links can reference only exported package-level objects
+- // and methods of exported package-level named types.
+- if !typesinternal.IsPackageLevel(obj) {
+- obj, isFunc := obj.(*types.Func)
+- if !isFunc {
+- continue
+- }
+- recv := obj.Signature().Recv()
+- if recv == nil {
+- continue
+- }
+- _, named := typesinternal.ReceiverNamed(recv)
+- if named == nil {
+- continue
+- }
+- // Doc links can't reference interface methods.
+- if types.IsInterface(named.Underlying()) {
+- continue
+- }
+- name := named.Origin().Obj()
+- if !name.Exported() || !typesinternal.IsPackageLevel(name) {
+- continue
+- }
+- recvName = name.Name()
+- }
+-
+- // Qualify objects from other packages.
+- pkgName := ""
+- if r.pkg.Types() != obj.Pkg() {
+- pkgName = obj.Pkg().Name()
+- }
+- _, isTypeName := obj.(*types.TypeName)
+- docRenamers = append(docRenamers, &docLinkRenamer{
+- isDep: r.pkg.Types() != obj.Pkg(),
+- isPkgOrType: isTypeName,
+- packagePath: obj.Pkg().Path(),
+- packageName: pkgName,
+- recvName: recvName,
+- objName: obj.Name(),
+- regexp: docLinkPattern(pkgName, recvName, obj.Name(), isTypeName),
+- to: r.to,
+- })
+- }
+- for _, pgf := range r.pkg.CompiledGoFiles() {
+- for _, d := range docRenamers {
+- edits, err := d.update(pgf)
+- if err != nil {
+- return nil, err
+- }
+- if len(edits) > 0 {
+- result[pgf.URI] = append(result[pgf.URI], edits...)
+- }
+- }
+- }
+- return result, nil
+-}
+-
+-// docLinkPattern returns a regular expression that matches doclinks in comments.
+-// It has one submatch that indicates the symbol to be updated.
+-func docLinkPattern(pkgName, recvName, objName string, isPkgOrType bool) *regexp.Regexp {
+- // The doc link may contain a leading star, e.g. [*bytes.Buffer].
+- pattern := `\[\*?`
+- if pkgName != "" {
+- pattern += pkgName + `\.`
+- }
+- if recvName != "" {
+- pattern += recvName + `\.`
+- }
+- // The first submatch is object name.
+- pattern += `(` + objName + `)`
+- // If the object is a *types.TypeName or *types.PkgName, also need
+- // match the objects referenced by them, so add `(\.\w+)*`.
+- if isPkgOrType {
+- pattern += `(?:\.\w+)*`
+- }
+- // There are two type of link in comments:
+- // 1. url link. e.g. [text]: url
+- // 2. doc link. e.g. [pkg.Name]
+- // in order to only match the doc link, add `([^:]|$)` in the end.
+- pattern += `\](?:[^:]|$)`
+-
+- return regexp.MustCompile(pattern)
+-}
+-
+-// A docLinkRenamer renames doc links of forms such as these:
+-//
+-// [Func]
+-// [pkg.Func]
+-// [RecvType.Method]
+-// [*Type]
+-// [*pkg.Type]
+-// [*pkg.RecvType.Method]
+-type docLinkRenamer struct {
+- isDep bool // object is from a dependency package
+- isPkgOrType bool // object is *types.PkgName or *types.TypeName
+- packagePath string
+- packageName string // e.g. "pkg"
+- recvName string // e.g. "RecvType"
+- objName string // e.g. "Func", "Type", "Method"
+- to string // new name
+- regexp *regexp.Regexp
+-
+- file *token.File // enclosing file, if renaming *types.PkgName
+-}
+-
+-// update updates doc links in the package level comments.
+-func (r *docLinkRenamer) update(pgf *parsego.File) (result []diff.Edit, err error) {
+- if r.file != nil && r.file != pgf.Tok {
+- return nil, nil
+- }
+- pattern := r.regexp
+- // If the object is in dependency package,
+- // the imported name in the file may be different from the original package name
+- if r.isDep {
+- for _, spec := range pgf.File.Imports {
+- importPath, _ := strconv.Unquote(spec.Path.Value)
+- if importPath == r.packagePath {
+- // Ignore blank imports
+- if spec.Name == nil || spec.Name.Name == "_" || spec.Name.Name == "." {
+- continue
+- }
+- if spec.Name.Name != r.packageName {
+- pattern = docLinkPattern(spec.Name.Name, r.recvName, r.objName, r.isPkgOrType)
+- }
+- break
+- }
+- }
+- }
+-
+- var edits []diff.Edit
+- updateDocLinks := func(doc *ast.CommentGroup) error {
+- if doc != nil {
+- for _, c := range doc.List {
+- for _, locs := range pattern.FindAllStringSubmatchIndex(c.Text, -1) {
+- // The first submatch is the object name, so the locs[2:4] is the index of object name.
+- edit, err := posEdit(pgf.Tok, c.Pos()+token.Pos(locs[2]), c.Pos()+token.Pos(locs[3]), r.to)
+- if err != nil {
+- return err
+- }
+- edits = append(edits, edit)
+- }
+- }
+- }
+- return nil
+- }
+-
+- // Update package doc comments.
+- err = updateDocLinks(pgf.File.Doc)
+- if err != nil {
+- return nil, err
+- }
+- for _, decl := range pgf.File.Decls {
+- var doc *ast.CommentGroup
+- switch decl := decl.(type) {
+- case *ast.GenDecl:
+- doc = decl.Doc
+- case *ast.FuncDecl:
+- doc = decl.Doc
+- }
+- err = updateDocLinks(doc)
+- if err != nil {
+- return nil, err
+- }
+- }
+- return edits, nil
+-}
+-
+-// docComment returns the doc for an identifier within the specified file.
+-func docComment(pgf *parsego.File, id *ast.Ident) *ast.CommentGroup {
+- nodes, _ := astutil.PathEnclosingInterval(pgf.File, id.Pos(), id.End())
+- for _, node := range nodes {
+- switch decl := node.(type) {
+- case *ast.FuncDecl:
+- return decl.Doc
+- case *ast.Field:
+- return decl.Doc
+- case *ast.GenDecl:
+- return decl.Doc
+- // For {Type,Value}Spec, if the doc on the spec is absent,
+- // search for the enclosing GenDecl
+- case *ast.TypeSpec:
+- if decl.Doc != nil {
+- return decl.Doc
+- }
+- case *ast.ValueSpec:
+- if decl.Doc != nil {
+- return decl.Doc
+- }
+- case *ast.Ident:
+- case *ast.AssignStmt:
+- // *ast.AssignStmt doesn't have an associated comment group.
+- // So, we try to find a comment just before the identifier.
+-
+- // Try to find a comment group only for short variable declarations (:=).
+- if decl.Tok != token.DEFINE {
+- return nil
+- }
+-
+- identLine := safetoken.Line(pgf.Tok, id.Pos())
+- for _, comment := range nodes[len(nodes)-1].(*ast.File).Comments {
+- if comment.Pos() > id.Pos() {
+- // Comment is after the identifier.
+- continue
+- }
+-
+- lastCommentLine := safetoken.Line(pgf.Tok, comment.End())
+- if lastCommentLine+1 == identLine {
+- return comment
+- }
+- }
+- default:
+- return nil
+- }
+- }
+- return nil
+-}
+-
+-// docCommentPosToIdent returns the node whose doc comment contains pos, if any.
+-// The pos must be within an occurrence of the identifier's name, otherwise it returns nil.
+-func docCommentPosToIdent(pgf *parsego.File, pos token.Pos, cur inspector.Cursor) *ast.Ident {
+- for curId := range cur.Preorder((*ast.Ident)(nil)) {
+- id := curId.Node().(*ast.Ident)
+- if pos > id.Pos() {
+- continue // Doc comments are not located after an ident.
+- }
+- doc := docComment(pgf, id)
+- if doc == nil || !(doc.Pos() <= pos && pos < doc.End()) {
+- continue
+- }
+-
+- docRegexp := regexp.MustCompile(`\b` + id.Name + `\b`)
+- for _, comment := range doc.List {
+- if isDirective(comment.Text) || !(comment.Pos() <= pos && pos < comment.End()) {
+- continue
+- }
+- start := comment.Pos()
+- text, err := pgf.NodeText(comment)
+- if err != nil {
+- return nil
+- }
+- for _, locs := range docRegexp.FindAllIndex(text, -1) {
+- matchStart := start + token.Pos(locs[0])
+- matchEnd := start + token.Pos(locs[1])
+- if matchStart <= pos && pos <= matchEnd {
+- return id
+- }
+- }
+- }
+- }
+- return nil
+-}
+-
+-// updatePkgName returns the updates to rename a pkgName in the import spec by
+-// only modifying the package name portion of the import declaration.
+-func (r *renamer) updatePkgName(pgf *parsego.File, pkgName *types.PkgName) (diff.Edit, error) {
+- // Modify ImportSpec syntax to add or remove the Name as needed.
+- path, _ := astutil.PathEnclosingInterval(pgf.File, pkgName.Pos(), pkgName.Pos())
+- if len(path) < 2 {
+- return diff.Edit{}, fmt.Errorf("no path enclosing interval for %s", pkgName.Name())
+- }
+- spec, ok := path[1].(*ast.ImportSpec)
+- if !ok {
+- return diff.Edit{}, fmt.Errorf("failed to update PkgName for %s", pkgName.Name())
+- }
+-
+- newText := ""
+- if pkgName.Imported().Name() != r.to {
+- newText = r.to + " "
+- }
+-
+- // Replace the portion (possibly empty) of the spec before the path:
+- // local "path" or "path"
+- // -> <- -><-
+- return posEdit(pgf.Tok, spec.Pos(), spec.Path.Pos(), newText)
+-}
+-
+-// parsePackageNameDecl is a convenience function that parses and
+-// returns the package name declaration of file fh, and reports
+-// whether the position ppos lies within it.
+-//
+-// Note: also used by references.
+-func parsePackageNameDecl(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, ppos protocol.Position) (*parsego.File, bool, error) {
+- pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header)
+- if err != nil {
+- return nil, false, err
+- }
+- // Careful: because we used parsego.Header,
+- // pgf.Pos(ppos) may be beyond EOF => (0, err).
+- pos, _ := pgf.PositionPos(ppos)
+- return pgf, internalastutil.NodeContains(pgf.File.Name, pos), nil
+-}
+-
+-// posEdit returns an edit to replace the (start, end) range of tf with 'new'.
+-func posEdit(tf *token.File, start, end token.Pos, new string) (diff.Edit, error) {
+- startOffset, endOffset, err := safetoken.Offsets(tf, start, end)
+- if err != nil {
+- return diff.Edit{}, err
+- }
+- return diff.Edit{Start: startOffset, End: endOffset, New: new}, nil
+-}
+diff -urN a/gopls/internal/golang/semtok.go b/gopls/internal/golang/semtok.go
+--- a/gopls/internal/golang/semtok.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/semtok.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,937 +0,0 @@
+-// Copyright 2024 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-// This file defines the Semantic Tokens operation for Go source.
+-
+-import (
+- "bytes"
+- "context"
+- "errors"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "log"
+- "path/filepath"
+- "regexp"
+- "slices"
+- "strconv"
+- "strings"
+- "time"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/protocol/semtok"
+- "golang.org/x/tools/gopls/internal/util/bug"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/astutil"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/fmtstr"
+-)
+-
+-// semDebug enables comprehensive logging of decisions
+-// (gopls semtok foo.go > /dev/null shows log output).
+-// It should never be true in checked-in code.
+-const semDebug = false
+-
+-func SemanticTokens(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng *protocol.Range) (*protocol.SemanticTokens, error) {
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+- if err != nil {
+- return nil, err
+- }
+-
+- // Select range.
+- var start, end token.Pos
+- if rng != nil {
+- var err error
+- start, end, err = pgf.RangePos(*rng)
+- if err != nil {
+- return nil, err // e.g. invalid range
+- }
+- } else {
+- tok := pgf.Tok
+- start, end = tok.Pos(0), tok.Pos(tok.Size()) // entire file
+- }
+-
+- // Reject full semantic token requests for large files.
+- //
+- // The LSP says that errors for the semantic token requests
+- // should only be returned for exceptions (a word not
+- // otherwise defined). This code treats a too-large file as an
+- // exception. On parse errors, the code does what it can.
+- const maxFullFileSize = 100000
+- if int(end-start) > maxFullFileSize {
+- return nil, fmt.Errorf("semantic tokens: range %s too large (%d > %d)",
+- fh.URI().Path(), end-start, maxFullFileSize)
+- }
+-
+- tv := tokenVisitor{
+- ctx: ctx,
+- metadataSource: snapshot,
+- metadata: pkg.Metadata(),
+- info: pkg.TypesInfo(),
+- fset: pkg.FileSet(),
+- pkg: pkg,
+- pgf: pgf,
+- start: start,
+- end: end,
+- }
+- tv.visit()
+- return &protocol.SemanticTokens{
+- Data: semtok.Encode(
+- tv.tokens,
+- snapshot.Options().EnabledSemanticTokenTypes(),
+- snapshot.Options().EnabledSemanticTokenModifiers()),
+- ResultID: time.Now().String(), // for delta requests, but we've never seen any
+- }, nil
+-}
+-
+-type tokenVisitor struct {
+- // inputs
+- ctx context.Context // for event logging
+- metadataSource metadata.Source // used to resolve imports
+- metadata *metadata.Package
+- info *types.Info
+- fset *token.FileSet
+- pkg *cache.Package
+- pgf *parsego.File
+- start, end token.Pos // range of interest
+-
+- // working state
+- stack []ast.Node // path from root of the syntax tree
+- tokens []semtok.Token // computed sequence of semantic tokens
+-}
+-
+-func (tv *tokenVisitor) visit() {
+- f := tv.pgf.File
+- // may not be in range, but harmless
+- tv.token(f.Package, len("package"), semtok.TokKeyword)
+- if f.Name != nil {
+- tv.token(f.Name.NamePos, len(f.Name.Name), semtok.TokNamespace)
+- }
+- for _, decl := range f.Decls {
+- // Only look at the decls that overlap the range.
+- if decl.End() <= tv.start || decl.Pos() >= tv.end {
+- continue
+- }
+- ast.Inspect(decl, tv.inspect)
+- }
+-
+- // Scan all files for imported pkgs, ignore the ambiguous pkg.
+- // This is to be consistent with the behavior in [go/doc]: https://pkg.go.dev/pkg/go/doc.
+- importByName := make(map[string]*types.PkgName)
+- for _, pgf := range tv.pkg.CompiledGoFiles() {
+- for _, imp := range pgf.File.Imports {
+- if obj := tv.pkg.TypesInfo().PkgNameOf(imp); obj != nil {
+- if old, ok := importByName[obj.Name()]; ok {
+- if old != nil && old.Imported() != obj.Imported() {
+- importByName[obj.Name()] = nil // nil => ambiguous across files
+- }
+- continue
+- }
+- importByName[obj.Name()] = obj
+- }
+- }
+- }
+-
+- for _, cg := range f.Comments {
+- for _, c := range cg.List {
+- // Only look at the comment that overlap the range.
+- if c.End() <= tv.start || c.Pos() >= tv.end {
+- continue
+- }
+- tv.comment(c, importByName)
+- }
+- }
+-}
+-
+-// Matches (for example) "[F]", "[*p.T]", "[p.T.M]"
+-// unless followed by a colon (exclude url link, e.g. "[go]: https://go.dev").
+-// The first group is reference name. e.g. The first group of "[*p.T.M]" is "p.T.M".
+-var docLinkRegex = regexp.MustCompile(`\[\*?([\pL_][\pL_0-9]*(\.[\pL_][\pL_0-9]*){0,2})](?:[^:]|$)`)
+-
+-// comment emits semantic tokens for a comment.
+-// If the comment contains doc links or "go:" directives,
+-// it emits a separate token for each link or directive and
+-// each comment portion between them.
+-func (tv *tokenVisitor) comment(c *ast.Comment, importByName map[string]*types.PkgName) {
+- if strings.HasPrefix(c.Text, "//go:") {
+- tv.godirective(c)
+- return
+- }
+-
+- pkgScope := tv.pkg.Types().Scope()
+- // lookupObjects interprets the name in various forms
+- // (X, p.T, p.T.M, etc) and return the list of symbols
+- // denoted by each identifier in the dotted list.
+- lookupObjects := func(name string) (objs []types.Object) {
+- scope := pkgScope
+- if pkg, suffix, ok := strings.Cut(name, "."); ok {
+- if obj, _ := importByName[pkg]; obj != nil {
+- objs = append(objs, obj)
+- scope = obj.Imported().Scope()
+- name = suffix
+- }
+- }
+-
+- if recv, method, ok := strings.Cut(name, "."); ok {
+- obj, ok := scope.Lookup(recv).(*types.TypeName)
+- if !ok {
+- return nil
+- }
+- objs = append(objs, obj)
+- t, ok := obj.Type().(*types.Named)
+- if !ok {
+- return nil
+- }
+- m, _, _ := types.LookupFieldOrMethod(t, true, tv.pkg.Types(), method)
+- if m == nil {
+- return nil
+- }
+- objs = append(objs, m)
+- return objs
+- } else {
+- obj := scope.Lookup(name)
+- if obj == nil {
+- return nil
+- }
+- if _, ok := obj.(*types.PkgName); !ok && !obj.Exported() {
+- return nil
+- }
+- objs = append(objs, obj)
+- return objs
+-
+- }
+- }
+-
+- pos := c.Pos()
+- for line := range strings.SplitSeq(c.Text, "\n") {
+- last := 0
+-
+- for _, idx := range docLinkRegex.FindAllStringSubmatchIndex(line, -1) {
+- // The first group is the reference name. e.g. "X", "p.T", "p.T.M".
+- name := line[idx[2]:idx[3]]
+- if objs := lookupObjects(name); len(objs) > 0 {
+- if last < idx[2] {
+- tv.token(pos+token.Pos(last), idx[2]-last, semtok.TokComment)
+- }
+- offset := pos + token.Pos(idx[2])
+- for i, obj := range objs {
+- if i > 0 {
+- tv.token(offset, len("."), semtok.TokComment)
+- offset += token.Pos(len("."))
+- }
+- id, rest, _ := strings.Cut(name, ".")
+- name = rest
+- tok, mods := tv.appendObjectModifiers(nil, obj)
+- tv.token(offset, len(id), tok, mods...)
+- offset += token.Pos(len(id))
+- }
+- last = idx[3]
+- }
+- }
+- if last != len(c.Text) {
+- tv.token(pos+token.Pos(last), len(line)-last, semtok.TokComment)
+- }
+- pos += token.Pos(len(line) + 1)
+- }
+-}
+-
+-// token emits a token of the specified extent and semantics.
+-func (tv *tokenVisitor) token(start token.Pos, length int, typ semtok.Type, modifiers ...semtok.Modifier) {
+- if !start.IsValid() {
+- return
+- }
+- if length <= 0 {
+- return // vscode doesn't like 0-length Tokens
+- }
+- end := start + token.Pos(length)
+- if start >= tv.end || end <= tv.start {
+- return
+- }
+- // want a line and column from start (in LSP coordinates). Ignore line directives.
+- rng, err := tv.pgf.PosRange(start, end)
+- if err != nil {
+- event.Error(tv.ctx, "failed to convert to range", err)
+- return
+- }
+- if rng.End.Line != rng.Start.Line {
+- // this happens if users are typing at the end of the file, but report nothing
+- return
+- }
+- tv.tokens = append(tv.tokens, semtok.Token{
+- Line: rng.Start.Line,
+- Start: rng.Start.Character,
+- Len: rng.End.Character - rng.Start.Character, // (on same line)
+- Type: typ,
+- Modifiers: modifiers,
+- })
+-}
+-
+-// strStack converts the stack to a string, for debugging and error messages.
+-func (tv *tokenVisitor) strStack() string {
+- msg := []string{"["}
+- for i := len(tv.stack) - 1; i >= 0; i-- {
+- n := tv.stack[i]
+- msg = append(msg, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
+- }
+- if len(tv.stack) > 0 {
+- pos := tv.stack[len(tv.stack)-1].Pos()
+- if _, err := safetoken.Offset(tv.pgf.Tok, pos); err != nil {
+- msg = append(msg, fmt.Sprintf("invalid position %v for %s", pos, tv.pgf.URI))
+- } else {
+- posn := safetoken.Position(tv.pgf.Tok, pos)
+- msg = append(msg, fmt.Sprintf("(%s:%d,col:%d)",
+- filepath.Base(posn.Filename), posn.Line, posn.Column))
+- }
+- }
+- msg = append(msg, "]")
+- return strings.Join(msg, " ")
+-}
+-
+-// srcLine returns the source text for n (truncated at first newline).
+-func (tv *tokenVisitor) srcLine(n ast.Node) string {
+- file := tv.pgf.Tok
+- line := safetoken.Line(file, n.Pos())
+- start, err := safetoken.Offset(file, file.LineStart(line))
+- if err != nil {
+- return ""
+- }
+- end := start
+- for ; end < len(tv.pgf.Src) && tv.pgf.Src[end] != '\n'; end++ {
+-
+- }
+- return string(tv.pgf.Src[start:end])
+-}
+-
+-func (tv *tokenVisitor) inspect(n ast.Node) (descend bool) {
+- if n == nil {
+- tv.stack = tv.stack[:len(tv.stack)-1] // pop
+- return true
+- }
+- tv.stack = append(tv.stack, n) // push
+- defer func() {
+- if !descend {
+- tv.stack = tv.stack[:len(tv.stack)-1] // pop
+- }
+- }()
+-
+- switch n := n.(type) {
+- case *ast.ArrayType:
+- case *ast.AssignStmt:
+- tv.token(n.TokPos, len(n.Tok.String()), semtok.TokOperator)
+- case *ast.BasicLit:
+- if n.Kind == token.STRING {
+- if strings.Contains(n.Value, "\n") {
+- // has to be a string.
+- tv.multiline(n.Pos(), n.End(), semtok.TokString)
+- } else if !tv.formatString(n) {
+- // not a format string, color the whole as a TokString.
+- tv.token(n.Pos(), len(n.Value), semtok.TokString)
+- }
+- } else {
+- tv.token(n.Pos(), len(n.Value), semtok.TokNumber)
+- }
+- case *ast.BinaryExpr:
+- tv.token(n.OpPos, len(n.Op.String()), semtok.TokOperator)
+- case *ast.BlockStmt:
+- case *ast.BranchStmt:
+- tv.token(n.TokPos, len(n.Tok.String()), semtok.TokKeyword)
+- case *ast.CallExpr:
+- if n.Ellipsis.IsValid() {
+- tv.token(n.Ellipsis, len("..."), semtok.TokOperator)
+- }
+- case *ast.CaseClause:
+- iam := "case"
+- if n.List == nil {
+- iam = "default"
+- }
+- tv.token(n.Case, len(iam), semtok.TokKeyword)
+- case *ast.ChanType:
+- // chan | chan <- | <- chan
+- switch {
+- case n.Arrow == token.NoPos:
+- tv.token(n.Begin, len("chan"), semtok.TokKeyword)
+- case n.Arrow == n.Begin:
+- tv.token(n.Arrow, 2, semtok.TokOperator)
+- pos := tv.findKeyword("chan", n.Begin+2, n.Value.Pos())
+- tv.token(pos, len("chan"), semtok.TokKeyword)
+- case n.Arrow != n.Begin:
+- tv.token(n.Begin, len("chan"), semtok.TokKeyword)
+- tv.token(n.Arrow, 2, semtok.TokOperator)
+- }
+- case *ast.CommClause:
+- length := len("case")
+- if n.Comm == nil {
+- length = len("default")
+- }
+- tv.token(n.Case, length, semtok.TokKeyword)
+- case *ast.CompositeLit:
+- case *ast.DeclStmt:
+- case *ast.DeferStmt:
+- tv.token(n.Defer, len("defer"), semtok.TokKeyword)
+- case *ast.Ellipsis:
+- tv.token(n.Ellipsis, len("..."), semtok.TokOperator)
+- case *ast.EmptyStmt:
+- case *ast.ExprStmt:
+- case *ast.Field:
+- case *ast.FieldList:
+- case *ast.ForStmt:
+- tv.token(n.For, len("for"), semtok.TokKeyword)
+- case *ast.FuncDecl:
+- case *ast.FuncLit:
+- case *ast.FuncType:
+- if n.Func != token.NoPos {
+- tv.token(n.Func, len("func"), semtok.TokKeyword)
+- }
+- case *ast.GenDecl:
+- tv.token(n.TokPos, len(n.Tok.String()), semtok.TokKeyword)
+- case *ast.GoStmt:
+- tv.token(n.Go, len("go"), semtok.TokKeyword)
+- case *ast.Ident:
+- tv.ident(n)
+- case *ast.IfStmt:
+- tv.token(n.If, len("if"), semtok.TokKeyword)
+- if n.Else != nil {
+- // x.Body.End() or x.Body.End()+1, not that it matters
+- pos := tv.findKeyword("else", n.Body.End(), n.Else.Pos())
+- tv.token(pos, len("else"), semtok.TokKeyword)
+- }
+- case *ast.ImportSpec:
+- tv.importSpec(n)
+- return false
+- case *ast.IncDecStmt:
+- tv.token(n.TokPos, len(n.Tok.String()), semtok.TokOperator)
+- case *ast.IndexExpr:
+- case *ast.IndexListExpr:
+- case *ast.InterfaceType:
+- tv.token(n.Interface, len("interface"), semtok.TokKeyword)
+- case *ast.KeyValueExpr:
+- case *ast.LabeledStmt:
+- case *ast.MapType:
+- tv.token(n.Map, len("map"), semtok.TokKeyword)
+- case *ast.ParenExpr:
+- case *ast.RangeStmt:
+- tv.token(n.For, len("for"), semtok.TokKeyword)
+- // x.TokPos == token.NoPos is legal (for range foo {})
+- offset := n.TokPos
+- if offset == token.NoPos {
+- offset = n.For
+- }
+- pos := tv.findKeyword("range", offset, n.X.Pos())
+- tv.token(pos, len("range"), semtok.TokKeyword)
+- case *ast.ReturnStmt:
+- tv.token(n.Return, len("return"), semtok.TokKeyword)
+- case *ast.SelectStmt:
+- tv.token(n.Select, len("select"), semtok.TokKeyword)
+- case *ast.SelectorExpr:
+- case *ast.SendStmt:
+- tv.token(n.Arrow, len("<-"), semtok.TokOperator)
+- case *ast.SliceExpr:
+- case *ast.StarExpr:
+- tv.token(n.Star, len("*"), semtok.TokOperator)
+- case *ast.StructType:
+- tv.token(n.Struct, len("struct"), semtok.TokKeyword)
+- case *ast.SwitchStmt:
+- tv.token(n.Switch, len("switch"), semtok.TokKeyword)
+- case *ast.TypeAssertExpr:
+- if n.Type == nil {
+- pos := tv.findKeyword("type", n.Lparen, n.Rparen)
+- tv.token(pos, len("type"), semtok.TokKeyword)
+- }
+- case *ast.TypeSpec:
+- case *ast.TypeSwitchStmt:
+- tv.token(n.Switch, len("switch"), semtok.TokKeyword)
+- case *ast.UnaryExpr:
+- tv.token(n.OpPos, len(n.Op.String()), semtok.TokOperator)
+- case *ast.ValueSpec:
+- // things only seen with parsing or type errors, so ignore them
+- case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
+- return false
+- // not going to see these
+- case *ast.File, *ast.Package:
+- tv.errorf("implement %T %s", n, safetoken.Position(tv.pgf.Tok, n.Pos()))
+- // other things we knowingly ignore
+- case *ast.Comment, *ast.CommentGroup:
+- return false
+- default:
+- tv.errorf("failed to implement %T", n)
+- }
+- return true
+-}
+-
+-// formatString tries to report directives and string literals
+-// inside a (possible) printf-like call, it returns false and does nothing
+-// if the string is not a format string.
+-func (tv *tokenVisitor) formatString(lit *ast.BasicLit) bool {
+- if len(tv.stack) <= 1 {
+- return false
+- }
+- call, ok := tv.stack[len(tv.stack)-2].(*ast.CallExpr)
+- if !ok {
+- return false
+- }
+- lastNonVariadic, idx := formatStringAndIndex(tv.info, call)
+- if idx == -1 || lit != lastNonVariadic {
+- return false
+- }
+- format, err := strconv.Unquote(lit.Value)
+- if err != nil {
+- return false
+- }
+- if !strings.Contains(format, "%") {
+- return false
+- }
+- operations, err := fmtstr.Parse(format, idx)
+- if err != nil {
+- return false
+- }
+-
+- // It's a format string, compute interleaved sub range of directives and literals.
+- // pos tracks literal substring position within the overall BasicLit.
+- pos := lit.ValuePos
+- for _, op := range operations {
+- // Skip "%%".
+- if op.Verb.Verb == '%' {
+- continue
+- }
+- rangeStart, rangeEnd, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
+- if err != nil {
+- return false
+- }
+- // Report literal substring.
+- tv.token(pos, int(rangeStart-pos), semtok.TokString)
+- // Report formatting directive.
+- tv.token(rangeStart, int(rangeEnd-rangeStart), semtok.TokString, semtok.ModFormat)
+- pos = rangeEnd
+- }
+- // Report remaining literal substring.
+- tv.token(pos, int(lit.End()-pos), semtok.TokString)
+- return true
+-}
+-
+-func (tv *tokenVisitor) appendObjectModifiers(mods []semtok.Modifier, obj types.Object) (semtok.Type, []semtok.Modifier) {
+- if obj.Pkg() == nil {
+- mods = append(mods, semtok.ModDefaultLibrary)
+- }
+-
+- // Note: PkgName, Builtin, Label have type Invalid, which adds no modifiers.
+- mods = appendTypeModifiers(mods, obj.Type())
+-
+- switch obj := obj.(type) {
+- case *types.PkgName:
+- return semtok.TokNamespace, mods
+-
+- case *types.Builtin:
+- return semtok.TokFunction, mods
+-
+- case *types.Func:
+- if obj.Signature().Recv() != nil {
+- return semtok.TokMethod, mods
+- } else {
+- return semtok.TokFunction, mods
+- }
+-
+- case *types.TypeName:
+- if is[*types.TypeParam](types.Unalias(obj.Type())) {
+- return semtok.TokTypeParam, mods
+- }
+- return semtok.TokType, mods
+-
+- case *types.Const:
+- mods = append(mods, semtok.ModReadonly)
+- return semtok.TokVariable, mods
+-
+- case *types.Var:
+- if tv.isParam(obj.Pos()) {
+- return semtok.TokParameter, mods
+- } else {
+- return semtok.TokVariable, mods
+- }
+-
+- case *types.Label:
+- return semtok.TokLabel, mods
+-
+- case *types.Nil:
+- mods = append(mods, semtok.ModReadonly)
+- return semtok.TokVariable, mods
+- }
+-
+- panic(obj)
+-}
+-
+-// appendTypeModifiers appends optional modifiers that describe the top-level
+-// type constructor of t: "pointer", "map", etc.
+-func appendTypeModifiers(mods []semtok.Modifier, t types.Type) []semtok.Modifier {
+- // For a type parameter, don't report "interface".
+- if is[*types.TypeParam](types.Unalias(t)) {
+- return mods
+- }
+-
+- switch t := t.Underlying().(type) {
+- case *types.Interface:
+- mods = append(mods, semtok.ModInterface)
+- case *types.Struct:
+- mods = append(mods, semtok.ModStruct)
+- case *types.Signature:
+- mods = append(mods, semtok.ModSignature)
+- case *types.Pointer:
+- mods = append(mods, semtok.ModPointer)
+- case *types.Array:
+- mods = append(mods, semtok.ModArray)
+- case *types.Map:
+- mods = append(mods, semtok.ModMap)
+- case *types.Slice:
+- mods = append(mods, semtok.ModSlice)
+- case *types.Chan:
+- mods = append(mods, semtok.ModChan)
+- case *types.Basic:
+- switch t.Kind() {
+- case types.Invalid:
+- // ignore (e.g. Builtin, PkgName, Label)
+- case types.String:
+- mods = append(mods, semtok.ModString)
+- case types.Bool:
+- mods = append(mods, semtok.ModBool)
+- case types.UnsafePointer:
+- mods = append(mods, semtok.ModPointer)
+- default:
+- if t.Info()&types.IsNumeric != 0 {
+- mods = append(mods, semtok.ModNumber)
+- }
+- }
+- }
+- return mods
+-}
+-
+-func (tv *tokenVisitor) ident(id *ast.Ident) {
+- var (
+- tok semtok.Type
+- mods []semtok.Modifier
+- obj types.Object
+- ok bool
+- )
+- if obj, _ = tv.info.Defs[id]; obj != nil {
+- // definition
+- mods = append(mods, semtok.ModDefinition)
+- tok, mods = tv.appendObjectModifiers(mods, obj)
+-
+- } else if obj, ok = tv.info.Uses[id]; ok {
+- // use
+- tok, mods = tv.appendObjectModifiers(mods, obj)
+-
+- } else if tok, mods = tv.unkIdent(id); tok != "" {
+- // ok
+-
+- } else {
+- return
+- }
+-
+- // Emit a token for the identifier's extent.
+- tv.token(id.Pos(), len(id.Name), tok, mods...)
+-
+- if semDebug {
+- q := "nil"
+- if obj != nil {
+- q = fmt.Sprintf("%T", obj.Type()) // e.g. "*types.Map"
+- }
+- log.Printf(" use %s/%T/%s got %s %v (%s)",
+- id.Name, obj, q, tok, mods, tv.strStack())
+- }
+-}
+-
+-// isParam reports whether the position is that of a parameter name of
+-// an enclosing function.
+-func (tv *tokenVisitor) isParam(pos token.Pos) bool {
+- for i := len(tv.stack) - 1; i >= 0; i-- {
+- switch n := tv.stack[i].(type) {
+- case *ast.FuncDecl:
+- for _, f := range n.Type.Params.List {
+- for _, id := range f.Names {
+- if id.Pos() == pos {
+- return true
+- }
+- }
+- }
+- case *ast.FuncLit:
+- for _, f := range n.Type.Params.List {
+- for _, id := range f.Names {
+- if id.Pos() == pos {
+- return true
+- }
+- }
+- }
+- }
+- }
+- return false
+-}
+-
+-// unkIdent handles identifiers with no types.Object (neither use nor
+-// def), use the parse stack.
+-// A lot of these only happen when the package doesn't compile,
+-// but in that case it is all best-effort from the parse tree.
+-func (tv *tokenVisitor) unkIdent(id *ast.Ident) (semtok.Type, []semtok.Modifier) {
+- def := []semtok.Modifier{semtok.ModDefinition}
+- n := len(tv.stack) - 2 // parent of Ident; stack is [File ... Ident]
+- if n < 0 {
+- tv.errorf("no stack") // can't happen
+- return "", nil
+- }
+- switch parent := tv.stack[n].(type) {
+- case *ast.BinaryExpr, *ast.UnaryExpr, *ast.ParenExpr, *ast.StarExpr,
+- *ast.IncDecStmt, *ast.SliceExpr, *ast.ExprStmt, *ast.IndexExpr,
+- *ast.ReturnStmt, *ast.ChanType, *ast.SendStmt,
+- *ast.ForStmt, // possibly incomplete
+- *ast.IfStmt, /* condition */
+- *ast.KeyValueExpr, // either key or value
+- *ast.IndexListExpr:
+- return semtok.TokVariable, nil
+- case *ast.Ellipsis:
+- return semtok.TokType, nil
+- case *ast.CaseClause:
+- if n-2 >= 0 && is[ast.TypeSwitchStmt](tv.stack[n-2]) {
+- return semtok.TokType, nil
+- }
+- return semtok.TokVariable, nil
+- case *ast.ArrayType:
+- if id == parent.Len {
+- // or maybe a Type Param, but we can't just from the parse tree
+- return semtok.TokVariable, nil
+- } else {
+- return semtok.TokType, nil
+- }
+- case *ast.MapType:
+- return semtok.TokType, nil
+- case *ast.CallExpr:
+- if id == parent.Fun {
+- return semtok.TokFunction, nil
+- }
+- return semtok.TokVariable, nil
+- case *ast.SwitchStmt:
+- return semtok.TokVariable, nil
+- case *ast.TypeAssertExpr:
+- if id == parent.X {
+- return semtok.TokVariable, nil
+- } else if id == parent.Type {
+- return semtok.TokType, nil
+- }
+- case *ast.ValueSpec:
+- if slices.Contains(parent.Names, id) {
+- return semtok.TokVariable, def
+- }
+- for _, p := range parent.Values {
+- if p == id {
+- return semtok.TokVariable, nil
+- }
+- }
+- return semtok.TokType, nil
+- case *ast.SelectorExpr: // e.ti.Selections[nd] is nil, so no help
+- if n-1 >= 0 {
+- if ce, ok := tv.stack[n-1].(*ast.CallExpr); ok {
+- // ... CallExpr SelectorExpr Ident (_.x())
+- if ce.Fun == parent && parent.Sel == id {
+- return semtok.TokFunction, nil
+- }
+- }
+- }
+- return semtok.TokVariable, nil
+- case *ast.AssignStmt:
+- for _, p := range parent.Lhs {
+- // x := ..., or x = ...
+- if p == id {
+- if parent.Tok != token.DEFINE {
+- def = nil
+- }
+- return semtok.TokVariable, def // '_' in _ = ...
+- }
+- }
+- // RHS, = x
+- return semtok.TokVariable, nil
+- case *ast.TypeSpec: // it's a type if it is either the Name or the Type
+- if id == parent.Type {
+- def = nil
+- }
+- return semtok.TokType, def
+- case *ast.Field:
+- // ident could be type in a field, or a method in an interface type, or a variable
+- if id == parent.Type {
+- return semtok.TokType, nil
+- }
+- if n > 2 &&
+- is[*ast.InterfaceType](tv.stack[n-2]) &&
+- is[*ast.FieldList](tv.stack[n-1]) {
+-
+- return semtok.TokMethod, def
+- }
+- return semtok.TokVariable, nil
+- case *ast.LabeledStmt:
+- if id == parent.Label {
+- return semtok.TokLabel, def
+- }
+- case *ast.BranchStmt:
+- if id == parent.Label {
+- return semtok.TokLabel, nil
+- }
+- case *ast.CompositeLit:
+- if parent.Type == id {
+- return semtok.TokType, nil
+- }
+- return semtok.TokVariable, nil
+- case *ast.RangeStmt:
+- if parent.Tok != token.DEFINE {
+- def = nil
+- }
+- return semtok.TokVariable, def
+- case *ast.FuncDecl:
+- return semtok.TokFunction, def
+- default:
+- tv.errorf("%T unexpected: %s %s%q", parent, id.Name, tv.strStack(), tv.srcLine(id))
+- }
+- return "", nil
+-}
+-
+-// multiline emits a multiline token (`string` or /*comment*/).
+-func (tv *tokenVisitor) multiline(start, end token.Pos, tok semtok.Type) {
+- // TODO(adonovan): test with non-ASCII.
+-
+- f := tv.fset.File(start)
+- // the hard part is finding the lengths of lines. include the \n
+- length := func(line int) int {
+- n := f.LineStart(line)
+- if line >= f.LineCount() {
+- return f.Size() - int(n)
+- }
+- return int(f.LineStart(line+1) - n)
+- }
+- spos := safetoken.StartPosition(tv.fset, start)
+- epos := safetoken.EndPosition(tv.fset, end)
+- sline := spos.Line
+- eline := epos.Line
+- // first line is from spos.Column to end
+- tv.token(start, length(sline)-spos.Column, tok) // leng(sline)-1 - (spos.Column-1)
+- for i := sline + 1; i < eline; i++ {
+- // intermediate lines are from 1 to end
+- tv.token(f.LineStart(i), length(i)-1, tok) // avoid the newline
+- }
+- // last line is from 1 to epos.Column
+- tv.token(f.LineStart(eline), epos.Column-1, tok) // columns are 1-based
+-}
+-
+-// findKeyword returns the position of a keyword by searching within
+-// the specified range, for when it cannot be exactly known from the AST.
+-// It returns NoPos if the keyword was not present in the source due to parse error.
+-func (tv *tokenVisitor) findKeyword(keyword string, start, end token.Pos) token.Pos {
+- // TODO(adonovan): use safetoken.Offset.
+- offset := int(start) - tv.pgf.Tok.Base()
+- last := int(end) - tv.pgf.Tok.Base()
+- buf := tv.pgf.Src
+- idx := bytes.Index(buf[offset:last], []byte(keyword))
+- if idx < 0 {
+- // Ill-formed code may form syntax trees without their usual tokens.
+- // For example, "type _ <-<-chan int" parses as <-chan (chan int),
+- // with two nested ChanTypes but only one chan keyword.
+- return token.NoPos
+- }
+- return start + token.Pos(idx)
+-}
+-
+-func (tv *tokenVisitor) importSpec(spec *ast.ImportSpec) {
+- // a local package name or the last component of the Path
+- if spec.Name != nil {
+- name := spec.Name.String()
+- if name != "_" && name != "." {
+- tv.token(spec.Name.Pos(), len(name), semtok.TokNamespace)
+- }
+- return // don't mark anything for . or _
+- }
+- importPath := metadata.UnquoteImportPath(spec)
+- if importPath == "" {
+- return
+- }
+- // Import strings are implementation defined. Try to match with parse information.
+- depID := tv.metadata.DepsByImpPath[importPath]
+- if depID == "" {
+- return
+- }
+- depMD := tv.metadataSource.Metadata(depID)
+- if depMD == nil {
+- // unexpected, but impact is that maybe some import is not colored
+- return
+- }
+- // Check whether the original literal contains the package's declared name.
+- j := strings.LastIndex(spec.Path.Value, string(depMD.Name))
+- if j < 0 {
+- // Package name does not match import path, so there is nothing to report.
+- return
+- }
+- // Report virtual declaration at the position of the substring.
+- start := spec.Path.Pos() + token.Pos(j)
+- tv.token(start, len(depMD.Name), semtok.TokNamespace)
+-}
+-
+-// errorf logs an error and reports a bug.
+-func (tv *tokenVisitor) errorf(format string, args ...any) {
+- msg := fmt.Sprintf(format, args...)
+- bug.Report(msg)
+- event.Error(tv.ctx, tv.strStack(), errors.New(msg))
+-}
+-
+-var godirectives = map[string]struct{}{
+- // https://pkg.go.dev/cmd/compile
+- "noescape": {},
+- "uintptrescapes": {},
+- "noinline": {},
+- "norace": {},
+- "nosplit": {},
+- "linkname": {},
+-
+- // https://pkg.go.dev/go/build
+- "build": {},
+- "binary-only-package": {},
+- "embed": {},
+-}
+-
+-// Tokenize godirective at the start of the comment c, if any, and the surrounding comment.
+-// If there is any failure, emits the entire comment as a TokComment token.
+-// Directives are highlighted as-is, even if used incorrectly. Typically there are
+-// dedicated analyzers that will warn about misuse.
+-func (tv *tokenVisitor) godirective(c *ast.Comment) {
+- // First check if '//go:directive args...' is a valid directive.
+- directive, args, _ := strings.Cut(c.Text, " ")
+- kind, _ := stringsCutPrefix(directive, "//go:")
+- if _, ok := godirectives[kind]; !ok {
+- // Unknown 'go:' directive.
+- tv.token(c.Pos(), len(c.Text), semtok.TokComment)
+- return
+- }
+-
+- // Make the 'go:directive' part stand out, the rest is comments.
+- tv.token(c.Pos(), len("//"), semtok.TokComment)
+-
+- directiveStart := c.Pos() + token.Pos(len("//"))
+- tv.token(directiveStart, len(directive[len("//"):]), semtok.TokNamespace)
+-
+- if len(args) > 0 {
+- tailStart := c.Pos() + token.Pos(len(directive)+len(" "))
+- tv.token(tailStart, len(args), semtok.TokComment)
+- }
+-}
+-
+-// Go 1.20 strings.CutPrefix.
+-func stringsCutPrefix(s, prefix string) (after string, found bool) {
+- if !strings.HasPrefix(s, prefix) {
+- return s, false
+- }
+- return s[len(prefix):], true
+-}
+-
+-func is[T any](x any) bool {
+- _, ok := x.(T)
+- return ok
+-}
+diff -urN a/gopls/internal/golang/signature_help.go b/gopls/internal/golang/signature_help.go
+--- a/gopls/internal/golang/signature_help.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/signature_help.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,216 +0,0 @@
+-// Copyright 2018 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "strings"
+-
+- "golang.org/x/tools/go/ast/astutil"
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/file"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/settings"
+- "golang.org/x/tools/internal/event"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-// SignatureHelp returns information about the signature of the innermost
+-// function call enclosing the position, or nil if there is none.
+-func SignatureHelp(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, params *protocol.SignatureHelpParams) (*protocol.SignatureInformation, error) {
+- ctx, done := event.Start(ctx, "golang.SignatureHelp")
+- defer done()
+-
+- // We need full type-checking here, as we must type-check function bodies in
+- // order to provide signature help at the requested position.
+- pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
+- if err != nil {
+- return nil, fmt.Errorf("getting file for SignatureHelp: %w", err)
+- }
+- pos, err := pgf.PositionPos(params.Position)
+- if err != nil {
+- return nil, err
+- }
+- // Find a call expression surrounding the query position.
+- var callExpr *ast.CallExpr
+- path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
+- if path == nil {
+- return nil, fmt.Errorf("cannot find node enclosing position")
+- }
+- info := pkg.TypesInfo()
+- var fnval ast.Expr
+-loop:
+- for i, node := range path {
+- switch node := node.(type) {
+- case *ast.Ident:
+- // If the selected text is a function/method Ident or SelectorExpr,
+- // even one not in function call position,
+- // show help for its signature. Example:
+- // once.Do(initialize⁁)
+- // should show help for initialize, not once.Do.
+- if t := info.TypeOf(node); t != nil &&
+- info.Defs[node] == nil &&
+- is[*types.Signature](t.Underlying()) {
+- if sel, ok := path[i+1].(*ast.SelectorExpr); ok && sel.Sel == node {
+- fnval = sel // e.g. fmt.Println⁁
+- } else {
+- fnval = node
+- }
+- break loop
+- }
+- case *ast.CallExpr:
+- // Beware: the ')' may be missing.
+- if node.Lparen <= pos && pos <= node.Rparen {
+- callExpr = node
+- fnval = callExpr.Fun
+- break loop
+- }
+- case *ast.FuncLit, *ast.FuncType, *ast.CompositeLit:
+- // The user is within an anonymous function or
+- // a composite literal, which may be the argument
+- // to the *ast.CallExpr.
+- // Don't show signature help in this case.
+- return nil, nil
+- case *ast.BasicLit:
+- // golang/go#43397: don't offer signature help when the user is typing
+- // in a string literal unless it was manually invoked or help is already active.
+- if node.Kind == token.STRING &&
+- (params.Context == nil || (params.Context.TriggerKind != protocol.SigInvoked && !params.Context.IsRetrigger)) {
+- return nil, nil
+- }
+- }
+- }
+-
+- if fnval == nil {
+- return nil, nil
+- }
+-
+- // Get the type information for the function being called.
+- var sig *types.Signature
+- if tv, ok := info.Types[fnval]; !ok {
+- return nil, fmt.Errorf("cannot get type for Fun %[1]T (%[1]v)", fnval)
+- } else if tv.IsType() {
+- return nil, nil // a conversion, not a call
+- } else if sig, ok = tv.Type.Underlying().(*types.Signature); !ok {
+- return nil, fmt.Errorf("call operand is not a func or type: %[1]T (%[1]v)", fnval)
+- }
+- // Inv: sig != nil
+-
+- // Get the object representing the function, if available.
+- // There is no object in certain cases such as calling a function returned by
+- // a function (e.g. "foo()()").
+- var obj types.Object
+- switch t := fnval.(type) {
+- case *ast.Ident:
+- obj = info.ObjectOf(t)
+- case *ast.SelectorExpr:
+- obj = info.ObjectOf(t.Sel)
+- }
+-
+- if obj != nil && isBuiltin(obj) {
+- // Special handling for error.Error, which is the only builtin method.
+- if obj.Name() == "Error" {
+- return &protocol.SignatureInformation{
+- Label: "Error() string",
+- // TODO(skewb1k): move the docstring for error.Error to builtin.go and reuse it across all relevant LSP methods.
+- Documentation: stringToSigInfoDocumentation("Error returns the error message.", snapshot.Options()),
+- Parameters: nil,
+- ActiveParameter: nil,
+- }, nil
+- }
+- s, err := NewBuiltinSignature(ctx, snapshot, obj.Name())
+- if err != nil {
+- return nil, err
+- }
+- return signatureInformation(s, snapshot.Options(), pos, callExpr)
+- }
+-
+- mq := MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata())
+- qual := typesinternal.FileQualifier(pgf.File, pkg.Types())
+- var (
+- comment *ast.CommentGroup
+- name string
+- )
+-
+- if obj != nil {
+- comment, err = HoverDocForObject(ctx, snapshot, pkg.FileSet(), obj)
+- if err != nil {
+- return nil, err
+- }
+- name = obj.Name()
+- } else {
+- name = "func"
+- }
+-
+- s, err := NewSignature(ctx, snapshot, pkg, sig, comment, qual, mq)
+- if err != nil {
+- return nil, err
+- }
+- s.name = name
+- return signatureInformation(s, snapshot.Options(), pos, callExpr)
+-}
+-
+-func signatureInformation(sig *signature, options *settings.Options, pos token.Pos, call *ast.CallExpr) (*protocol.SignatureInformation, error) {
+- paramInfo := make([]protocol.ParameterInformation, 0, len(sig.params))
+- for _, p := range sig.params {
+- paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
+- }
+- return &protocol.SignatureInformation{
+- Label: sig.name + sig.Format(),
+- Documentation: stringToSigInfoDocumentation(sig.doc, options),
+- Parameters: paramInfo,
+- ActiveParameter: activeParameter(sig, pos, call),
+- }, nil
+-}
+-
+-// activeParameter returns a pointer to a variable containing
+-// the index of the active parameter (if known), or nil otherwise.
+-func activeParameter(sig *signature, pos token.Pos, call *ast.CallExpr) *uint32 {
+- if call == nil {
+- return nil
+- }
+- numParams := uint32(len(sig.params))
+- if numParams == 0 {
+- return nil
+- }
+- // Check if the position is even in the range of the arguments.
+- if !(call.Lparen < pos && pos <= call.Rparen) {
+- return nil
+- }
+-
+- var activeParam uint32
+- for _, arg := range call.Args {
+- if pos <= arg.End() {
+- break
+- }
+- // Don't advance the active parameter for the last parameter of a variadic function.
+- if !sig.variadic || activeParam < numParams-1 {
+- activeParam++
+- }
+- }
+- return &activeParam
+-}
+-
+-func stringToSigInfoDocumentation(s string, options *settings.Options) *protocol.Or_SignatureInformation_documentation {
+- v := s
+- k := protocol.PlainText
+- if options.PreferredContentFormat == protocol.Markdown {
+- v = DocCommentToMarkdown(s, options)
+- // whether or not content is newline terminated may not matter for LSP clients,
+- // but our tests expect trailing newlines to be stripped.
+- v = strings.TrimSuffix(v, "\n") // TODO(pjw): change the golden files
+- k = protocol.Markdown
+- }
+- return &protocol.Or_SignatureInformation_documentation{
+- Value: protocol.MarkupContent{
+- Kind: k,
+- Value: v,
+- },
+- }
+-}
+diff -urN a/gopls/internal/golang/snapshot.go b/gopls/internal/golang/snapshot.go
+--- a/gopls/internal/golang/snapshot.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/snapshot.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,82 +0,0 @@
+-// Copyright 2018 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package golang
+-
+-import (
+- "context"
+- "fmt"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/cache/parsego"
+- "golang.org/x/tools/gopls/internal/protocol"
+-)
+-
+-// NarrowestPackageForFile is a convenience function that selects the narrowest
+-// non-ITV package to which this file belongs, type-checks it in the requested
+-// mode (full or workspace), and returns it, along with the parse tree of that
+-// file.
+-//
+-// The "narrowest" package is the one with the fewest number of files that
+-// includes the given file. This solves the problem of test variants, as the
+-// test will have more files than the non-test package.
+-//
+-// An intermediate test variant (ITV) package has identical source to a regular
+-// package but resolves imports differently. gopls should never need to
+-// type-check them.
+-//
+-// Type-checking is expensive. Call snapshot.ParseGo if all you need is a parse
+-// tree, or snapshot.MetadataForFile if you only need metadata.
+-func NarrowestPackageForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*cache.Package, *parsego.File, error) {
+- return selectPackageForFile(ctx, snapshot, uri, func(metas []*metadata.Package) *metadata.Package { return metas[0] })
+-}
+-
+-// WidestPackageForFile is a convenience function that selects the widest
+-// non-ITV package to which this file belongs, type-checks it in the requested
+-// mode (full or workspace), and returns it, along with the parse tree of that
+-// file.
+-//
+-// The "widest" package is the one with the most number of files that includes
+-// the given file. Which is the test variant if one exists.
+-//
+-// An intermediate test variant (ITV) package has identical source to a regular
+-// package but resolves imports differently. gopls should never need to
+-// type-check them.
+-//
+-// Type-checking is expensive. Call snapshot.ParseGo if all you need is a parse
+-// tree, or snapshot.MetadataForFile if you only need metadata.
+-func WidestPackageForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI) (*cache.Package, *parsego.File, error) {
+- return selectPackageForFile(ctx, snapshot, uri, func(metas []*metadata.Package) *metadata.Package { return metas[len(metas)-1] })
+-}
+-
+-func selectPackageForFile(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, selector func([]*metadata.Package) *metadata.Package) (*cache.Package, *parsego.File, error) {
+- mps, err := snapshot.MetadataForFile(ctx, uri, true)
+- if err != nil {
+- return nil, nil, err
+- }
+- if len(mps) == 0 {
+- return nil, nil, fmt.Errorf("no package metadata for file %s", uri)
+- }
+- mp := selector(mps)
+- pkgs, err := snapshot.TypeCheck(ctx, mp.ID)
+- if err != nil {
+- return nil, nil, err
+- }
+- pkg := pkgs[0]
+- pgf, err := pkg.File(uri)
+- if err != nil {
+- return nil, nil, err // "can't happen"
+- }
+- return pkg, pgf, err
+-}
+-
+-type (
+- PackageID = metadata.PackageID
+- PackagePath = metadata.PackagePath
+- PackageName = metadata.PackageName
+- ImportPath = metadata.ImportPath
+-)
+-
+-type unit = struct{}
+diff -urN a/gopls/internal/golang/splitpkg/graph.go b/gopls/internal/golang/splitpkg/graph.go
+--- a/gopls/internal/golang/splitpkg/graph.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/splitpkg/graph.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,101 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package splitpkg
+-
+-// SCC algorithm stolen from cmd/digraph.
+-
+-type (
+- graph = map[int]map[int]bool
+- nodeList = []int
+- nodeSet = map[int]bool
+-)
+-
+-// addNode ensures a node exists in the graph with an initialized edge set.
+-func addNode(g graph, node int) map[int]bool {
+- edges := g[node]
+- if edges == nil {
+- edges = make(map[int]bool)
+- g[node] = edges
+- }
+- return edges
+-}
+-
+-// addEdges adds one or more edges from a 'from' node.
+-func addEdges(g graph, from int, to ...int) {
+- edges := addNode(g, from)
+- for _, toNode := range to {
+- addNode(g, toNode)
+- edges[toNode] = true
+- }
+-}
+-
+-// transpose creates the transpose (reverse) of the graph.
+-func transpose(g graph) graph {
+- rev := make(graph)
+- for node, edges := range g {
+- addNode(rev, node) // Ensure all nodes exist in the transposed graph
+- for succ := range edges {
+- addEdges(rev, succ, node)
+- }
+- }
+- return rev
+-}
+-
+-// sccs returns the non-trivial strongly connected components of the graph.
+-func sccs(g graph) []nodeSet {
+- // Kosaraju's algorithm---Tarjan is overkill here.
+- //
+- // TODO(adonovan): factor with Tarjan's algorithms from
+- // go/ssa/dom.go,
+- // go/callgraph/vta/propagation.go,
+- // ../../cache/typerefs/refs.go,
+- // ../../cache/metadata/graph.go.
+-
+- // Forward pass.
+- S := make(nodeList, 0, len(g)) // postorder stack
+- seen := make(nodeSet)
+- var visit func(node int)
+- visit = func(node int) {
+- if !seen[node] {
+- seen[node] = true
+- for e := range g[node] {
+- visit(e)
+- }
+- S = append(S, node)
+- }
+- }
+- for node := range g {
+- visit(node)
+- }
+-
+- // Reverse pass.
+- rev := transpose(g)
+- var scc nodeSet
+- seen = make(nodeSet)
+- var rvisit func(node int)
+- rvisit = func(node int) {
+- if !seen[node] {
+- seen[node] = true
+- scc[node] = true
+- for e := range rev[node] {
+- rvisit(e)
+- }
+- }
+- }
+- var sccs []nodeSet
+- for len(S) > 0 {
+- top := S[len(S)-1]
+- S = S[:len(S)-1] // pop
+- if !seen[top] {
+- scc = make(nodeSet)
+- rvisit(top)
+- if len(scc) == 1 && !g[top][top] {
+- continue
+- }
+- sccs = append(sccs, scc)
+- }
+- }
+- return sccs
+-}
+diff -urN a/gopls/internal/golang/splitpkg/splitpkg.go b/gopls/internal/golang/splitpkg/splitpkg.go
+--- a/gopls/internal/golang/splitpkg/splitpkg.go 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/splitpkg/splitpkg.go 1969-12-31 18:00:00.000000000 -0600
+@@ -1,512 +0,0 @@
+-// Copyright 2025 The Go Authors. All rights reserved.
+-// Use of this source code is governed by a BSD-style
+-// license that can be found in the LICENSE file.
+-
+-package splitpkg
+-
+-// This file produces the "Split package" HTML report.
+-//
+-// The server persistently holds, for each PackageID, the current set
+-// of components and the mapping from declared names to components. On
+-// each page reload or JS reload() call, the server type-checks the
+-// package, computes its symbol reference graph, projects it onto
+-// components, then returns the component reference graph, and if it
+-// is cyclic, which edges form cycles. Thus changes to the package
+-// source are reflected in the client UI at the next page reload or JS
+-// reload() event.
+-//
+-// See also:
+-// - ../codeaction.go - offers the CodeAction command
+-// - ../../server/command.go - handles the command by opening a web page
+-// - ../../server/server.go - handles the HTTP request and calls this function
+-// - ../../server/assets/splitpkg.js - client-side logic
+-// - ../../test/integration/web/splitpkg_test.go - integration test of server
+-//
+-// TODO(adonovan): future work
+-//
+-// Refine symbol reference graph:
+-// - deal with enums (values must stay together; implicit dependency on iota expression)
+-// - deal with coupled vars "var x, y = f()"
+-// - deal with declared methods (coupled to receiver named type)
+-// - deal with fields/interface methods (loosely coupled to struct/interface type)
+-// In both cases the field/method name must be either exported or in the same component.
+-//
+-// UI:
+-// - make shift click extend selection of a range of checkboxes.
+-// - display two-level grouping of decls and specs: var ( x int; y int )
+-// - indicate when package has type errors (data may be incomplete).
+-//
+-// Code transformation:
+-// - add "Split" button that is green when acyclic. It should:
+-// 1) move each component into a new package, or separate file of
+-// the same package. (The UI will need to hold this user
+-// intent in the list of components.)
+-// 2) ensure that each declaration referenced from another package
+-// is public, renaming as needed.
+-// 3) update package decls, imports, package docs, file docs,
+-// doc comments, etc.
+-// Should we call this feature "Reorganize package" or "Decompose package"
+-// until the "Split" button actually exists?
+-
+-import (
+- "bytes"
+- "crypto/sha256"
+- _ "embed"
+- "encoding/json"
+- "fmt"
+- "go/ast"
+- "go/token"
+- "go/types"
+- "html/template"
+- "log"
+- "strconv"
+- "strings"
+-
+- "golang.org/x/tools/gopls/internal/cache"
+- "golang.org/x/tools/gopls/internal/cache/metadata"
+- "golang.org/x/tools/gopls/internal/filecache"
+- "golang.org/x/tools/gopls/internal/protocol"
+- "golang.org/x/tools/gopls/internal/util/moremaps"
+- "golang.org/x/tools/gopls/internal/util/safetoken"
+- "golang.org/x/tools/internal/typesinternal"
+-)
+-
+-//go:embed splitpkg.html.tmpl
+-var htmlTmpl string
+-
+-// HTML returns the HTML for the main "Split package" page, for the
+-// /splitpkg endpoint. The real magic happens in JavaScript; see
+-// ../../server/assets/splitpkg.js.
+-func HTML(pkgpath metadata.PackagePath) []byte {
+- t, err := template.New("splitpkg.html").Parse(htmlTmpl)
+- if err != nil {
+- log.Fatal(err)
+- }
+- data := struct {
+- Title string
+- }{
+- Title: fmt.Sprintf("Split package %s", pkgpath),
+- }
+- var buf bytes.Buffer
+- if err := t.Execute(&buf, data); err != nil {
+- log.Fatal(err)
+- }
+- return buf.Bytes()
+-}
+-
+-const cacheKind = "splitpkg" // filecache kind
+-
+-func cacheKey(pkgID metadata.PackageID) [32]byte {
+- return sha256.Sum256([]byte(pkgID))
+-}
+-
+-// UpdateComponentsJSON parses the JSON description of components and
+-// their assigned declarations and updates the component state for the
+-// specified package.
+-func UpdateComponentsJSON(pkgID metadata.PackageID, data []byte) error {
+- return filecache.Set(cacheKind, cacheKey(pkgID), data)
+-}
+-
+-// Web is an abstraction of gopls' web server.
+-type Web interface {
+- // SrcURL forms URLs that cause the editor to open a file at a specific position.
+- SrcURL(filename string, line, col8 int) protocol.URI
+-}
+-
+-// JSON returns the JSON encoding of the data needed by
+-// the /splitpkg-json endpoint for the specified package. It includes:
+-// - the set of names declared by the package, grouped by file;
+-// - the set of components and their assigned declarations from
+-// the most recent call to [UpdateComponentsJSON]; and
+-// - the component graph derived from them, along with the
+-// sets of reference that give rise to each edge.
+-func JSON(pkg *cache.Package, web Web) ([]byte, error) {
+- // Retrieve package's most recent state from the file cache.
+- var comp ComponentsJSON
+- data, err := filecache.Get(cacheKind, cacheKey(pkg.Metadata().ID))
+- if err != nil {
+- if err != filecache.ErrNotFound {
+- return nil, err
+- }
+- // cache miss: use zero value
+- } else if err := json.Unmarshal(data, &comp); err != nil {
+- return nil, err
+- }
+-
+- // Prepare to construct symbol reference graph.
+- var (
+- info = pkg.TypesInfo()
+- symbols = make(map[types.Object]*symbol)
+- )
+-
+- // setName records the UI name for an object.
+- // (The UI name disambiguates "init", "_", etc.)
+- setName := func(obj types.Object, name string) {
+- symbols[obj] = &symbol{
+- name: name,
+- component: comp.Assignments[name], // missing => "default"
+- }
+- }
+-
+- // Pass 1: name everything, since naming is order-dependent.
+- var initCounter, blankCounter int
+- for _, pgf := range pkg.CompiledGoFiles() {
+- for _, decl := range pgf.File.Decls {
+- switch decl := decl.(type) {
+- case *ast.FuncDecl:
+- if fn, ok := info.Defs[decl.Name].(*types.Func); ok {
+- // For now we treat methods as first class decls,
+- // but since they are coupled to the named type
+- // they should be omitted in the UI for brevity.
+- name := fn.Name()
+- if recv := fn.Signature().Recv(); recv != nil {
+- fn = fn.Origin()
+- _, named := typesinternal.ReceiverNamed(recv)
+- name = named.Obj().Name() + "." + name
+- } else if name == "init" {
+- // Disambiguate top-level init functions.
+- name += suffix(&initCounter)
+- }
+- if name == "_" { // (function or method)
+- name += suffix(&blankCounter)
+- }
+- setName(fn, name)
+- }
+-
+- case *ast.GenDecl:
+- switch decl.Tok {
+- case token.CONST, token.VAR:
+- for _, spec := range decl.Specs {
+- spec := spec.(*ast.ValueSpec)
+- for _, id := range spec.Names {
+- if obj := info.Defs[id]; obj != nil {
+- name := obj.Name()
+- if name == "_" {
+- name += suffix(&blankCounter)
+- }
+- setName(obj, name)
+- }
+- }
+- }
+-
+- case token.TYPE:
+- for _, spec := range decl.Specs {
+- spec := spec.(*ast.TypeSpec)
+- if obj := info.Defs[spec.Name]; obj != nil {
+- name := obj.Name()
+- if name == "_" {
+- name += suffix(&blankCounter)
+- }
+- setName(obj, name)
+- }
+- }
+- }
+- }
+- }
+- }
+-
+- // Pass 2: compute symbol reference graph, project onto
+- // component dependency graph, and build JSON response.
+- var (
+- files []*fileJSON
+- refs []*refJSON
+- )
+- for _, pgf := range pkg.CompiledGoFiles() {
+- identURL := func(id *ast.Ident) string {
+- posn := safetoken.Position(pgf.Tok, id.Pos())
+- return web.SrcURL(posn.Filename, posn.Line, posn.Column)
+- }
+- newCollector := func(from *symbol) *refCollector {
+- return &refCollector{
+- from: from,
+- identURL: identURL,
+- pkg: pkg.Types(),
+- info: info,
+- symbols: symbols,
+- }
+- }
+- var decls []*declJSON
+- for _, decl := range pgf.File.Decls {
+- var (
+- kind string
+- specs []*specJSON
+- )
+- switch decl := decl.(type) {
+- case *ast.FuncDecl:
+- kind = "func"
+- if fn, ok := info.Defs[decl.Name].(*types.Func); ok {
+- symbol := symbols[fn]
+- rc := newCollector(symbol).collect(decl)
+- refs = append(refs, rc.refs...)
+- specs = append(specs, &specJSON{
+- Name: symbol.name,
+- URL: identURL(decl.Name),
+- })
+- }
+-
+- case *ast.GenDecl:
+- kind = decl.Tok.String()
+-
+- switch decl.Tok {
+- case token.CONST, token.VAR:
+- for _, spec := range decl.Specs {
+- spec := spec.(*ast.ValueSpec)
+- for i, id := range spec.Names {
+- if obj := info.Defs[id]; obj != nil {
+- symbol := symbols[obj]
+- rc := newCollector(symbol)
+- // If there's a type,
+- // all RHSs depend on it.
+- if spec.Type != nil {
+- rc.collect(spec.Type)
+- }
+- switch len(spec.Values) {
+- case len(spec.Names):
+- // var x, y = a, b
+- rc.collect(spec.Values[i])
+- case 1:
+- // var x, y = f()
+- rc.collect(spec.Values[0])
+- case 0:
+- // var x T
+- }
+- refs = append(refs, rc.refs...)
+- specs = append(specs, &specJSON{
+- Name: symbol.name,
+- URL: identURL(id),
+- })
+- }
+- }
+- }
+-
+- case token.TYPE:
+- for _, spec := range decl.Specs {
+- spec := spec.(*ast.TypeSpec)
+- if obj := info.Defs[spec.Name]; obj != nil {
+- symbol := symbols[obj]
+- rc := newCollector(symbol).collect(spec.Type)
+- refs = append(refs, rc.refs...)
+- specs = append(specs, &specJSON{
+- Name: symbol.name,
+- URL: identURL(spec.Name),
+- })
+- }
+- }
+- }
+- }
+- if len(specs) > 0 {
+- decls = append(decls, &declJSON{Kind: kind, Specs: specs})
+- }
+- }
+- files = append(files, &fileJSON{
+- Base: pgf.URI.Base(),
+- URL: web.SrcURL(pgf.URI.Path(), 1, 1),
+- Decls: decls,
+- })
+- }
+-
+- // Compute the graph of dependencies between components, by
+- // projecting the symbol dependency graph through component
+- // assignments.
+- var (
+- g = make(graph)
+- edgeRefs = make(map[[2]int][]*refJSON) // refs that induce each intercomponent edge
+- )
+- for _, ref := range refs {
+- from, to := ref.from, ref.to
+- if from.component != to.component {
+- // inter-component reference
+- m, ok := g[from.component]
+- if !ok {
+- m = make(map[int]bool)
+- g[from.component] = m
+- }
+- m[to.component] = true
+-
+- key := [2]int{from.component, to.component}
+- edgeRefs[key] = append(edgeRefs[key], ref)
+- }
+- }
+-
+- // Detect cycles in the component graph
+- // and record cyclic (⚠) components.
+- cycles := [][]int{} // non-nil for JSON
+- scmap := make(map[int]int) // maps component index to 1 + SCC index (0 => acyclic)
+- for i, scc := range sccs(g) {
+- for c := range scc {
+- scmap[c] = i + 1
+- }
+- cycles = append(cycles, moremaps.KeySlice(scc))
+- }
+-
+- // Record intercomponent edges and their references.
+- edges := []*edgeJSON{} // non-nil for JSON
+- for edge, refs := range edgeRefs {
+- from, to := edge[0], edge[1]
+- edges = append(edges, &edgeJSON{
+- From: from,
+- To: to,
+- Refs: refs,
+- Cyclic: scmap[from] > 0 && scmap[from] == scmap[to],
+- })
+- }
+-
+- return json.Marshal(ResultJSON{
+- Files: files,
+- Components: comp,
+- Edges: edges,
+- Cycles: cycles,
+- })
+-}
+-
+-// A refCollector gathers intra-package references to top-level
+-// symbols from within one syntax tree, in lexical order.
+-type refCollector struct {
+- from *symbol
+- identURL func(*ast.Ident) string
+- pkg *types.Package
+- info *types.Info
+- index map[types.Object]*refJSON
+- symbols map[types.Object]*symbol
+-
+- refs []*refJSON // output
+-}
+-
+-// A symbol describes a declared name and its assigned component.
+-type symbol struct {
+- name string // unique name in the UI and JSON/HTTP protocol
+- component int // index of assigned component
+-}
+-
+-// collect adds the free references of n to the collection.
+-func (rc *refCollector) collect(n ast.Node) *refCollector {
+- var f func(n ast.Node) bool
+- f = func(n ast.Node) bool {
+- switch n := n.(type) {
+- case *ast.SelectorExpr:
+- if sel, ok := rc.info.Selections[n]; ok {
+- rc.addRef(n.Sel, sel.Obj())
+- ast.Inspect(n.X, f)
+- return false // don't visit n.Sel
+- }
+-
+- case *ast.Ident:
+- if obj := rc.info.Uses[n]; obj != nil {
+- rc.addRef(n, obj)
+- }
+- }
+- return true
+- }
+- ast.Inspect(n, f)
+-
+- return rc
+-}
+-
+-// addRef records a reference from id to obj.
+-func (rc *refCollector) addRef(id *ast.Ident, obj types.Object) {
+- if obj.Pkg() != rc.pkg {
+- return // cross-package reference
+- }
+-
+- // Un-instantiate methods.
+- if fn, ok := obj.(*types.Func); ok && fn.Signature().Recv() != nil {
+- obj = fn.Origin() // G[int].method -> G[T].method
+- }
+-
+- // We only care about refs to package-level symbols.
+- // And methods, for now.
+- decl := rc.symbols[obj]
+- if decl == nil {
+- return // not a package-level symbol or top-level method
+- }
+-
+- if ref, ok := rc.index[obj]; !ok {
+- ref = &refJSON{
+- From: rc.from.name,
+- To: decl.name,
+- URL: rc.identURL(id),
+- from: rc.from,
+- to: decl,
+- }
+- if rc.index == nil {
+- rc.index = make(map[types.Object]*refJSON)
+- }
+- rc.index[obj] = ref
+- rc.refs = append(rc.refs, ref)
+- }
+-}
+-
+-// suffix returns a subscripted decimal suffix,
+-// preincrementing the specified counter.
+-func suffix(counter *int) string {
+- *counter++
+- n := *counter
+- return subscripter.Replace(strconv.Itoa(n))
+-}
+-
+-var subscripter = strings.NewReplacer(
+- "0", "₀",
+- "1", "₁",
+- "2", "₂",
+- "3", "₃",
+- "4", "₄",
+- "5", "₅",
+- "6", "₆",
+- "7", "₇",
+- "8", "₈",
+- "9", "₉",
+-)
+-
+-// -- JSON types --
+-
+-// ResultJSON describes the result of a /splitpkg-json query.
+-// It is public for testing.
+-type ResultJSON struct {
+- Components ComponentsJSON // component names and their assigned declarations
+- Files []*fileJSON // files of the packages and their declarations and references
+- Edges []*edgeJSON // inter-component edges and their references
+- Cycles [][]int // sets of strongly-connected components
+-}
+-
+-// request body of a /splitpkg-components update;
+-// also part of /splitpkg-json response.
+-type ComponentsJSON struct {
+- Names []string `json:",omitempty"` // if empty, implied Names[0]=="default".
+- Assignments map[string]int `json:",omitempty"` // maps specJSON.Name to component index; missing => 0
+-}
+-
+-// edgeJSON describes an inter-component dependency.
+-type edgeJSON struct {
+- From, To int // component IDs
+- Refs []*refJSON // references that give rise to this edge
+- Cyclic bool // edge is part of nontrivial strongly connected component
+-}
+-
+-// fileJSON records groups decl/spec information about a single file.
+-type fileJSON struct {
+- Base string // file base name
+- URL string // showDocument link for file
+- Decls []*declJSON `json:",omitempty"`
+-}
+-
+-// declJSON groups specs (e.g. "var ( x int; y int )").
+-type declJSON struct {
+- Kind string // const, var, type, func
+- Specs []*specJSON `json:",omitempty"`
+-}
+-
+-// specJSON describes a single declared name.
+-// (A coupled declaration "var x, y = f()" results in two specJSONs.)
+-type specJSON struct {
+- Name string // x or T.x
+- URL string // showDocument link for declaring identifier
+-}
+-
+-// refJSON records the first reference from a given declaration to a symbol.
+-// (Repeat uses of the same identifier are omitted.)
+-type refJSON struct {
+- From, To string // x or T.x of referenced spec
+- URL string // showDocument link for referring identifier
+-
+- from, to *symbol // transient
+-}
+diff -urN a/gopls/internal/golang/splitpkg/splitpkg.html.tmpl b/gopls/internal/golang/splitpkg/splitpkg.html.tmpl
+--- a/gopls/internal/golang/splitpkg/splitpkg.html.tmpl 2000-01-01 00:00:00.000000000 -0000
++++ b/gopls/internal/golang/splitpkg/splitpkg.html.tmpl 1969-12-31 18:00:00.000000000 -0600
+@@ -1,69 +0,0 @@
+-
+-
+-
+-
+- {{.Title}}
+-
+-
+-
+-
+-
+-
+-
{{.Title}}
+-
+-
+-ⓘ Use this tool to decompose a package into a set of components
+-whose dependencies are acyclic.
+-
+-First, name a set of components.
+-
+-Second, assign each declaration to an
+-appropriate component: check their checkboxes, choose a component, and
+-click Apply. Use the checkbox for a file to select all declarations in
+-that file.
+-
+-Third, examine the set of dependencies between
+-components. Each inter-component dependency lists the symbol
+-references that cross the boundary. Click on one to navigate your
+-editor there.
+-
+-If two or more components form a dependency cycle (⚠),
+-you will need to either change your code,
+-or change the component assignments.
+-
+-Iterate this process.
+-Reload the page to refresh after each code change.
+-
+-
+-Once you are happy with the result, you can split the package,
+-renaming declarations as needed to export them.
+-In a future release, the code transformation will be automated.
+-