Skip to content

Commit 72ae882

Browse files
committed
"clothe" naked returns for the sake of clarity
When a function has named results, one can use a "naked return" to implicitly return those variables. That is, the code func Foo() (err error) { return } is functionally equivalent to func Foo() (err error) { return err } Naked returns exist primarily for the sake of avoiding repetition, but even the Go tour recommends that their use should be limited: Naked return statements should be used only in short functions, as with the example shown here. They can harm readability in longer functions. Most Go functions out in the wild aren't short, and in practice, naked returns tend to be avoided in larger or collaborative codebases as they confuse the reader about whether any values are being returned, or which values are being returned if any. Given that gofumpt is already opinionated, and its use tends to be on Go modules on the larger side or with multiple contributors, it seems best to rewrite naked returns to "clothe" them. Fixes #285.
1 parent 5e1cad6 commit 72ae882

File tree

3 files changed

+132
-1
lines changed

3 files changed

+132
-1
lines changed

Diff for: README.md

+19-1
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,25 @@ type ZeroFields struct {
435435

436436
</details>
437437

438-
#### Extra rules behind `-extra`
438+
**Avoid naked returns for the sake of clarity**
439+
440+
<details><summary><i>Example</i></summary>
441+
442+
```go
443+
func Foo() (err error) {
444+
return
445+
}
446+
```
447+
448+
```go
449+
func Foo() (err error) {
450+
return err
451+
}
452+
```
453+
454+
</details>
455+
456+
### Extra rules behind `-extra`
439457

440458
**Adjacent parameters with the same type should be grouped together**
441459

Diff for: format/format.go

+39
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ func File(fset *token.FileSet, file *ast.File, opts Options) {
114114
switch node := c.Node().(type) {
115115
case *ast.FuncDecl:
116116
topFuncType = node.Type
117+
f.parentFuncTypes = append(f.parentFuncTypes, node.Type)
118+
case *ast.FuncLit:
119+
f.parentFuncTypes = append(f.parentFuncTypes, node.Type)
117120
case *ast.FieldList:
118121
ft, _ := c.Parent().(*ast.FuncType)
119122
if ft == nil || ft != topFuncType {
@@ -149,6 +152,8 @@ func File(fset *token.FileSet, file *ast.File, opts Options) {
149152

150153
// Reset minSplitFactor and blockLevel.
151154
switch node := c.Node().(type) {
155+
case *ast.FuncDecl, *ast.FuncLit:
156+
f.parentFuncTypes = f.parentFuncTypes[:len(f.parentFuncTypes)-1]
152157
case *ast.FuncType:
153158
if node == topFuncType {
154159
f.minSplitFactor = 0.4
@@ -185,6 +190,10 @@ type fumpter struct {
185190
blockLevel int
186191

187192
minSplitFactor float64
193+
194+
// parentFuncTypes is a stack of parent function types,
195+
// used to determine return type information when clothing naked returns.
196+
parentFuncTypes []*ast.FuncType
188197
}
189198

190199
func (f *fumpter) commentsBetween(p1, p2 token.Pos) []*ast.CommentGroup {
@@ -685,6 +694,36 @@ func (f *fumpter) applyPre(c *astutil.Cursor) {
685694
case *ast.AssignStmt:
686695
// Only remove lines between the assignment token and the first right-hand side expression
687696
f.removeLines(f.Line(node.TokPos), f.Line(node.Rhs[0].Pos()))
697+
698+
case *ast.ReturnStmt:
699+
if len(node.Results) > 0 {
700+
break
701+
}
702+
results := f.parentFuncTypes[len(f.parentFuncTypes)-1].Results
703+
if results.NumFields() == 0 {
704+
break
705+
}
706+
707+
// The function has return values; let's clothe the return.
708+
node.Results = make([]ast.Expr, 0, results.NumFields())
709+
nameLoop:
710+
for _, result := range results.List {
711+
for _, ident := range result.Names {
712+
name := ident.Name
713+
if name == "_" { // we can't handle blank names just yet
714+
node.Results = nil
715+
break nameLoop
716+
}
717+
node.Results = append(node.Results, &ast.Ident{
718+
// Use the Pos of the return statement, to not interfere with comment placement.
719+
NamePos: node.Pos(),
720+
Name: name,
721+
})
722+
}
723+
}
724+
if len(node.Results) > 0 {
725+
c.Replace(node)
726+
}
688727
}
689728
}
690729

Diff for: testdata/script/clothe-returns.txtar

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
exec gofumpt -w foo.go
2+
cmp foo.go foo.go.golden
3+
4+
exec gofumpt -d foo.go.golden
5+
! stdout .
6+
7+
-- foo.go --
8+
package p
9+
10+
func foo() (err error) {
11+
if true {
12+
return
13+
}
14+
if false {
15+
return func() (err2 error) {
16+
return
17+
}
18+
}
19+
return
20+
}
21+
22+
func bar() (_ int, err error) {
23+
return
24+
}
25+
26+
func baz() (a, b, c int) {
27+
return
28+
}
29+
30+
func qux() (file string, b int, err error) {
31+
if err == nil {
32+
return
33+
}
34+
35+
// A comment
36+
return
37+
}
38+
39+
// quux does quuxy things
40+
func quux() {}
41+
-- foo.go.golden --
42+
package p
43+
44+
func foo() (err error) {
45+
if true {
46+
return err
47+
}
48+
if false {
49+
return func() (err2 error) {
50+
return err2
51+
}
52+
}
53+
return err
54+
}
55+
56+
func bar() (_ int, err error) {
57+
return
58+
}
59+
60+
func baz() (a, b, c int) {
61+
return a, b, c
62+
}
63+
64+
func qux() (file string, b int, err error) {
65+
if err == nil {
66+
return file, b, err
67+
}
68+
69+
// A comment
70+
return file, b, err
71+
}
72+
73+
// quux does quuxy things
74+
func quux() {}

0 commit comments

Comments
 (0)