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
 )