Skip to content

Commit 2a9f7ad

Browse files
committed
Add ST1024: Avoid named error return values named 'err'
Named return values called 'err' are discouraged because they can lead to confusion and subtle mistakes. In most Go code, 'err' is used for short-lived local variables. Naming a return parameter 'err' can induce cognitive load. For example: func fn() (err error) { if err := doSomething(); err != nil { // shadows the return return err } defer func() {qqq // This 'err' is the named return, not the one above if err != nil { log.Println("deferred error:", err) } }() return nil } Using a distinct name for the named return makes the return value’s role explicit and avoids confusion: func fn() (retErr error) { if err := doSomething(); err != nil { return err } defer func() { if retErr != nil { log.Println("deferred error:", retErr) } }() return nil } This check is opt-in and not enabled by default. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent ba737e5 commit 2a9f7ad

File tree

5 files changed

+232
-0
lines changed

5 files changed

+232
-0
lines changed

stylecheck/analysis.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

stylecheck/st1024/st1024.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package st1024
2+
3+
import (
4+
"go/types"
5+
6+
"golang.org/x/tools/go/analysis"
7+
"honnef.co/go/tools/analysis/lint"
8+
"honnef.co/go/tools/analysis/report"
9+
"honnef.co/go/tools/internal/passes/buildir"
10+
)
11+
12+
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
13+
Analyzer: &analysis.Analyzer{
14+
Name: "ST1024",
15+
Run: run,
16+
Requires: []*analysis.Analyzer{buildir.Analyzer},
17+
},
18+
Doc: &lint.RawDocumentation{
19+
Title: "Avoid named error return values named 'err'",
20+
Text: `Named return values called 'err' are discouraged because they can lead
21+
to confusion and subtle mistakes.
22+
23+
In most Go code, 'err' is used for short-lived local variables. Naming a
24+
return parameter 'err' can induce cognitive load.
25+
26+
For example:
27+
28+
func fn() (err error) {
29+
if err := doSomething(); err != nil { // shadows the return
30+
return err
31+
}
32+
defer func() {qqq
33+
// This 'err' is the named return, not the one above
34+
if err != nil {
35+
log.Println("deferred error:", err)
36+
}
37+
}()
38+
return nil
39+
}
40+
41+
Using a distinct name for the named return makes the return value’s
42+
role explicit and avoids confusion:
43+
44+
func fn() (retErr error) {
45+
if err := doSomething(); err != nil {
46+
return err
47+
}
48+
defer func() {
49+
if retErr != nil {
50+
log.Println("deferred error:", retErr)
51+
}
52+
}()
53+
return nil
54+
}
55+
56+
This check is opt-in and not enabled by default.`,
57+
Since: "2025.2",
58+
NonDefault: true,
59+
MergeIf: lint.MergeIfAll,
60+
},
61+
})
62+
63+
func run(pass *analysis.Pass) (interface{}, error) {
64+
errorType := types.Universe.Lookup("error").Type()
65+
66+
fnLoop:
67+
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
68+
sig := fn.Type().(*types.Signature)
69+
rets := sig.Results()
70+
if rets == nil {
71+
continue
72+
}
73+
for i := range rets.Len() {
74+
if r := rets.At(i); types.Unalias(r.Type()) == errorType && r.Name() == "err" {
75+
report.Report(pass, rets.At(i), "named error return should not be named 'err'", report.ShortRange())
76+
continue fnLoop
77+
}
78+
}
79+
}
80+
return nil, nil
81+
}

