Skip to content

Commit 2f60bda

Browse files
committed
support IndexListExpr receiver
1 parent 859c94c commit 2f60bda

File tree

4 files changed

+76
-11
lines changed

4 files changed

+76
-11
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ func NewDecodeError() error {
112112

113113
- Package aliases are not supported if the source package and its directory differ in name.
114114

115+
- Nested error types are not supported
116+
117+
```go
118+
type timeoutErr struct { // no warning from the linter :(
119+
error
120+
}
121+
122+
type DeadlineErr struct { // no warning from the linter :(
123+
timeoutErr
124+
}
125+
```
126+
115127
- Not supported sentinel errors that were created by an external type or func (except `errors`/`fmt`) and that do not
116128
have an explicit type `error`:
117129

pkg/analyzer/analyzer.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package analyzer
22

33
import (
4+
"fmt"
45
"go/ast"
56
"go/token"
67
"strconv"
@@ -25,16 +26,16 @@ func New() *analysis.Analyzer {
2526
type stringSet = map[string]struct{}
2627

2728
var (
28-
imports = []ast.Node{(*ast.ImportSpec)(nil)}
29-
types = []ast.Node{(*ast.TypeSpec)(nil)}
30-
funcs = []ast.Node{(*ast.FuncDecl)(nil)}
29+
importNodes = []ast.Node{(*ast.ImportSpec)(nil)}
30+
typeNodes = []ast.Node{(*ast.TypeSpec)(nil)}
31+
funcNodes = []ast.Node{(*ast.FuncDecl)(nil)}
3132
)
3233

3334
func run(pass *analysis.Pass) (interface{}, error) {
3435
insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
3536

3637
pkgAliases := map[string]string{}
37-
insp.Preorder(imports, func(node ast.Node) {
38+
insp.Preorder(importNodes, func(node ast.Node) {
3839
i := node.(*ast.ImportSpec)
3940
if n := i.Name; n != nil && i.Path != nil {
4041
if path, err := strconv.Unquote(i.Path.Value); err == nil {
@@ -45,14 +46,14 @@ func run(pass *analysis.Pass) (interface{}, error) {
4546

4647
allTypes := stringSet{}
4748
typesSpecs := map[string]*ast.TypeSpec{}
48-
insp.Preorder(types, func(node ast.Node) {
49+
insp.Preorder(typeNodes, func(node ast.Node) {
4950
t := node.(*ast.TypeSpec)
5051
allTypes[t.Name.Name] = struct{}{}
5152
typesSpecs[t.Name.Name] = t
5253
})
5354

5455
errorTypes := stringSet{}
55-
insp.Preorder(funcs, func(node ast.Node) {
56+
insp.Preorder(funcNodes, func(node ast.Node) {
5657
f := node.(*ast.FuncDecl)
5758
t, ok := isMethodError(f)
5859
if !ok {
@@ -62,7 +63,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
6263

6364
tSpec, ok := typesSpecs[t]
6465
if !ok {
65-
panic("no specification for type " + t)
66+
panic(fmt.Sprintf("no specification for type %q", t))
6667
}
6768

6869
if _, ok := tSpec.Type.(*ast.ArrayType); ok {
@@ -75,7 +76,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
7576
})
7677

7778
errorFuncs := stringSet{}
78-
insp.Preorder(funcs, func(node ast.Node) {
79+
insp.Preorder(funcNodes, func(node ast.Node) {
7980
f := node.(*ast.FuncDecl)
8081
if isFuncReturningErr(f.Type, allTypes, errorTypes) {
8182
errorFuncs[f.Name.Name] = struct{}{}

pkg/analyzer/facts.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package analyzer
22

33
import (
4+
"fmt"
45
"go/ast"
56
"go/token"
7+
"go/types"
68
"strings"
79
"unicode"
810
)
@@ -34,15 +36,19 @@ func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) {
3436
if i, ok := v.X.(*ast.Ident); ok {
3537
return i.Name
3638
}
39+
case *ast.IndexListExpr:
40+
if i, ok := v.X.(*ast.Ident); ok {
41+
return i.Name
42+
}
3743
}
38-
return ""
44+
panic(fmt.Errorf("unsupported Error() receiver type %q", types.ExprString(e)))
3945
}
4046

4147
switch rt := f.Recv.List[0].Type; v := rt.(type) {
42-
case *ast.Ident, *ast.IndexExpr: // SomeError, SomeError[T]
48+
case *ast.Ident, *ast.IndexExpr, *ast.IndexListExpr: // SomeError, SomeError[T], SomeError[T1, T2, ...]
4349
receiverType = unwrapIdentName(rt)
4450

45-
case *ast.StarExpr: // *SomeError, *SomeError[T]
51+
case *ast.StarExpr: // *SomeError, *SomeError[T], *SomeError[T1, T2, ...]
4652
receiverType = unwrapIdentName(v.X)
4753
}
4854

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package generics
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"reflect"
8+
)
9+
10+
type (
11+
Req any
12+
Resp any
13+
)
14+
15+
type timeoutErr[REQ Req, RESP Resp] struct { // want "the type name `timeoutErr` should conform to the `xxxError` format"
16+
err error
17+
sending bool
18+
}
19+
20+
func (e *timeoutErr[REQ, RESP]) Error() string {
21+
var req REQ
22+
var resp RESP
23+
24+
direction := "sending"
25+
if !e.sending {
26+
direction = "receiving"
27+
}
28+
29+
return fmt.Sprintf("deferred call %T->%T timeout %s: %s",
30+
reflect.TypeOf(req), reflect.TypeOf(resp), direction, e.err.Error())
31+
}
32+
33+
func (e *timeoutErr[REQ, RESP]) Unwrap() error {
34+
return e.err
35+
}
36+
37+
type TimeoutError[REQ Req, RESP Resp] struct{} //
38+
func (TimeoutError[REQ, RESP]) Error() string { return "timeouted" }
39+
40+
type ValErr[A, B, C, D, E, F any] struct{} // want "the type name `ValErr` should conform to the `XxxError` format"
41+
func (ValErr[A, B, C, D, E, F]) Error() string { return "boom!" }
42+
43+
var (
44+
ErrTimeout error = &timeoutErr[*http.Request, *http.Response]{err: context.DeadlineExceeded, sending: false}
45+
tErr error = &timeoutErr[string, string]{err: context.DeadlineExceeded, sending: true} // want "the variable name `tErr` should conform to the `errXxx` format"
46+
)

0 commit comments

Comments
 (0)