Skip to content

Commit 23c7f58

Browse files
committed
all: merge master (5b4d426) into gopls-release-branch.0.13
For golang/go#61675 Merge List: + 2023-08-01 5b4d426 gopls/internal/hooks: clean language version before passing to gofumpt + 2023-08-01 2160c5f gopls/internal/lsp/analysis: fix stubmethods with variadic parameters Change-Id: I5a27fc0307d7f032b6f06d677a29a757a3c7f758
2 parents c924e60 + 5b4d426 commit 23c7f58

File tree

6 files changed

+220
-9
lines changed

6 files changed

+220
-9
lines changed

gopls/internal/hooks/gofumpt_118.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,70 @@ package hooks
99

1010
import (
1111
"context"
12+
"fmt"
1213

1314
"golang.org/x/tools/gopls/internal/lsp/source"
1415
"mvdan.cc/gofumpt/format"
1516
)
1617

1718
func updateGofumpt(options *source.Options) {
1819
options.GofumptFormat = func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error) {
20+
fixedVersion, err := fixLangVersion(langVersion)
21+
if err != nil {
22+
return nil, err
23+
}
1924
return format.Source(src, format.Options{
20-
LangVersion: langVersion,
25+
LangVersion: fixedVersion,
2126
ModulePath: modulePath,
2227
})
2328
}
2429
}
30+
31+
// fixLangVersion function cleans the input so that gofumpt doesn't panic. It is
32+
// rather permissive, and accepts version strings that aren't technically valid
33+
// in a go.mod file.
34+
//
35+
// More specifically, it looks for an optional 'v' followed by 1-3
36+
// '.'-separated numbers. The resulting string is stripped of any suffix beyond
37+
// this expected version number pattern.
38+
//
39+
// See also golang/go#61692: gofumpt does not accept the new language versions
40+
// appearing in go.mod files (e.g. go1.21rc3).
41+
func fixLangVersion(input string) (string, error) {
42+
bad := func() (string, error) {
43+
return "", fmt.Errorf("invalid language version syntax %q", input)
44+
}
45+
if input == "" {
46+
return input, nil
47+
}
48+
i := 0
49+
if input[0] == 'v' { // be flexible about 'v'
50+
i++
51+
}
52+
// takeDigits consumes ascii numerals 0-9 and reports if at least one was
53+
// consumed.
54+
takeDigits := func() bool {
55+
found := false
56+
for ; i < len(input) && '0' <= input[i] && input[i] <= '9'; i++ {
57+
found = true
58+
}
59+
return found
60+
}
61+
if !takeDigits() { // versions must start with at least one number
62+
return bad()
63+
}
64+
65+
// Accept optional minor and patch versions.
66+
for n := 0; n < 2; n++ {
67+
if i < len(input) && input[i] == '.' {
68+
// Look for minor/patch version.
69+
i++
70+
if !takeDigits() {
71+
i--
72+
break
73+
}
74+
}
75+
}
76+
// Accept any suffix.
77+
return input[:i], nil
78+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2022 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+
//go:build go1.18
6+
// +build go1.18
7+
8+
package hooks
9+
10+
import "testing"
11+
12+
func TestFixLangVersion(t *testing.T) {
13+
tests := []struct {
14+
input, want string
15+
wantErr bool
16+
}{
17+
{"", "", false},
18+
{"1.18", "1.18", false},
19+
{"v1.18", "v1.18", false},
20+
{"1.21", "1.21", false},
21+
{"1.21rc3", "1.21", false},
22+
{"1.21.0", "1.21.0", false},
23+
{"1.21.1", "1.21.1", false},
24+
{"v1.21.1", "v1.21.1", false},
25+
{"v1.21.0rc1", "v1.21.0", false}, // not technically valid, but we're flexible
26+
{"v1.21.0.0", "v1.21.0", false}, // also technically invalid
27+
{"1.1", "1.1", false},
28+
{"v1", "v1", false},
29+
{"1", "1", false},
30+
{"v1.21.", "v1.21", false}, // also invalid
31+
{"1.21.", "1.21", false},
32+
33+
// Error cases.
34+
{"rc1", "", true},
35+
{"x1.2.3", "", true},
36+
}
37+
38+
for _, test := range tests {
39+
got, err := fixLangVersion(test.input)
40+
if test.wantErr {
41+
if err == nil {
42+
t.Errorf("fixLangVersion(%q) succeeded unexpectedly", test.input)
43+
}
44+
continue
45+
}
46+
if err != nil {
47+
t.Fatalf("fixLangVersion(%q) failed: %v", test.input, err)
48+
}
49+
if got != test.want {
50+
t.Errorf("fixLangVersion(%q) = %s, want %s", test.input, got, test.want)
51+
}
52+
}
53+
}

gopls/internal/lsp/analysis/stubmethods/stubmethods.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,19 @@ func fromCallExpr(fset *token.FileSet, ti *types.Info, pos token.Pos, ce *ast.Ca
174174
if !ok {
175175
return nil
176176
}
177-
sigVar := sig.Params().At(paramIdx)
178-
iface := ifaceObjFromType(sigVar.Type())
177+
var paramType types.Type
178+
if sig.Variadic() && paramIdx >= sig.Params().Len()-1 {
179+
v := sig.Params().At(sig.Params().Len() - 1)
180+
if s, _ := v.Type().(*types.Slice); s != nil {
181+
paramType = s.Elem()
182+
}
183+
} else if paramIdx < sig.Params().Len() {
184+
paramType = sig.Params().At(paramIdx).Type()
185+
}
186+
if paramType == nil {
187+
return nil // A type error prevents us from determining the param type.
188+
}
189+
iface := ifaceObjFromType(paramType)
179190
if iface == nil {
180191
return nil
181192
}

gopls/internal/lsp/code_action.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313

1414
"golang.org/x/tools/go/ast/inspector"
15+
"golang.org/x/tools/gopls/internal/bug"
1516
"golang.org/x/tools/gopls/internal/lsp/analysis/fillstruct"
1617
"golang.org/x/tools/gopls/internal/lsp/analysis/infertypeargs"
1718
"golang.org/x/tools/gopls/internal/lsp/analysis/stubmethods"
@@ -198,26 +199,46 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
198199
if err != nil {
199200
return nil, err
200201
}
201-
for _, pd := range diagnostics {
202+
for _, pd := range stubMethodsDiagnostics {
202203
start, end, err := pgf.RangePos(pd.Range)
203204
if err != nil {
204205
return nil, err
205206
}
206-
if d, ok := stubmethods.DiagnosticForError(pkg.FileSet(), pgf.File, start, end, pd.Message, pkg.GetTypesInfo()); ok {
207+
action, ok, err := func() (_ protocol.CodeAction, _ bool, rerr error) {
208+
// golang/go#61693: code actions were refactored to run outside of the
209+
// analysis framework, but as a result they lost their panic recovery.
210+
//
211+
// Stubmethods "should never fail"", but put back the panic recovery as a
212+
// defensive measure.
213+
defer func() {
214+
if r := recover(); r != nil {
215+
rerr = bug.Errorf("stubmethods panicked: %v", r)
216+
}
217+
}()
218+
d, ok := stubmethods.DiagnosticForError(pkg.FileSet(), pgf.File, start, end, pd.Message, pkg.GetTypesInfo())
219+
if !ok {
220+
return protocol.CodeAction{}, false, nil
221+
}
207222
cmd, err := command.NewApplyFixCommand(d.Message, command.ApplyFixArgs{
208223
URI: protocol.URIFromSpanURI(pgf.URI),
209224
Fix: source.StubMethods,
210225
Range: pd.Range,
211226
})
212227
if err != nil {
213-
return nil, err
228+
return protocol.CodeAction{}, false, err
214229
}
215-
actions = append(actions, protocol.CodeAction{
230+
return protocol.CodeAction{
216231
Title: d.Message,
217232
Kind: protocol.QuickFix,
218233
Command: &cmd,
219234
Diagnostics: []protocol.Diagnostic{pd},
220-
})
235+
}, true, nil
236+
}()
237+
if err != nil {
238+
return nil, err
239+
}
240+
if ok {
241+
actions = append(actions, action)
221242
}
222243
}
223244

@@ -387,7 +408,17 @@ func refactorExtract(ctx context.Context, snapshot source.Snapshot, pgf *source.
387408
return actions, nil
388409
}
389410

390-
func refactorRewrite(ctx context.Context, snapshot source.Snapshot, pkg source.Package, pgf *source.ParsedGoFile, fh source.FileHandle, rng protocol.Range) ([]protocol.CodeAction, error) {
411+
func refactorRewrite(ctx context.Context, snapshot source.Snapshot, pkg source.Package, pgf *source.ParsedGoFile, fh source.FileHandle, rng protocol.Range) (_ []protocol.CodeAction, rerr error) {
412+
// golang/go#61693: code actions were refactored to run outside of the
413+
// analysis framework, but as a result they lost their panic recovery.
414+
//
415+
// These code actions should never fail, but put back the panic recovery as a
416+
// defensive measure.
417+
defer func() {
418+
if r := recover(); r != nil {
419+
rerr = bug.Errorf("refactor.rewrite code actions panicked: %v", r)
420+
}
421+
}()
391422
start, end, err := pgf.RangePos(rng)
392423
if err != nil {
393424
return nil, err
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
This test exercises stub methods functionality with variadic parameters.
2+
3+
In golang/go#61693 stubmethods was panicking in this case.
4+
5+
-- go.mod --
6+
module mod.com
7+
8+
go 1.18
9+
-- main.go --
10+
package main
11+
12+
type C int
13+
14+
func F(err ...error) {}
15+
16+
func _() {
17+
var x error
18+
F(x, C(0)) //@suggestedfix(re"C.0.", re"missing method Error", "quickfix", stub)
19+
}
20+
-- @stub/main.go --
21+
package main
22+
23+
type C int
24+
25+
// Error implements error.
26+
func (C) Error() string {
27+
panic("unimplemented")
28+
}
29+
30+
func F(err ...error) {}
31+
32+
func _() {
33+
var x error
34+
F(x, C(0)) //@suggestedfix(re"C.0.", re"missing method Error", "quickfix", stub)
35+
}

gopls/internal/regtest/misc/formatting_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,3 +366,30 @@ const Bar = 42
366366
}
367367
})
368368
}
369+
370+
func TestGofumpt_Issue61692(t *testing.T) {
371+
testenv.NeedsGo1Point(t, 21)
372+
373+
const input = `
374+
-- go.mod --
375+
module foo
376+
377+
go 1.21rc3
378+
-- foo.go --
379+
package foo
380+
381+
func _() {
382+
foo :=
383+
"bar"
384+
}
385+
`
386+
387+
WithOptions(
388+
Settings{
389+
"gofumpt": true,
390+
},
391+
).Run(t, input, func(t *testing.T, env *Env) {
392+
env.OpenFile("foo.go")
393+
env.FormatBuffer("foo.go") // golang/go#61692: must not panic
394+
})
395+
}

0 commit comments

Comments
 (0)