gopls/internal/analysis/ptrtoerror: support type assert and instances

The previous CL missed two important cases where syntax implies
an implements relation between E (or *E) and the error interface:

1. Type assertions and type switches
      err.(E)
      switch err.(type) { case E: }

2. Instantiatio of type parameters
     errors.AsType[E](err)

This change augments the 'conversions' iterator to return them too,
and adds tests for the missing cases.

Updates golang/go#61342
Updates golang/go#70638
Updates golang/go#80159

Change-Id: I7612aa0a9e38079ef4dec1b8eabff458e74ea81b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/801000
Reviewed-by: Madeline Kalil <mkalil@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/internal/analysis/ptrtoerror/conv.go b/gopls/internal/analysis/ptrtoerror/conv.go
index b044c7e..c3b1b9b 100644
--- a/gopls/internal/analysis/ptrtoerror/conv.go
+++ b/gopls/internal/analysis/ptrtoerror/conv.go
@@ -35,6 +35,8 @@
 			(*ast.ReturnStmt)(nil),
 			(*ast.CompositeLit)(nil),
 			(*ast.SendStmt)(nil),
+			(*ast.TypeAssertExpr)(nil),
+			(*ast.TypeSwitchStmt)(nil),
 		}
 
 		for c := range root.Preorder(nodeFilter...) {
@@ -228,6 +230,48 @@
 						return
 					}
 				}
+
+			case *ast.TypeAssertExpr:
+				// I(x).(E) acts like a pseudoconversion from E to E.
+				if n.Type != nil && // (not beneath type switch)
+					!yield(conversion{info.TypeOf(n.X), info.TypeOf(n.Type), n.Type}) {
+					return
+				}
+
+			case *ast.TypeSwitchStmt:
+				// switch I(x).(type) { case E: } depends on E being assignable to I.
+				// Report the (pseudo)conversion of type (not term) E to I.
+				var assert *ast.TypeAssertExpr
+				switch assign := n.Assign.(type) {
+				case *ast.ExprStmt:
+					assert = assign.X.(*ast.TypeAssertExpr)
+				case *ast.AssignStmt:
+					assert = assign.Rhs[0].(*ast.TypeAssertExpr)
+				}
+				for _, cc := range n.Body.List {
+					for _, typ := range cc.(*ast.CaseClause).List {
+						if !yield(conversion{info.TypeOf(assert.X), info.TypeOf(typ), typ}) {
+							return
+						}
+					}
+				}
+			}
+		}
+
+		// Yield conversions from type parameter instantiations
+		// e.g. errors.AsType[*E](err)
+		for id, inst := range info.Instances {
+			t := info.ObjectOf(id).Type()
+			type hasTypeParams interface{ TypeParams() *types.TypeParamList } // = Signature, Named, Alias
+			if t, ok := t.(hasTypeParams); ok {
+				tparams := t.TypeParams()
+				for i := 0; i < tparams.Len(); i++ {
+					tparam := tparams.At(i)
+					targ := inst.TypeArgs.At(i)
+					if !yield(conversion{tparam.Constraint(), targ, id}) {
+						return
+					}
+				}
 			}
 		}
 	}
diff --git a/gopls/internal/analysis/ptrtoerror/ptrtoerror_test.go b/gopls/internal/analysis/ptrtoerror/ptrtoerror_test.go
index 9597b14..a5dd9b5 100644
--- a/gopls/internal/analysis/ptrtoerror/ptrtoerror_test.go
+++ b/gopls/internal/analysis/ptrtoerror/ptrtoerror_test.go
@@ -12,5 +12,5 @@
 )
 
 func Test(t *testing.T) {
-	analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), ptrtoerror.Analyzer, "a", "b")
+	analysistest.RunWithSuggestedFixes(t, analysistest.TestData(), ptrtoerror.Analyzer, "a", "b", "c", "d")
 }
