Skip to content

Commit 706525d

Browse files
rogerykgopherbot
authored andcommitted
gopls/internal/lsp/source/completion: support postfix completion (iferr,
variferr) Go require explicit error handle, you must check and return error after some function call. These postfix completion can help to write the check error code. The postfix completion iferr replace strconv.Atoi("32").iferr to if _, err := strconv.Atoi("32"); err != nil { return zero1, zero2, err } The postfix completion variferr replace strconv.Atoi("32").variferr to value, err := strconv.Atoi("32"); if err != nil { return zero1, zero2, err } The "zero1", "zero2" represent the zero value of enclosed function results. Fixes golang/go#64178 Change-Id: I8313168514bfdfd22ad03b0228d4ca738ba9e9e3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/550455 Reviewed-by: Robert Findley <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Auto-Submit: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 581c0b3 commit 706525d

File tree

4 files changed

+217
-13
lines changed

4 files changed

+217
-13
lines changed

gopls/internal/lsp/source/completion/postfix_snippets.go

Lines changed: 152 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,18 @@ type postfixTmplArgs struct {
6868
// Type is the type of "foo.bar" in "foo.bar.print!".
6969
Type types.Type
7070

71+
// FuncResult are results of the enclosed function
72+
FuncResults []*types.Var
73+
74+
sel *ast.SelectorExpr
7175
scope *types.Scope
7276
snip snippet.Builder
7377
importIfNeeded func(pkgPath string, scope *types.Scope) (name string, edits []protocol.TextEdit, err error)
7478
edits []protocol.TextEdit
7579
qf types.Qualifier
7680
varNames map[string]bool
7781
placeholders bool
82+
currentTabStop int
7883
}
7984

