Skip to content

Commit

Permalink
support IndexListExpr receiver
Browse files Browse the repository at this point in the history
  • Loading branch information
Antonboom committed Aug 14, 2023
1 parent 859c94c commit 2f60bda
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 11 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ func NewDecodeError() error {

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

- Nested error types are not supported

```go
type timeoutErr struct { // no warning from the linter :(
error
}

type DeadlineErr struct { // no warning from the linter :(
timeoutErr
}
```

- Not supported sentinel errors that were created by an external type or func (except `errors`/`fmt`) and that do not
have an explicit type `error`:

Expand Down
17 changes: 9 additions & 8 deletions pkg/analyzer/analyzer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package analyzer

import (
"fmt"
"go/ast"
"go/token"
"strconv"
Expand All @@ -25,16 +26,16 @@ func New() *analysis.Analyzer {
type stringSet = map[string]struct{}

var (
imports = []ast.Node{(*ast.ImportSpec)(nil)}
types = []ast.Node{(*ast.TypeSpec)(nil)}
funcs = []ast.Node{(*ast.FuncDecl)(nil)}
importNodes = []ast.Node{(*ast.ImportSpec)(nil)}
typeNodes = []ast.Node{(*ast.TypeSpec)(nil)}
funcNodes = []ast.Node{(*ast.FuncDecl)(nil)}
)

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

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

allTypes := stringSet{}
typesSpecs := map[string]*ast.TypeSpec{}
insp.Preorder(types, func(node ast.Node) {
insp.Preorder(typeNodes, func(node ast.Node) {
t := node.(*ast.TypeSpec)
allTypes[t.Name.Name] = struct{}{}
typesSpecs[t.Name.Name] = t
})

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

tSpec, ok := typesSpecs[t]
if !ok {
panic("no specification for type " + t)
panic(fmt.Sprintf("no specification for type %q", t))
}

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

errorFuncs := stringSet{}
insp.Preorder(funcs, func(node ast.Node) {
insp.Preorder(funcNodes, func(node ast.Node) {
f := node.(*ast.FuncDecl)
if isFuncReturningErr(f.Type, allTypes, errorTypes) {
errorFuncs[f.Name.Name] = struct{}{}
Expand Down
12 changes: 9 additions & 3 deletions pkg/analyzer/facts.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package analyzer

import (
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"unicode"
)
Expand Down Expand Up @@ -34,15 +36,19 @@ func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) {
if i, ok := v.X.(*ast.Ident); ok {
return i.Name
}
case *ast.IndexListExpr:
if i, ok := v.X.(*ast.Ident); ok {
return i.Name
}
}
return ""
panic(fmt.Errorf("unsupported Error() receiver type %q", types.ExprString(e)))
}

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

case *ast.StarExpr: // *SomeError, *SomeError[T]
case *ast.StarExpr: // *SomeError, *SomeError[T], *SomeError[T1, T2, ...]
receiverType = unwrapIdentName(v.X)
}

Expand Down
46 changes: 46 additions & 0 deletions pkg/analyzer/testdata/src/unusual/generics/issue24.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package generics

import (
"context"
"fmt"
"net/http"
"reflect"
)

type (
Req any
Resp any
)

type timeoutErr[REQ Req, RESP Resp] struct { // want "the type name `timeoutErr` should conform to the `xxxError` format"
err error
sending bool
}

func (e *timeoutErr[REQ, RESP]) Error() string {
var req REQ
var resp RESP

direction := "sending"
if !e.sending {
direction = "receiving"
}

return fmt.Sprintf("deferred call %T->%T timeout %s: %s",
reflect.TypeOf(req), reflect.TypeOf(resp), direction, e.err.Error())
}

func (e *timeoutErr[REQ, RESP]) Unwrap() error {
return e.err
}

type TimeoutError[REQ Req, RESP Resp] struct{} //
func (TimeoutError[REQ, RESP]) Error() string { return "timeouted" }

type ValErr[A, B, C, D, E, F any] struct{} // want "the type name `ValErr` should conform to the `XxxError` format"
func (ValErr[A, B, C, D, E, F]) Error() string { return "boom!" }

var (
ErrTimeout error = &timeoutErr[*http.Request, *http.Response]{err: context.DeadlineExceeded, sending: false}
tErr error = &timeoutErr[string, string]{err: context.DeadlineExceeded, sending: true} // want "the variable name `tErr` should conform to the `errXxx` format"
)

0 comments on commit 2f60bda

Please sign in to comment.