diff --git a/gopls/internal/analysis/ptrtoerror/testdata/src/c/c.go b/gopls/internal/analysis/ptrtoerror/testdata/src/c/c.go
new file mode 100644
index 0000000..dcfae79
--- /dev/null
+++ b/gopls/internal/analysis/ptrtoerror/testdata/src/c/c.go
@@ -0,0 +1,63 @@
+// Copyright 2026 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 c
+
+import "errors"
+
+type SwitchErr struct{}
+
+func (SwitchErr) Error() string { return "" }
+
+func testSwitch(err error) {
+	switch err.(type) {
+	case SwitchErr: // want "SwitchErr is converted to error both as a value and as a pointer"
+	}
+
+	var p *SwitchErr
+	var _ error = p // want "SwitchErr is converted to error both as a value and as a pointer"
+}
+
+type AsTypeErr struct{}
+
+func (AsTypeErr) Error() string { return "" }
+
+func testAsType(err error) {
+	errors.AsType[AsTypeErr](err) // want "AsTypeErr is converted to error both as a value and as a pointer"
+
+	var p *AsTypeErr
+	var _ error = p // want "AsTypeErr is converted to error both as a value and as a pointer"
+}
+
+type ConsistentSwitchErr struct{} // want ConsistentSwitchErr:`E`
+
+func (ConsistentSwitchErr) Error() string { return "" }
+
+func _(err error) {
+	switch err.(type) {
+	case ConsistentSwitchErr: // ok, value conversion
+	}
+}
+
+type ConsistentAsTypeErr struct{} // want ConsistentAsTypeErr:`E`
+
+func (ConsistentAsTypeErr) Error() string { return "" }
+
+func _(err error) {
+	errors.AsType[ConsistentAsTypeErr](err) // ok, value conversion
+}
+
+type ConsistentSwitchPtrErr struct{ error } // want ConsistentSwitchPtrErr:`\*E`
+
+func _(err error) {
+	switch err.(type) {
+	case *ConsistentSwitchPtrErr: // ok, pointer conversion
+	}
+}
+
+type ConsistentAsTypePtrErr struct{ error } // want ConsistentAsTypePtrErr:`\*E`
+
+func _(err error) {
+	errors.AsType[*ConsistentAsTypePtrErr](err) // ok, pointer conversion
+}
diff --git a/gopls/internal/analysis/ptrtoerror/testdata/src/c/c.go.golden b/gopls/internal/analysis/ptrtoerror/testdata/src/c/c.go.golden
new file mode 100644
index 0000000..dcfae79
--- /dev/null
+++ b/gopls/internal/analysis/ptrtoerror/testdata/src/c/c.go.golden
@@ -0,0 +1,63 @@
+// Copyright 2026 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 c
+
+import "errors"
+
+type SwitchErr struct{}
+
+func (SwitchErr) Error() string { return "" }
+
+func testSwitch(err error) {
+	switch err.(type) {
+	case SwitchErr: // want "SwitchErr is converted to error both as a value and as a pointer"
+	}
+
+	var p *SwitchErr
+	var _ error = p // want "SwitchErr is converted to error both as a value and as a pointer"
+}
+
+type AsTypeErr struct{}
+
+func (AsTypeErr) Error() string { return "" }
+
+func testAsType(err error) {
+	errors.AsType[AsTypeErr](err) // want "AsTypeErr is converted to error both as a value and as a pointer"
+
+	var p *AsTypeErr
+	var _ error = p // want "AsTypeErr is converted to error both as a value and as a pointer"
+}
+
+type ConsistentSwitchErr struct{} // want ConsistentSwitchErr:`E`
+
+func (ConsistentSwitchErr) Error() string { return "" }
+
+func _(err error) {
+	switch err.(type) {
+	case ConsistentSwitchErr: // ok, value conversion
+	}
+}
+
+type ConsistentAsTypeErr struct{} // want ConsistentAsTypeErr:`E`
+
+func (ConsistentAsTypeErr) Error() string { return "" }
+
+func _(err error) {
+	errors.AsType[ConsistentAsTypeErr](err) // ok, value conversion
+}
+
+type ConsistentSwitchPtrErr struct{ error } // want ConsistentSwitchPtrErr:`\*E`
+
+func _(err error) {
+	switch err.(type) {
+	case *ConsistentSwitchPtrErr: // ok, pointer conversion
+	}
+}
+
+type ConsistentAsTypePtrErr struct{ error } // want ConsistentAsTypePtrErr:`\*E`
+
+func _(err error) {
+	errors.AsType[*ConsistentAsTypePtrErr](err) // ok, pointer conversion
+}
diff --git a/gopls/internal/analysis/ptrtoerror/testdata/src/d/d.go b/gopls/internal/analysis/ptrtoerror/testdata/src/d/d.go
new file mode 100644
index 0000000..a9b4eda
--- /dev/null
+++ b/gopls/internal/analysis/ptrtoerror/testdata/src/d/d.go
@@ -0,0 +1,21 @@
+// Copyright 2026 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 d
+
+import "c"
+
+func testConsistent() {
+	var p *c.ConsistentSwitchErr
+	var _ error = p // want `conversion of \*c.ConsistentSwitchErr to error, but package "c" uses ConsistentSwitchErr \(sans pointer\) as an error`
+
+	var p2 *c.ConsistentAsTypeErr
+	var _ error = p2 // want `conversion of \*c.ConsistentAsTypeErr to error, but package "c" uses ConsistentAsTypeErr \(sans pointer\) as an error`
+
+	var v c.ConsistentSwitchPtrErr
+	var _ error = v // want `conversion of c.ConsistentSwitchPtrErr to error, but package "c" uses pointer \*ConsistentSwitchPtrErr as an error`
+
+	var v2 c.ConsistentAsTypePtrErr
+	var _ error = v2 // want `conversion of c.ConsistentAsTypePtrErr to error, but package "c" uses pointer \*ConsistentAsTypePtrErr as an error`
+}
diff --git a/gopls/internal/analysis/ptrtoerror/testdata/src/errors/errors.go b/gopls/internal/analysis/ptrtoerror/testdata/src/errors/errors.go
new file mode 100644
index 0000000..ccc0376
--- /dev/null
+++ b/gopls/internal/analysis/ptrtoerror/testdata/src/errors/errors.go
@@ -0,0 +1,10 @@
+// Copyright 2026 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 errors
+
+func AsType[T error](err error) (T, bool) {
+	var zero T
+	return zero, false
+}