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
+}