Skip to content

Commit 92fdfda

Browse files
committed
go/analysis/passes/modernize: errorsastype: errors.As -> AsType[T]
This CL adds a modernizer that replaces simple uses of errors.As such as: var myerr *MyErr if errors.As(err, &myerr) { handle(myerr) } with the less error-prone generic [errors.AsType] function, introduced in Go 1.26: if myerr, ok := errors.AsType[*MyErr](err); ok { handle(myerr) } Also, redo the existing errorsas vet check (for actual bugs) to take advantage of newer analysis features. Fixes golang/go#75692 Updates golang/go#51945 Change-Id: I56bd5370449daeacedf5634e46c2a29697dcbf19 Reviewed-on: https://go-review.googlesource.com/c/tools/+/708895 Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 133beb8 commit 92fdfda

File tree

14 files changed

+477
-39
lines changed

14 files changed

+477
-39
lines changed

go/analysis/passes/errorsas/errorsas.go

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,20 @@ import (
1212
"go/types"
1313

1414
"golang.org/x/tools/go/analysis"
15-
"golang.org/x/tools/go/analysis/passes/inspect"
16-
"golang.org/x/tools/go/ast/inspector"
17-
"golang.org/x/tools/go/types/typeutil"
18-
"golang.org/x/tools/internal/analysisinternal"
15+
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
16+
"golang.org/x/tools/internal/typesinternal/typeindex"
1917
)
2018

2119
const Doc = `report passing non-pointer or non-error values to errors.As
2220
23-
The errorsas analysis reports calls to errors.As where the type
21+
The errorsas analyzer reports calls to errors.As where the type
2422
of the second argument is not a pointer to a type implementing error.`
2523

2624
var Analyzer = &analysis.Analyzer{
2725
Name: "errorsas",
2826
Doc: Doc,
2927
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
30-
Requires: []*analysis.Analyzer{inspect.Analyzer},
28+
Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
3129
Run: run,
3230
}
3331

@@ -39,51 +37,48 @@ func run(pass *analysis.Pass) (any, error) {
3937
return nil, nil
4038
}
4139

42-
if !analysisinternal.Imports(pass.Pkg, "errors") {
43-
return nil, nil // doesn't directly import errors
44-
}
45-
46-
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
40+
var (
41+
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
42+
info = pass.TypesInfo
43+
)
4744

48-
nodeFilter := []ast.Node{
49-
(*ast.CallExpr)(nil),
50-
}
51-
inspect.Preorder(nodeFilter, func(n ast.Node) {
52-
call := n.(*ast.CallExpr)
53-
obj := typeutil.Callee(pass.TypesInfo, call)
54-
if !analysisinternal.IsFunctionNamed(obj, "errors", "As") {
55-
return
56-
}
45+
for curCall := range index.Calls(index.Object("errors", "As")) {
46+
call := curCall.Node().(*ast.CallExpr)
5747
if len(call.Args) < 2 {
58-
return // not enough arguments, e.g. called with return values of another function
48+
continue // spread call: errors.As(pair())
5949
}
60-
if err := checkAsTarget(pass, call.Args[1]); err != nil {
50+
51+
// Check for incorrect arguments.
52+
if err := checkAsTarget(info, call.Args[1]); err != nil {
6153
pass.ReportRangef(call, "%v", err)
54+
continue
6255
}
63-
})
56+
}
6457
return nil, nil
6558
}
6659

67-
var errorType = types.Universe.Lookup("error").Type()
68-
6960
// checkAsTarget reports an error if the second argument to errors.As is invalid.
70-
func checkAsTarget(pass *analysis.Pass, e ast.Expr) error {
71-
t := pass.TypesInfo.Types[e].Type
72-
if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
73-
// A target of interface{} is always allowed, since it often indicates
61+
func checkAsTarget(info *types.Info, e ast.Expr) error {
62+
t := info.Types[e].Type
63+
if types.Identical(t.Underlying(), anyType) {
64+
// A target of any is always allowed, since it often indicates
7465
// a value forwarded from another source.
7566
return nil
7667
}
7768
pt, ok := t.Underlying().(*types.Pointer)
7869
if !ok {
7970
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
8071
}
81-
if pt.Elem() == errorType {
72+
if types.Identical(pt.Elem(), errorType) {
8273
return errors.New("second argument to errors.As should not be *error")
8374
}
84-
_, ok = pt.Elem().Underlying().(*types.Interface)
85-
if ok || types.Implements(pt.Elem(), errorType.Underlying().(*types.Interface)) {
86-
return nil
75+
if !types.IsInterface(pt.Elem()) && !types.AssignableTo(pt.Elem(), errorType) {
76+
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
8777
}
88-
return errors.New("second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type")
78+
return nil
8979
}
80+
81+
var (
82+
anyType = types.Universe.Lookup("any").Type()
83+
errorType = types.Universe.Lookup("error").Type()
84+
)

go/analysis/passes/errorsas/errorsas_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
//go:build go1.13
6-
75
package errorsas_test
86

97
import (
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2025 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 ignore
6+
7+
// The errorsas command applies the golang.org/x/tools/go/analysis/passes/errorsas
8+
// analysis to the specified packages of Go source code.
9+
package main
10+
11+
import (
12+
"golang.org/x/tools/go/analysis/passes/errorsas"
13+
"golang.org/x/tools/go/analysis/singlechecker"
14+
)
15+
16+
func main() { singlechecker.Main(errorsas.Analyzer) }

go/analysis/passes/modernize/doc.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,28 @@ The any analyzer suggests replacing uses of the empty interface type,
8989
`interface{}`, with the `any` alias, which was introduced in Go 1.18.
9090
This is a purely stylistic change that makes code more readable.
9191
92+
# Analyzer errorsastype
93+
94+
errorsastype: replace errors.As with errors.AsType[T]
95+
96+
This analyzer suggests fixes to simplify uses of [errors.As] of
97+
this form:
98+
99+
var myerr *MyErr
100+
if errors.As(err, &myerr) {
101+
handle(myerr)
102+
}
103+
104+
by using the less error-prone generic [errors.AsType] function,
105+
introduced in Go 1.26:
106+
107+
if myerr, ok := errors.AsType[*MyErr](err); ok {
108+
handle(myerr)
109+
}
110+
111+
The fix is only offered if the var declaration has the form shown and
112+
there are no uses of myerr outside the if statement.
113+
92114
# Analyzer fmtappendf
93115
94116
fmtappendf: replace []byte(fmt.Sprintf) with fmt.Appendf

0 commit comments

Comments
 (0)