8085
var postfixTmpls = []postfixTmpl{{
@@ -250,26 +255,119 @@ if {{.X}} != nil {
250255
body: `{{if (eq .Kind "slice" "map" "array" "chan") -}}
251256
len({{.X}})
252257
{{- end}}`,
258+
}, {
259+
label: "iferr",
260+
details: "check error and return",
261+
body: `{{if and .StmtOK (eq (.TypeName .Type) "error") -}}
262+
{{- $errName := (or (and .IsIdent .X) "err") -}}
263+
if {{if not .IsIdent}}err := {{.X}}; {{end}}{{$errName}} != nil {
264+
return {{$a := .}}{{range $i, $v := .FuncResults}}
265+
{{- if $i}}, {{end -}}
266+
{{- if eq ($a.TypeName $v.Type) "error" -}}
267+
{{$a.Placeholder $errName}}
268+
{{- else -}}
269+
{{$a.Zero $v.Type}}
270+
{{- end -}}
271+
{{end}}
272+
}
273+
{{end}}`,
274+
}, {
275+
label: "iferr",
276+
details: "check error and return",
277+
body: `{{if and .StmtOK (eq .Kind "tuple") (len .Tuple) (eq (.TypeName .TupleLast.Type) "error") -}}
278+
{{- $a := . -}}
279+
if {{range $i, $v := .Tuple}}{{if $i}}, {{end}}{{if and (eq ($a.TypeName $v.Type) "error") (eq (inc $i) (len $a.Tuple))}}err{{else}}_{{end}}{{end}} := {{.X -}}
280+
; err != nil {
281+
return {{range $i, $v := .FuncResults}}
282+
{{- if $i}}, {{end -}}
283+
{{- if eq ($a.TypeName $v.Type) "error" -}}
284+
{{$a.Placeholder "err"}}
285+
{{- else -}}
286+
{{$a.Zero $v.Type}}
287+
{{- end -}}
288+
{{end}}
289+
}
290+
{{end}}`,
291+
}, {
292+
// variferr snippets use nested placeholders, as described in
293+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax,
294+
// so that users can wrap the returned error without modifying the error
295+
// variable name.
296+
label: "variferr",
297+
details: "assign variables and check error",
298+
body: `{{if and .StmtOK (eq .Kind "tuple") (len .Tuple) (eq (.TypeName .TupleLast.Type) "error") -}}
299+
{{- $a := . -}}
300+
{{- $errName := "err" -}}
301+
{{- range $i, $v := .Tuple -}}
302+
{{- if $i}}, {{end -}}
303+
{{- if and (eq ($a.TypeName $v.Type) "error") (eq (inc $i) (len $a.Tuple)) -}}
304+
{{$errName | $a.SpecifiedPlaceholder (len $a.Tuple)}}
305+
{{- else -}}
306+
{{$a.VarName $v.Type $v.Name | $a.Placeholder}}
307+
{{- end -}}
308+
{{- end}} := {{.X}}
309+
if {{$errName | $a.SpecifiedPlaceholder (len $a.Tuple)}} != nil {
310+
return {{range $i, $v := .FuncResults}}
311+
{{- if $i}}, {{end -}}
312+
{{- if eq ($a.TypeName $v.Type) "error" -}}
313+
{{$errName | $a.SpecifiedPlaceholder (len $a.Tuple) |
314+
$a.SpecifiedPlaceholder (inc (len $a.Tuple))}}
315+
{{- else -}}
316+
{{$a.Zero $v.Type}}
317+
{{- end -}}
318+
{{end}}
319+
}
320+
{{end}}`,
321+
}, {
322+
label: "variferr",
323+
details: "assign variables and check error",
324+
body: `{{if and .StmtOK (eq (.TypeName .Type) "error") -}}
325+
{{- $a := . -}}
326+
{{- $errName := .VarName nil "err" -}}
327+
{{$errName | $a.SpecifiedPlaceholder 1}} := {{.X}}
328+
if {{$errName | $a.SpecifiedPlaceholder 1}} != nil {
329+
return {{range $i, $v := .FuncResults}}
330+
{{- if $i}}, {{end -}}
331+
{{- if eq ($a.TypeName $v.Type) "error" -}}
332+
{{$errName | $a.SpecifiedPlaceholder 1 | $a.SpecifiedPlaceholder 2}}
333+
{{- else -}}
334+
{{$a.Zero $v.Type}}
335+
{{- end -}}
336+
{{end}}
337+
}
338+
{{end}}`,
253339
}}
254340

255341
// Cursor indicates where the client's cursor should end up after the
256342
// snippet is done.
257343
func (a *postfixTmplArgs) Cursor() string {
258-
a.snip.WriteFinalTabstop()
259-
return ""
344+
return "$0"
260345
}
261346

262-
// Placeholder indicate a tab stops with the placeholder string, the order
347+
// Placeholder indicate a tab stop with the placeholder string, the order
263348
// of tab stops is the same as the order of invocation
264-
func (a *postfixTmplArgs) Placeholder(s string) string {
265-
if a.placeholders {
266-
a.snip.WritePlaceholder(func(b *snippet.Builder) {
267-
b.WriteText(s)
268-
})
269-
} else {
270-
a.snip.WritePlaceholder(nil)
349+
func (a *postfixTmplArgs) Placeholder(placeholder string) string {
350+
if !a.placeholders {
351+
placeholder = ""
352+
}
353+
return fmt.Sprintf("${%d:%s}", a.nextTabStop(), placeholder)
354+
}
355+
356+
// nextTabStop returns the next tab stop index for a new placeholder.
357+
func (a *postfixTmplArgs) nextTabStop() int {
358+
// Tab stops start from 1, so increment before returning.
359+
a.currentTabStop++
360+
return a.currentTabStop
361+
}
362+
363+
// SpecifiedPlaceholder indicate a specified tab stop with the placeholder string.
364+
// Sometimes the same tab stop appears in multiple places and their numbers
365+
// need to be specified. e.g. variferr
366+
func (a *postfixTmplArgs) SpecifiedPlaceholder(tabStop int, placeholder string) string {
367+
if !a.placeholders {
368+
placeholder = ""
271369
}
272-
return ""
370+
return fmt.Sprintf("${%d:%s}", tabStop, placeholder)
273371
}
274372

275373
// Import makes sure the package corresponding to path is imported,
@@ -309,7 +407,7 @@ func (a *postfixTmplArgs) KeyType() types.Type {
309407
return a.Type.Underlying().(*types.Map).Key()
310408
}
311409

312-
// Tuple returns the tuple result vars if X is a call expression.
410+
// Tuple returns the tuple result vars if the type of X is tuple.
313411
func (a *postfixTmplArgs) Tuple() []*types.Var {
314412
tuple, _ := a.Type.(*types.Tuple)
315413
if tuple == nil {
@@ -323,6 +421,18 @@ func (a *postfixTmplArgs) Tuple() []*types.Var {
323421
return typs
324422
}
325423

424+
// TupleLast returns the last tuple result vars if the type of X is tuple.
425+
func (a *postfixTmplArgs) TupleLast() *types.Var {
426+
tuple, _ := a.Type.(*types.Tuple)
427+
if tuple == nil {
428+
return nil
429+
}
430+
if tuple.Len() == 0 {
431+
return nil
432+
}
433+
return tuple.At(tuple.Len() - 1)
434+
}
435+
326436
// TypeName returns the textual representation of type t.
327437
func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
328438
if t == nil || t == types.Typ[types.Invalid] {
@@ -331,6 +441,16 @@ func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
331441
return types.TypeString(t, a.qf), nil
332442
}
333443

444+
// Zero return the zero value representation of type t
445+
func (a *postfixTmplArgs) Zero(t types.Type) string {
446+
return formatZeroValue(t, a.qf)
447+
}
448+
449+
func (a *postfixTmplArgs) IsIdent() bool {
450+
_, ok := a.sel.X.(*ast.Ident)
451+
return ok
452+
}
453+
334454
// VarName returns a suitable variable name for the type t. If t
335455
// implements the error interface, "err" is used. If t is not a named
336456
// type then nonNamedDefault is used. Otherwise a name is made by
@@ -417,6 +537,17 @@ func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.Se
417537
}
418538
}
419539

540+
var funcResults []*types.Var
541+
if c.enclosingFunc != nil {
542+
results := c.enclosingFunc.sig.Results()
543+
if results != nil {
544+
funcResults = make([]*types.Var, results.Len())
545+
for i := 0; i < results.Len(); i++ {
546+
funcResults[i] = results.At(i)
547+
}
548+
}
549+
}
550+
420551
scope := c.pkg.GetTypes().Scope().Innermost(c.pos)
421552
if scope == nil {
422553
return
@@ -455,6 +586,8 @@ func (c *completer) addPostfixSnippetCandidates(ctx context.Context, sel *ast.Se
455586
StmtOK: stmtOK,
456587
Obj: exprObj(c.pkg.GetTypesInfo(), sel.X),
457588
Type: selType,
589+
FuncResults: funcResults,
590+
sel: sel,
458591
qf: c.qf,
459592
importIfNeeded: c.importIfNeeded,
460593
scope: scope,
@@ -497,7 +630,9 @@ func initPostfixRules() {
497630
var idx int
498631
for _, rule := range postfixTmpls {
499632
var err error
500-
rule.tmpl, err = template.New("postfix_snippet").Parse(rule.body)
633+
rule.tmpl, err = template.New("postfix_snippet").Funcs(template.FuncMap{
634+
"inc": inc,
635+
}).Parse(rule.body)
501636
if err != nil {
502637
log.Panicf("error parsing postfix snippet template: %v", err)
503638
}
@@ -508,6 +643,10 @@ func initPostfixRules() {
508643
})
509644
}
510645

646+
func inc(i int) int {
647+
return i + 1
648+
}
649+
511650
// importIfNeeded returns the package identifier and any necessary
512651
// edits to import package pkgPath.
513652
func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string, []protocol.TextEdit, error) {

gopls/internal/test/integration/completion/postfix_snippet_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ func _() {
306306
${1:}, ${2:} := foo()
307307
}
308308
`,
309+
allowMultipleItem: true,
309310
},
310311
{
311312
name: "var_single_value",
@@ -318,6 +319,7 @@ func _() {
318319
foo().var
319320
}
320321
`,
322+
allowMultipleItem: true,
321323
after: `
322324
package foo
323325

gopls/internal/test/marker/testdata/completion/postfix.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ go 1.18
1313
-- postfix.go --
1414
package snippets
1515

16+
import (
17+
"strconv"
18+
)
19+
1620
func _() {
1721
var foo []int
1822
foo.append //@rank(" //", postfixAppend)
@@ -96,3 +100,32 @@ func _() {
96100
foo.fo //@snippet(" //", postfixForChannel, "for ${1:} := range foo {\n\t$0\n}")
97101
foo.rang //@snippet(" //", postfixRangeChannel, "for ${1:} := range foo {\n\t$0\n}")
98102
}
103+
104+
type T struct {
105+
Name string
106+
}
107+
108+
func _() (string, T, map[string]string, error) {
109+
/* iferr! */ //@item(postfixIfErr, "iferr!", "check error and return", "snippet")
110+
/* variferr! */ //@item(postfixVarIfErr, "variferr!", "assign variables and check error", "snippet")
111+
/* var! */ //@item(postfixVars, "var!", "assign to variables", "snippet")
112+
113+
strconv.Atoi("32"). //@complete(" //", postfixIfErr, postfixPrint, postfixVars, postfixVarIfErr)
114+
115+
var err error
116+
err.iferr //@snippet(" //", postfixIfErr, "if err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n")
117+
118+
strconv.Atoi("32").iferr //@snippet(" //", postfixIfErr, "if _, err := strconv.Atoi(\"32\"); err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n")
119+
120+
strconv.Atoi("32").variferr //@snippet(" //", postfixVarIfErr, "${1:}, ${2:} := strconv.Atoi(\"32\")\nif ${2:} != nil {\n\treturn \"\", T{}, nil, ${3:}\n}\n")
121+
122+
// test function return multiple errors
123+
var foo func() (error, error)
124+
foo().iferr //@snippet(" //", postfixIfErr, "if _, err := foo(); err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n")
125+
foo().variferr //@snippet(" //", postfixVarIfErr, "${1:}, ${2:} := foo()\nif ${2:} != nil {\n\treturn \"\", T{}, nil, ${3:}\n}\n")
126+
127+
// test function just return error
128+
var bar func() error
129+
bar().iferr //@snippet(" //", postfixIfErr, "if err := bar(); err != nil {\n\treturn \"\", T{}, nil, ${1:}\n}\n")
130+
bar().variferr //@snippet(" //", postfixVarIfErr, "${1:} := bar()\nif ${1:} != nil {\n\treturn \"\", T{}, nil, ${2:}\n}\n")
131+
}

gopls/internal/test/marker/testdata/completion/postfix_placeholder.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ go 1.18
1616
-- postfix.go --
1717
package snippets
1818

19+
import (
20+
"strconv"
21+
)
22+
1923
func _() {
2024
/* for! */ //@item(postfixFor, "for!", "range over slice by index", "snippet")
2125
/* forr! */ //@item(postfixForr, "forr!", "range over slice by index and value", "snippet")
@@ -51,3 +55,29 @@ func _() {
5155
foo.fo //@snippet(" //", postfixForChannel, "for ${1:e} := range foo {\n\t$0\n}")
5256
foo.rang //@snippet(" //", postfixRangeChannel, "for ${1:e} := range foo {\n\t$0\n}")
5357
}
58+
59+
type T struct {
60+
Name string
61+
}
62+
63+
func _() (string, T, map[string]string, error) {
64+
/* iferr! */ //@item(postfixIfErr, "iferr!", "check error and return", "snippet")
65+
/* variferr! */ //@item(postfixVarIfErr, "variferr!", "assign variables and check error", "snippet")
66+
/* var! */ //@item(postfixVars, "var!", "assign to variables", "snippet")
67+
68+
69+
var err error
70+
err.iferr //@snippet(" //", postfixIfErr, "if err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n")
71+
strconv.Atoi("32").iferr //@snippet(" //", postfixIfErr, "if _, err := strconv.Atoi(\"32\"); err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n")
72+
strconv.Atoi("32").variferr //@snippet(" //", postfixVarIfErr, "${1:i}, ${2:err} := strconv.Atoi(\"32\")\nif ${2:err} != nil {\n\treturn \"\", T{}, nil, ${3:${2:err}}\n}\n")
73+
74+
// test function return multiple errors
75+
var foo func() (error, error)
76+
foo().iferr //@snippet(" //", postfixIfErr, "if _, err := foo(); err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n")
77+
foo().variferr //@snippet(" //", postfixVarIfErr, "${1:err2}, ${2:err} := foo()\nif ${2:err} != nil {\n\treturn \"\", T{}, nil, ${3:${2:err}}\n}\n")
78+
79+
// test function just return error
80+
var bar func() error
81+
bar().iferr //@snippet(" //", postfixIfErr, "if err := bar(); err != nil {\n\treturn \"\", T{}, nil, ${1:err}\n}\n")
82+
bar().variferr //@snippet(" //", postfixVarIfErr, "${1:err2} := bar()\nif ${1:err2} != nil {\n\treturn \"\", T{}, nil, ${2:${1:err2}}\n}\n")
83+
}

0 commit comments

Comments
 (0)