Skip to content

Commit

Permalink
Merge pull request #25 from Antonboom/fix-no-spec-for-type
Browse files Browse the repository at this point in the history
Go 1.21 & Support `ast.IndexListExpr` receiver
  • Loading branch information
Antonboom committed Aug 14, 2023
2 parents c7f9a10 + fd1bffc commit ed16cfd
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ updates:
- package-ecosystem: gomod
directory: "/"
schedule:
interval: "daily"
interval: "monthly"
11 changes: 6 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: rlespinasse/github-slug-action@v4
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: ^1.20
go-version: ^1.21
- run: cd /tmp && go install github.com/Antonboom/errname@${{ env.GITHUB_REF_NAME }} && errname -h

lint:
Expand All @@ -22,14 +22,15 @@ jobs:
- uses: actions/checkout@v3
- uses: golangci/golangci-lint-action@v3
with:
version: latest
version: v1.54.0
args: --timeout=5m

tests:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: ^1.20
go-version: ^1.21
- uses: actions/checkout@v3
- run: go test -coverprofile=coverage.out ./...
- uses: shogo82148/actions-goveralls@v1
Expand Down
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ linters:
- asciicheck
- bidichk
- bodyclose
- depguard
- dogsled
- dupl
- durationcheck
Expand Down Expand Up @@ -53,6 +52,7 @@ linters:
- lll
- makezero
- misspell
- mirror
- nakedret
- nilerr
- nilnil
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ var ErrInvalidURL = errors.New("invalid url") // or errInvalidURL

More examples in [tests](https://github.com/Antonboom/errname/blob/master/pkg/analyzer/facts_test.go).

## Assumptions
## Assumptions (open to contributors 🙏🏻)

<details>
<summary>Click to expand</summary>
Expand Down 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
4 changes: 2 additions & 2 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ tasks:

tools:install:
- echo "Install local tools..."
- (which gci > /dev/null) || GO111MODULE=off go install github.com/daixiang0/gci@latest
- (which gofumpt > /dev/null) || GO111MODULE=off go install mvdan.cc/gofumpt@latest
- (which gci > /dev/null) || go install github.com/daixiang0/gci@latest
- (which gofumpt > /dev/null) || go install mvdan.cc/gofumpt@latest

tidy:
cmds:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/Antonboom/errname

go 1.20
go 1.21

require (
golang.org/x/sys v0.11.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
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 ed16cfd

Please sign in to comment.