stylecheck/st1024/st1024_test.go

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package pkg
2+
3+
// no names
4+
func fnA0() {}
5+
func fnA1() error { return nil }
6+
func fnA2() (int, error) { return 0, nil }
7+
func fnA3() (error, int) { return nil, 0 }
8+
func fnA4() (error, error) { return nil, nil }
9+
10+
// names, but not 'err'
11+
func fnB1() (a error) { return nil }
12+
func fnB2() (a int, b error) { return 0, nil }
13+
func fnB3() (a error, b int) { return nil, 0 }
14+
func fnB4() (a error, b error) { return nil, nil }
15+
16+
// names, and '_'
17+
func fnC1() (_ error) { return nil }
18+
func fnC2() (a int, _ error) { return 0, nil }
19+
func fnC3() (_ error, b int) { return nil, 0 }
20+
func fnC4() (_ error, b error) { return nil, nil }
21+
func fnC5() (a error, _ error) { return nil, nil }
22+
23+
// false positives: non-errors named 'err'
24+
func fnE1() (err int) { return 0 }
25+
func fnE2() (err int, b error) { return 0, nil }
26+
func fnE3() (a error, err int) { return nil, 0 }
27+
28+
// bad ones: errors named 'err'
29+
func fnD1() (err error) { return nil } //@ diag(`named error return should not be named 'err'`)
30+
func fnD2() (a int, err error) { return 0, nil } //@ diag(`named error return should not be named 'err'`)
31+
func fnD3() (err error, b int) { return nil, 0 } //@ diag(`named error return should not be named 'err'`)
32+
func fnD4() (err error, b error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
33+
func fnD5() (a error, err error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
34+
35+
type structA struct{}
36+
37+
// no names
38+
func (structA) fnA0() {}
39+
func (structA) fnA1() error { return nil }
40+
func (structA) fnA2() (int, error) { return 0, nil }
41+
func (structA) fnA3() (error, int) { return nil, 0 }
42+
func (structA) fnA4() (error, error) { return nil, nil }
43+
44+
// names, but not 'err'
45+
func (structA) fnB1() (a error) { return nil }
46+
func (structA) fnB2() (a int, b error) { return 0, nil }
47+
func (structA) fnB3() (a error, b int) { return nil, 0 }
48+
func (structA) fnB4() (a error, b error) { return nil, nil }
49+
50+
// names, and '_'
51+
func (structA) fnC1() (_ error) { return nil }
52+
func (structA) fnC2() (a int, _ error) { return 0, nil }
53+
func (structA) fnC3() (_ error, b int) { return nil, 0 }
54+
func (structA) fnC4() (_ error, b error) { return nil, nil }
55+
func (structA) fnC5() (a error, _ error) { return nil, nil }
56+
57+
// false positives: non-errors named 'err'
58+
func (structA) fnE1() (err int) { return 0 }
59+
func (structA) fnE2() (err int, b error) { return 0, nil }
60+
func (structA) fnE3() (a error, err int) { return nil, 0 }
61+
62+
// bad ones: errors named 'err'
63+
func (structA) fnD1() (err error) { return nil } //@ diag(`named error return should not be named 'err'`)
64+
func (structA) fnD2() (a int, err error) { return 0, nil } //@ diag(`named error return should not be named 'err'`)
65+
func (structA) fnD3() (err error, b int) { return nil, 0 } //@ diag(`named error return should not be named 'err'`)
66+
func (structA) fnD4() (err error, b error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
67+
func (structA) fnD5() (a error, err error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package pkg
2+
3+
type Error = error
4+
5+
// no names
6+
func fnA0() {}
7+
func fnA1() Error { return nil }
8+
func fnA2() (int, Error) { return 0, nil }
9+
func fnA3() (Error, int) { return nil, 0 }
10+
func fnA4() (Error, Error) { return nil, nil }
11+
12+
// names, but not 'err'
13+
func fnB1() (a Error) { return nil }
14+
func fnB2() (a int, b Error) { return 0, nil }
15+
func fnB3() (a Error, b int) { return nil, 0 }
16+
func fnB4() (a Error, b Error) { return nil, nil }
17+
18+
// names, and '_'
19+
func fnC1() (_ Error) { return nil }
20+
func fnC2() (a int, _ Error) { return 0, nil }
21+
func fnC3() (_ Error, b int) { return nil, 0 }
22+
func fnC4() (_ Error, b Error) { return nil, nil }
23+
func fnC5() (a Error, _ Error) { return nil, nil }
24+
25+
// false positives: non-errors named 'err'
26+
func fnE1() (err int) { return 0 }
27+
func fnE2() (err int, b Error) { return 0, nil }
28+
func fnE3() (a Error, err int) { return nil, 0 }
29+
30+
// bad ones: errors named 'err'
31+
func fnD1() (err Error) { return nil } //@ diag(`named error return should not be named 'err'`)
32+
func fnD2() (a int, err Error) { return 0, nil } //@ diag(`named error return should not be named 'err'`)
33+
func fnD3() (err Error, b int) { return nil, 0 } //@ diag(`named error return should not be named 'err'`)
34+
func fnD4() (err Error, b Error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
35+
func fnD5() (a Error, err Error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
36+
37+
type structA struct{}
38+
39+
// no names
40+
func (structA) fnA0() {}
41+
func (structA) fnA1() Error { return nil }
42+
func (structA) fnA2() (int, Error) { return 0, nil }
43+
func (structA) fnA3() (Error, int) { return nil, 0 }
44+
func (structA) fnA4() (Error, Error) { return nil, nil }
45+
46+
// names, but not 'err'
47+
func (structA) fnB1() (a Error) { return nil }
48+
func (structA) fnB2() (a int, b Error) { return 0, nil }
49+
func (structA) fnB3() (a Error, b int) { return nil, 0 }
50+
func (structA) fnB4() (a Error, b Error) { return nil, nil }
51+
52+
// names, and '_'
53+
func (structA) fnC1() (_ Error) { return nil }
54+
func (structA) fnC2() (a int, _ Error) { return 0, nil }
55+
func (structA) fnC3() (_ Error, b int) { return nil, 0 }
56+
func (structA) fnC4() (_ Error, b Error) { return nil, nil }
57+
func (structA) fnC5() (a Error, _ Error) { return nil, nil }
58+
59+
// false positives: non-errors named 'err'
60+
func (structA) fnE1() (err int) { return 0 }
61+
func (structA) fnE2() (err int, b Error) { return 0, nil }
62+
func (structA) fnE3() (a Error, err int) { return nil, 0 }
63+
64+
// bad ones: errors named 'err'
65+
func (structA) fnD1() (err Error) { return nil } //@ diag(`named error return should not be named 'err'`)
66+
func (structA) fnD2() (a int, err Error) { return 0, nil } //@ diag(`named error return should not be named 'err'`)
67+
func (structA) fnD3() (err Error, b int) { return nil, 0 } //@ diag(`named error return should not be named 'err'`)
68+
func (structA) fnD4() (err Error, b Error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)
69+
func (structA) fnD5() (a Error, err Error) { return nil, nil } //@ diag(`named error return should not be named 'err'`)

0 commit comments

Comments
 (0)