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>
diff --git a/go/analysis/passes/errorsas/errorsas.go b/go/analysis/passes/errorsas/errorsas.go
index b8d29d0..b3df999 100644
--- a/go/analysis/passes/errorsas/errorsas.go
+++ b/go/analysis/passes/errorsas/errorsas.go
@@ -12,22 +12,20 @@
"go/types"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/go/types/typeutil"
- "golang.org/x/tools/internal/analysisinternal"
+ typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/typesinternal/typeindex"
)
const Doc = `report passing non-pointer or non-error values to errors.As
-The errorsas analysis reports calls to errors.As where the type
+The errorsas analyzer reports calls to errors.As where the type
of the second argument is not a pointer to a type implementing error.`
var Analyzer = &analysis.Analyzer{
Name: "errorsas",
Doc: Doc,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
- Requires: []*analysis.Analyzer{inspect.Analyzer},
+ Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
Run: run,
}
@@ -39,38 +37,31 @@
return nil, nil
}
- if !analysisinternal.Imports(pass.Pkg, "errors") {
- return nil, nil // doesn't directly import errors
- }
+ var (
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
+ )
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- nodeFilter := []ast.Node{
- (*ast.CallExpr)(nil),
- }
- inspect.Preorder(nodeFilter, func(n ast.Node) {
- call := n.(*ast.CallExpr)
- obj := typeutil.Callee(pass.TypesInfo, call)
- if !analysisinternal.IsFunctionNamed(obj, "errors", "As") {
- return
- }
+ for curCall := range index.Calls(index.Object("errors", "As")) {
+ call := curCall.Node().(*ast.CallExpr)
if len(call.Args) < 2 {
- return // not enough arguments, e.g. called with return values of another function
+ continue // spread call: errors.As(pair())
}
- if err := checkAsTarget(pass, call.Args[1]); err != nil {
+
+ // Check for incorrect arguments.
+ if err := checkAsTarget(info, call.Args[1]); err != nil {
pass.ReportRangef(call, "%v", err)
+ continue
}
- })
+ }
return nil, nil
}
-var errorType = types.Universe.Lookup("error").Type()
-
// checkAsTarget reports an error if the second argument to errors.As is invalid.
-func checkAsTarget(pass *analysis.Pass, e ast.Expr) error {
- t := pass.TypesInfo.Types[e].Type
- if it, ok := t.Underlying().(*types.Interface); ok && it.NumMethods() == 0 {
- // A target of interface{} is always allowed, since it often indicates
+func checkAsTarget(info *types.Info, e ast.Expr) error {
+ t := info.Types[e].Type
+ if types.Identical(t.Underlying(), anyType) {
+ // A target of any is always allowed, since it often indicates
// a value forwarded from another source.
return nil
}
@@ -78,12 +69,16 @@
if !ok {
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")
}
- if pt.Elem() == errorType {
+ if types.Identical(pt.Elem(), errorType) {
return errors.New("second argument to errors.As should not be *error")
}
- _, ok = pt.Elem().Underlying().(*types.Interface)
- if ok || types.Implements(pt.Elem(), errorType.Underlying().(*types.Interface)) {
- return nil
+ if !types.IsInterface(pt.Elem()) && !types.AssignableTo(pt.Elem(), errorType) {
+ 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")
}
- 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")
+ return nil
}
+
+var (
+ anyType = types.Universe.Lookup("any").Type()
+ errorType = types.Universe.Lookup("error").Type()
+)
diff --git a/go/analysis/passes/errorsas/errorsas_test.go b/go/analysis/passes/errorsas/errorsas_test.go
index 5414f9e..35e90f9 100644
--- a/go/analysis/passes/errorsas/errorsas_test.go
+++ b/go/analysis/passes/errorsas/errorsas_test.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build go1.13
-
package errorsas_test
import (
diff --git a/go/analysis/passes/errorsas/main.go b/go/analysis/passes/errorsas/main.go
new file mode 100644
index 0000000..ee0d3e1
--- /dev/null
+++ b/go/analysis/passes/errorsas/main.go
@@ -0,0 +1,16 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build ignore
+
+// The errorsas command applies the golang.org/x/tools/go/analysis/passes/errorsas
+// analysis to the specified packages of Go source code.
+package main
+
+import (
+ "golang.org/x/tools/go/analysis/passes/errorsas"
+ "golang.org/x/tools/go/analysis/singlechecker"
+)
+
+func main() { singlechecker.Main(errorsas.Analyzer) }
diff --git a/go/analysis/passes/modernize/doc.go b/go/analysis/passes/modernize/doc.go
index 43a883f..5d8f93c 100644
--- a/go/analysis/passes/modernize/doc.go
+++ b/go/analysis/passes/modernize/doc.go
@@ -89,6 +89,28 @@
`interface{}`, with the `any` alias, which was introduced in Go 1.18.
This is a purely stylistic change that makes code more readable.
+# Analyzer errorsastype
+
+errorsastype: replace errors.As with errors.AsType[T]
+
+This analyzer suggests fixes to simplify uses of [errors.As] of
+this form:
+
+ var myerr *MyErr
+ if errors.As(err, &myerr) {
+ handle(myerr)
+ }
+
+by using the less error-prone generic [errors.AsType] function,
+introduced in Go 1.26:
+
+ if myerr, ok := errors.AsType[*MyErr](err); ok {
+ handle(myerr)
+ }
+
+The fix is only offered if the var declaration has the form shown and
+there are no uses of myerr outside the if statement.
+
# Analyzer fmtappendf
fmtappendf: replace []byte(fmt.Sprintf) with fmt.Appendf
diff --git a/go/analysis/passes/modernize/errorsastype.go b/go/analysis/passes/modernize/errorsastype.go
new file mode 100644
index 0000000..ef8ce8b
--- /dev/null
+++ b/go/analysis/passes/modernize/errorsastype.go
@@ -0,0 +1,241 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package modernize
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "fmt"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/ast/edge"
+ "golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/analysisinternal/generated"
+ typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
+ "golang.org/x/tools/internal/goplsexport"
+ "golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/typesinternal/typeindex"
+)
+
+var errorsastypeAnalyzer = &analysis.Analyzer{
+ Name: "errorsastype",
+ Doc: analysisinternal.MustExtractDoc(doc, "errorsastype"),
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
+ Requires: []*analysis.Analyzer{generated.Analyzer, typeindexanalyzer.Analyzer},
+ Run: errorsastype,
+}
+
+func init() {
+ // Export to gopls until this is a published modernizer.
+ goplsexport.ErrorsAsTypeModernizer = errorsastypeAnalyzer
+}
+
+// errorsastype offers a fix to replace error.As with the newer
+// errors.AsType[T] following this pattern:
+//
+// var myerr *MyErr
+// if errors.As(err, &myerr) { ... }
+//
+// =>
+//
+// if myerr, ok := errors.AsType[*MyErr](err); ok { ... }
+//
+// (In principle several of these can then be chained using if/else,
+// but we don't attempt that.)
+//
+// We offer the fix only within an if statement, but not within a
+// switch case such as:
+//
+// var myerr *MyErr
+// switch {
+// case errors.As(err, &myerr):
+// }
+//
+// because the transformation in that case would be ungainly.
+//
+// Note that the cmd/vet suite includes the "errorsas" analyzer, which
+// detects actual mistakes in the use of errors.As. This logic does
+// not belong in errorsas because the problems it fixes are merely
+// stylistic.
+//
+// TODO(adonovan): support more cases:
+//
+// - Negative cases
+// var myerr E
+// if !errors.As(err, &myerr) { ... }
+// =>
+// myerr, ok := errors.AsType[E](err)
+// if !ok { ... }
+//
+// - if myerr := new(E); errors.As(err, myerr); { ... }
+//
+// - if errors.As(err, myerr) && othercond { ... }
+func errorsastype(pass *analysis.Pass) (any, error) {
+ skipGenerated(pass)
+
+ var (
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
+ )
+
+ for curCall := range index.Calls(index.Object("errors", "As")) {
+ call := curCall.Node().(*ast.CallExpr)
+ if len(call.Args) < 2 {
+ continue // spread call: errors.As(pair())
+ }
+
+ v, curDeclStmt := canUseErrorsAsType(info, index, curCall)
+ if v == nil {
+ continue
+ }
+
+ file := enclosingFile(curDeclStmt)
+ if !fileUses(info, file, "go1.26") {
+ continue // errors.AsType is too new
+ }
+
+ // Locate identifier "As" in errors.As.
+ var asIdent *ast.Ident
+ switch n := ast.Unparen(call.Fun).(type) {
+ case *ast.Ident:
+ asIdent = n // "errors" was dot-imported
+ case *ast.SelectorExpr:
+ asIdent = n.Sel
+ default:
+ panic("no Ident for errors.As")
+ }
+
+ // Format the type as valid Go syntax.
+ // TODO(adonovan): fix: FileQualifier needs to respect
+ // visibility at the current point, and either fail
+ // or edit the imports as needed.
+ // TODO(adonovan): fix: TypeString is not a sound way
+ // to print types as Go syntax as it does not respect
+ // symbol visibility, etc. We need something loosely
+ // integrated with FileQualifier that accumulates
+ // import edits, and may fail (e.g. for unexported
+ // type or field names from other packages).
+ // See https://go.dev/issues/75604.
+ qual := typesinternal.FileQualifier(file, pass.Pkg)
+ errtype := types.TypeString(v.Type(), qual)
+
+ // Choose a name for the "ok" variable.
+ okName := "ok"
+ if okVar := lookup(info, curCall, "ok"); okVar != nil {
+ // The name 'ok' is already declared, but
+ // don't choose a fresh name unless okVar
+ // is also used within the if-statement.
+ curIf := curCall.Parent()
+ for curUse := range index.Uses(okVar) {
+ if curIf.Contains(curUse) {
+ scope := info.Scopes[curIf.Node().(*ast.IfStmt)]
+ okName = analysisinternal.FreshName(scope, v.Pos(), "ok")
+ break
+ }
+ }
+ }
+
+ pass.Report(analysis.Diagnostic{
+ Pos: call.Fun.Pos(),
+ End: call.Fun.End(),
+ Message: fmt.Sprintf("errors.As can be simplified using AsType[%s]", errtype),
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: fmt.Sprintf("Replace errors.As with AsType[%s]", errtype),
+ TextEdits: append(
+ // delete "var myerr *MyErr"
+ analysisinternal.DeleteStmt(pass.Fset, curDeclStmt),
+ // if errors.As (err, &myerr) { ... }
+ // ------------- -------------- -------- ----
+ // if myerr, ok := errors.AsType[*MyErr](err ); ok { ... }
+ analysis.TextEdit{
+ // insert "myerr, ok := "
+ Pos: call.Pos(),
+ End: call.Pos(),
+ NewText: fmt.Appendf(nil, "%s, %s := ", v.Name(), okName),
+ },
+ analysis.TextEdit{
+ // replace As with AsType[T]
+ Pos: asIdent.Pos(),
+ End: asIdent.End(),
+ NewText: fmt.Appendf(nil, "AsType[%s]", errtype),
+ },
+ analysis.TextEdit{
+ // delete ", &myerr"
+ Pos: call.Args[0].End(),
+ End: call.Args[1].End(),
+ },
+ analysis.TextEdit{
+ // insert "; ok"
+ Pos: call.End(),
+ End: call.End(),
+ NewText: fmt.Appendf(nil, "; %s", okName),
+ },
+ ),
+ }},
+ })
+ }
+ return nil, nil
+}
+
+// canUseErrorsAsType reports whether curCall is a call to
+// errors.As beneath an if statement, preceded by a
+// declaration of the typed error var. The var must not be
+// used outside the if statement.
+func canUseErrorsAsType(info *types.Info, index *typeindex.Index, curCall inspector.Cursor) (_ *types.Var, _ inspector.Cursor) {
+ if ek, _ := curCall.ParentEdge(); ek != edge.IfStmt_Cond {
+ return // not beneath if statement
+ }
+ var (
+ curIfStmt = curCall.Parent()
+ ifStmt = curIfStmt.Node().(*ast.IfStmt)
+ )
+ if ifStmt.Init != nil {
+ return // if statement already has an init part
+ }
+ unary, ok := curCall.Node().(*ast.CallExpr).Args[1].(*ast.UnaryExpr)
+ if !ok || unary.Op != token.AND {
+ return // 2nd arg is not &var
+ }
+ id, ok := unary.X.(*ast.Ident)
+ if !ok {
+ return // not a simple ident (local var)
+ }
+ v := info.Uses[id].(*types.Var)
+ curDef, ok := index.Def(v)
+ if !ok {
+ return // var is not local (e.g. dot-imported)
+ }
+ // Have: if errors.As(err, &v) { ... }
+
+ // Reject if v is used outside (before or after) the
+ // IfStmt, since that will become its new scope.
+ for curUse := range index.Uses(v) {
+ if !curIfStmt.Contains(curUse) {
+ return // v used before/after if statement
+ }
+ }
+ if ek, _ := curDef.ParentEdge(); ek != edge.ValueSpec_Names {
+ return // v not declared by "var v T"
+ }
+ var (
+ curSpec = curDef.Parent() // ValueSpec
+ curDecl = curSpec.Parent() // GenDecl
+ spec = curSpec.Node().(*ast.ValueSpec)
+ )
+ if len(spec.Names) != 1 || len(spec.Values) != 0 ||
+ len(curDecl.Node().(*ast.GenDecl).Specs) != 1 {
+ return // not a simple "var v T" decl
+ }
+
+ // Have:
+ // var v *MyErr
+ // ...
+ // if errors.As(err, &v) { ... }
+ // with no uses of v outside the IfStmt.
+ return v, curDecl.Parent() // DeclStmt
+}
diff --git a/go/analysis/passes/modernize/modernize_test.go b/go/analysis/passes/modernize/modernize_test.go
index c71d38c..0ca72ca 100644
--- a/go/analysis/passes/modernize/modernize_test.go
+++ b/go/analysis/passes/modernize/modernize_test.go
@@ -24,6 +24,10 @@
RunWithSuggestedFixes(t, TestData(), modernize.AnyAnalyzer, "any")
}
+func TestErrorsAsType(t *testing.T) {
+ RunWithSuggestedFixes(t, TestData(), goplsexport.ErrorsAsTypeModernizer, "errorsastype/...")
+}
+
func TestFmtAppendf(t *testing.T) {
RunWithSuggestedFixes(t, TestData(), modernize.FmtAppendfAnalyzer, "fmtappendf")
}
diff --git a/go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go b/go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go
new file mode 100644
index 0000000..272aab9
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go
@@ -0,0 +1,13 @@
+package errorsastype
+
+import (
+ . "errors"
+ "os"
+)
+
+func _(err error) {
+ var patherr *os.PathError
+ if As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+}
diff --git a/go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go.golden b/go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go.golden
new file mode 100644
index 0000000..8e92433
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/errorsastype/dotimport/a.go.golden
@@ -0,0 +1,12 @@
+package errorsastype
+
+import (
+ . "errors"
+ "os"
+)
+
+func _(err error) {
+ if patherr, ok := AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+}
diff --git a/go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go b/go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go
new file mode 100644
index 0000000..c3cf830
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go
@@ -0,0 +1,52 @@
+package errorsastype
+
+import (
+ "errors"
+ "os"
+)
+
+func _(err error) {
+ {
+ var patherr *os.PathError
+ if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+ }
+ {
+ var patherr *os.PathError
+ print("not a use of patherr")
+ if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+ print("also not a use of patherr")
+ }
+ {
+ var patherr *os.PathError
+ print(patherr)
+ if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
+ print(patherr)
+ }
+ }
+ {
+ var patherr *os.PathError
+ if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
+ print(patherr)
+ }
+ print(patherr)
+ }
+
+ // Test of 'ok' var shadowing/freshness.
+ const ok = 1
+ {
+ var patherr *os.PathError
+ if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+ }
+ {
+ var patherr *os.PathError
+ if errors.As(err, &patherr) { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr, ok)
+ }
+ }
+}
diff --git a/go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go.golden b/go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go.golden
new file mode 100644
index 0000000..3e8a06b
--- /dev/null
+++ b/go/analysis/passes/modernize/testdata/src/errorsastype/errorsastype.go.golden
@@ -0,0 +1,48 @@
+package errorsastype
+
+import (
+ "errors"
+ "os"
+)
+
+func _(err error) {
+ {
+ if patherr, ok := errors.AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+ }
+ {
+ print("not a use of patherr")
+ if patherr, ok := errors.AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+ print("also not a use of patherr")
+ }
+ {
+ var patherr *os.PathError
+ print(patherr)
+ if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
+ print(patherr)
+ }
+ }
+ {
+ var patherr *os.PathError
+ if errors.As(err, &patherr) { // nope: patherr is used outside scope of if
+ print(patherr)
+ }
+ print(patherr)
+ }
+
+ // Test of 'ok' var shadowing/freshness.
+ const ok = 1
+ {
+ if patherr, ok := errors.AsType[*os.PathError](err); ok { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr)
+ }
+ }
+ {
+ if patherr, ok0 := errors.AsType[*os.PathError](err); ok0 { // want `errors.As can be simplified using AsType\[\*os.PathError\]`
+ print(patherr, ok)
+ }
+ }
+}
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index b63f2d7..39ca9bf 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -3092,13 +3092,36 @@
<a id='errorsas'></a>
## `errorsas`: report passing non-pointer or non-error values to errors.As
-The errorsas analysis reports calls to errors.As where the type of the second argument is not a pointer to a type implementing error.
+The errorsas analyzer reports calls to errors.As where the type of the second argument is not a pointer to a type implementing error.
Default: on.
Package documentation: [errorsas](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas)
+<a id='errorsastype'></a>
+## `errorsastype`: replace errors.As with errors.AsType[T]
+
+This analyzer suggests fixes to simplify uses of [errors.As](/errors#As) of this form:
+
+ var myerr *MyErr
+ if errors.As(err, &myerr) {
+ handle(myerr)
+ }
+
+by using the less error-prone generic [errors.AsType](/errors#AsType) function, introduced in Go 1.26:
+
+ if myerr, ok := errors.AsType[*MyErr](err); ok {
+ handle(myerr)
+ }
+
+The fix is only offered if the var declaration has the form shown and there are no uses of myerr outside the if statement.
+
+
+Default: on.
+
+Package documentation: [errorsastype](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype)
+
<a id='fillreturns'></a>
## `fillreturns`: suggest fixes for errors due to an incorrect number of return values
diff --git a/gopls/internal/doc/api.json b/gopls/internal/doc/api.json
index 93ed9ff..4627fb3 100644
--- a/gopls/internal/doc/api.json
+++ b/gopls/internal/doc/api.json
@@ -1450,7 +1450,13 @@
},
{
"Name": "\"errorsas\"",
- "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+ "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analyzer reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+ "Default": "true",
+ "Status": ""
+ },
+ {
+ "Name": "\"errorsastype\"",
+ "Doc": "replace errors.As with errors.AsType[T]\n\nThis analyzer suggests fixes to simplify uses of [errors.As] of\nthis form:\n\n\tvar myerr *MyErr\n\tif errors.As(err, \u0026myerr) {\n\t\thandle(myerr)\n\t}\n\nby using the less error-prone generic [errors.AsType] function,\nintroduced in Go 1.26:\n\n\tif myerr, ok := errors.AsType[*MyErr](err); ok {\n\t\thandle(myerr)\n\t}\n\nThe fix is only offered if the var declaration has the form shown and\nthere are no uses of myerr outside the if statement.",
"Default": "true",
"Status": ""
},
@@ -3330,11 +3336,17 @@
},
{
"Name": "errorsas",
- "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
+ "Doc": "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analyzer reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
"URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/errorsas",
"Default": true
},
{
+ "Name": "errorsastype",
+ "Doc": "replace errors.As with errors.AsType[T]\n\nThis analyzer suggests fixes to simplify uses of [errors.As] of\nthis form:\n\n\tvar myerr *MyErr\n\tif errors.As(err, \u0026myerr) {\n\t\thandle(myerr)\n\t}\n\nby using the less error-prone generic [errors.AsType] function,\nintroduced in Go 1.26:\n\n\tif myerr, ok := errors.AsType[*MyErr](err); ok {\n\t\thandle(myerr)\n\t}\n\nThe fix is only offered if the var declaration has the form shown and\nthere are no uses of myerr outside the if statement.",
+ "URL": "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
+ "Default": true
+ },
+ {
"Name": "fillreturns",
"Doc": "suggest fixes for errors due to an incorrect number of return values\n\nThis checker provides suggested fixes for type errors of the\ntype \"wrong number of return values (want %d, got %d)\". For example:\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn\n\t}\n\nwill turn into\n\n\tfunc m() (int, string, *bool, error) {\n\t\treturn 0, \"\", nil, nil\n\t}\n\nThis functionality is similar to https://github.com/sqs/goreturns.",
"URL": "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/fillreturns",
diff --git a/gopls/internal/settings/analysis.go b/gopls/internal/settings/analysis.go
index 9ea6a55..97fae1f 100644
--- a/gopls/internal/settings/analysis.go
+++ b/gopls/internal/settings/analysis.go
@@ -251,6 +251,7 @@
{analyzer: modernize.AnyAnalyzer, severity: protocol.SeverityHint},
{analyzer: modernize.AppendClippedAnalyzer, severity: protocol.SeverityHint, nonDefault: true}, // not nil-preserving
{analyzer: modernize.BLoopAnalyzer, severity: protocol.SeverityHint},
+ {analyzer: goplsexport.ErrorsAsTypeModernizer, severity: protocol.SeverityHint},
{analyzer: modernize.FmtAppendfAnalyzer, severity: protocol.SeverityHint},
{analyzer: modernize.ForVarAnalyzer, severity: protocol.SeverityHint},
{analyzer: goplsexport.StdIteratorsModernizer, severity: protocol.SeverityHint},
diff --git a/internal/goplsexport/export.go b/internal/goplsexport/export.go
index 01e0288..d7c2b9f 100644
--- a/internal/goplsexport/export.go
+++ b/internal/goplsexport/export.go
@@ -9,5 +9,6 @@
import "golang.org/x/tools/go/analysis"
var (
+ ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyer
)