Skip to content

Commit 9f9b7e3

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/settings: add missing deep cloning in Options.Clone
As noted in a TODO, it appeared that settings.Clone was failing to deep clone several map or slice fields. A test revealed that ten (!) fields were not deeply cloned. Fix this by: 1. Removing pointers and interfaces from settings.Options, by making ClientInfo a non-pointer, and by making LinksInHover a proper enum. 2. Adding a deepclone package that implements deep cloning using reflection. By avoiding supporting pointers and interfaces, this package doesn't need to worry about recursive data structures. Change-Id: Ic89916f7cad51d8e60ed0a8a095758acd1c09a2d Reviewed-on: https://go-review.googlesource.com/c/tools/+/606816 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Robert Findley <[email protected]>
1 parent ce7eed4 commit 9f9b7e3

File tree

8 files changed

+317
-49
lines changed

8 files changed

+317
-49
lines changed

gopls/internal/cache/snapshot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -964,7 +964,7 @@ func (s *Snapshot) watchSubdirs() bool {
964964
// requirements that client names do not change. We should update the VS
965965
// Code extension to set a default value of "subdirWatchPatterns" to "on",
966966
// so that this workaround is only temporary.
967-
if s.Options().ClientInfo != nil && s.Options().ClientInfo.Name == "Visual Studio Code" {
967+
if s.Options().ClientInfo.Name == "Visual Studio Code" {
968968
return true
969969
}
970970
return false

gopls/internal/clonetest/clonetest.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Package clonetest provides utility functions for testing Clone operations.
6+
//
7+
// The [NonZero] helper may be used to construct a type in which fields are
8+
// recursively set to a non-zero value. This value can then be cloned, and the
9+
// [ZeroOut] helper can set values stored in the clone to zero, recursively.
10+
// Doing so should not mutate the original.
11+
package clonetest
12+
13+
import (
14+
"fmt"
15+
"reflect"
16+
)
17+
18+
// NonZero returns a T set to some appropriate nonzero value:
19+
// - Values of basic type are set to an arbitrary non-zero value.
20+
// - Struct fields are set to a non-zero value.
21+
// - Array indices are set to a non-zero value.
22+
// - Pointers point to a non-zero value.
23+
// - Maps and slices are given a non-zero element.
24+
// - Chan, Func, Interface, UnsafePointer are all unsupported.
25+
//
26+
// NonZero breaks cycles by returning a zero value for recursive types.
27+
func NonZero[T any]() T {
28+
var x T
29+
t := reflect.TypeOf(x)
30+
if t == nil {
31+
panic("untyped nil")
32+
}
33+
v := nonZeroValue(t, nil)
34+
return v.Interface().(T)
35+
}
36+
37+
// nonZeroValue returns a non-zero, addressable value of the given type.
38+
func nonZeroValue(t reflect.Type, seen []reflect.Type) reflect.Value {
39+
for _, t2 := range seen {
40+
if t == t2 {
41+
// Cycle: return the zero value.
42+
return reflect.Zero(t)
43+
}
44+
}
45+
seen = append(seen, t)
46+
v := reflect.New(t).Elem()
47+
switch t.Kind() {
48+
case reflect.Bool:
49+
v.SetBool(true)
50+
51+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
52+
v.SetInt(1)
53+
54+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
55+
v.SetUint(1)
56+
57+
case reflect.Float32, reflect.Float64:
58+
v.SetFloat(1)
59+
60+
case reflect.Complex64, reflect.Complex128:
61+
v.SetComplex(1)
62+
63+
case reflect.Array:
64+
for i := 0; i < v.Len(); i++ {
65+
v.Index(i).Set(nonZeroValue(t.Elem(), seen))
66+
}
67+
68+
case reflect.Map:
69+
v2 := reflect.MakeMap(t)
70+
v2.SetMapIndex(nonZeroValue(t.Key(), seen), nonZeroValue(t.Elem(), seen))
71+
v.Set(v2)
72+
73+
case reflect.Pointer:
74+
v2 := nonZeroValue(t.Elem(), seen)
75+
v.Set(v2.Addr())
76+
77+
case reflect.Slice:
78+
v2 := reflect.Append(v, nonZeroValue(t.Elem(), seen))
79+
v.Set(v2)
80+
81+
case reflect.String:
82+
v.SetString(".")
83+
84+
case reflect.Struct:
85+
for i := 0; i < v.NumField(); i++ {
86+
v.Field(i).Set(nonZeroValue(t.Field(i).Type, seen))
87+
}
88+
89+
default: // Chan, Func, Interface, UnsafePointer
90+
panic(fmt.Sprintf("reflect kind %v not supported", t.Kind()))
91+
}
92+
return v
93+
}
94+
95+
// ZeroOut recursively sets values contained in t to zero.
96+
// Values of king Chan, Func, Interface, UnsafePointer are all unsupported.
97+
//
98+
// No attempt is made to handle cyclic values.
99+
func ZeroOut[T any](t *T) {
100+
v := reflect.ValueOf(t).Elem()
101+
zeroOutValue(v)
102+
}
103+
104+
func zeroOutValue(v reflect.Value) {
105+
if v.IsZero() {
106+
return // nothing to do; this also handles untyped nil values
107+
}
108+
109+
switch v.Kind() {
110+
case reflect.Bool,
111+
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
112+
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
113+
reflect.Float32, reflect.Float64,
114+
reflect.Complex64, reflect.Complex128,
115+
reflect.String:
116+
117+
v.Set(reflect.Zero(v.Type()))
118+
119+
case reflect.Array:
120+
for i := 0; i < v.Len(); i++ {
121+
zeroOutValue(v.Index(i))
122+
}
123+
124+
case reflect.Map:
125+
iter := v.MapRange()
126+
for iter.Next() {
127+
mv := iter.Value()
128+
if mv.CanAddr() {
129+
zeroOutValue(mv)
130+
} else {
131+
mv = reflect.New(mv.Type()).Elem()
132+
}
133+
v.SetMapIndex(iter.Key(), mv)
134+
}
135+
136+
case reflect.Pointer:
137+
zeroOutValue(v.Elem())
138+
139+
case reflect.Slice:
140+
for i := 0; i < v.Len(); i++ {
141+
zeroOutValue(v.Index(i))
142+
}
143+
144+
case reflect.Struct:
145+
for i := 0; i < v.NumField(); i++ {
146+
zeroOutValue(v.Field(i))
147+
}
148+
149+
default:
150+
panic(fmt.Sprintf("reflect kind %v not supported", v.Kind()))
151+
}
152+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package clonetest_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"golang.org/x/tools/gopls/internal/clonetest"
12+
)
13+
14+
func Test(t *testing.T) {
15+
doTest(t, true, false)
16+
type B bool
17+
doTest(t, B(true), false)
18+
doTest(t, 1, 0)
19+
doTest(t, int(1), 0)
20+
doTest(t, int8(1), 0)
21+
doTest(t, int16(1), 0)
22+
doTest(t, int32(1), 0)
23+
doTest(t, int64(1), 0)
24+
doTest(t, uint(1), 0)
25+
doTest(t, uint8(1), 0)
26+
doTest(t, uint16(1), 0)
27+
doTest(t, uint32(1), 0)
28+
doTest(t, uint64(1), 0)
29+
doTest(t, uintptr(1), 0)
30+
doTest(t, float32(1), 0)
31+
doTest(t, float64(1), 0)
32+
doTest(t, complex64(1), 0)
33+
doTest(t, complex128(1), 0)
34+
doTest(t, [3]int{1, 1, 1}, [3]int{0, 0, 0})
35+
doTest(t, ".", "")
36+
m1, m2 := map[string]int{".": 1}, map[string]int{".": 0}
37+
doTest(t, m1, m2)
38+
doTest(t, &m1, &m2)
39+
doTest(t, []int{1}, []int{0})
40+
i, j := 1, 0
41+
doTest(t, &i, &j)
42+
k, l := &i, &j
43+
doTest(t, &k, &l)
44+
45+
s1, s2 := []int{1}, []int{0}
46+
doTest(t, &s1, &s2)
47+
48+
type S struct {
49+
Field int
50+
}
51+
doTest(t, S{1}, S{0})
52+
53+
doTest(t, []*S{{1}}, []*S{{0}})
54+
55+
// An arbitrary recursive type.
56+
type LinkedList[T any] struct {
57+
V T
58+
Next *LinkedList[T]
59+
}
60+
doTest(t, &LinkedList[int]{V: 1}, &LinkedList[int]{V: 0})
61+
}
62+
63+
// doTest checks that the result of NonZero matches the nonzero argument, and
64+
// that zeroing out that result matches the zero argument.
65+
func doTest[T any](t *testing.T, nonzero, zero T) {
66+
got := clonetest.NonZero[T]()
67+
if diff := cmp.Diff(nonzero, got); diff != "" {
68+
t.Fatalf("NonZero() returned unexpected diff (-want +got):\n%s", diff)
69+
}
70+
clonetest.ZeroOut(&got)
71+
if diff := cmp.Diff(zero, got); diff != "" {
72+
t.Errorf("ZeroOut() returned unexpected diff (-want +got):\n%s", diff)
73+
}
74+
}

gopls/internal/golang/hover.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1279,7 +1279,7 @@ func StdSymbolOf(obj types.Object) *stdlib.Symbol {
12791279

12801280
// If pkgURL is non-nil, it should be used to generate doc links.
12811281
func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string {
1282-
if options.LinksInHover == false || h.LinkPath == "" {
1282+
if options.LinksInHover == settings.LinksInHover_None || h.LinkPath == "" {
12831283
return ""
12841284
}
12851285
var url protocol.URI

gopls/internal/server/hover.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"golang.org/x/tools/gopls/internal/label"
1313
"golang.org/x/tools/gopls/internal/mod"
1414
"golang.org/x/tools/gopls/internal/protocol"
15+
"golang.org/x/tools/gopls/internal/settings"
1516
"golang.org/x/tools/gopls/internal/telemetry"
1617
"golang.org/x/tools/gopls/internal/template"
1718
"golang.org/x/tools/gopls/internal/work"
@@ -38,7 +39,7 @@ func (s *server) Hover(ctx context.Context, params *protocol.HoverParams) (_ *pr
3839
return mod.Hover(ctx, snapshot, fh, params.Position)
3940
case file.Go:
4041
var pkgURL func(path golang.PackagePath, fragment string) protocol.URI
41-
if snapshot.Options().LinksInHover == "gopls" {
42+
if snapshot.Options().LinksInHover == settings.LinksInHover_Gopls {
4243
web, err := s.getWeb()
4344
if err != nil {
4445
event.Error(ctx, "failed to start web server", err)

gopls/internal/settings/default.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ func DefaultOptions(overrides ...func(*Options)) *Options {
8989
DocumentationOptions: DocumentationOptions{
9090
HoverKind: FullDocumentation,
9191
LinkTarget: "pkg.go.dev",
92-
LinksInHover: true,
92+
LinksInHover: LinksInHover_LinkTarget,
9393
},
9494
NavigationOptions: NavigationOptions{
9595
ImportShortcut: BothShortcuts,

0 commit comments

Comments
 (0)