Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generics #11

Merged
merged 1 commit into from May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .golangci.yml
Expand Up @@ -15,13 +15,17 @@ linters:
disable-all: true
enable:
- asciicheck
- bidichk
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- durationcheck
- errcheck
- errchkjson
- errname
- execinquery
- exhaustive
- exportloopref
- forbidigo
Expand Down Expand Up @@ -51,9 +55,11 @@ linters:
- misspell
- nakedret
- nilerr
- nilnil
- nestif
- noctx
- nolintlint
- nosprintfhostport
- prealloc
- predeclared
- revive
Expand All @@ -63,6 +69,7 @@ linters:
- structcheck
- stylecheck
- tagliatelle
- tenv
- thelper
- typecheck
- unconvert
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -3,11 +3,11 @@ module github.com/Antonboom/errname
go 1.18

require (
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/tools v0.1.10
)

require (
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
)
8 changes: 4 additions & 4 deletions go.sum
@@ -1,8 +1,8 @@
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
2 changes: 1 addition & 1 deletion pkg/analyzer/analyzer.go
Expand Up @@ -98,7 +98,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
ast.Inspect(f, inspectPkgLevelVarsOnly)
}

return nil, nil
return nil, nil //nolint:nilnil
}

func reportAboutErrorType(pass *analysis.Pass, typePos token.Pos, typeName string, isArrayType bool) {
Expand Down
1 change: 1 addition & 0 deletions pkg/analyzer/analyzer_test.go
Expand Up @@ -10,6 +10,7 @@ func TestErrName(t *testing.T) {
pkgs := []string{
"regular",
"unusual/errortype",
"unusual/generics",
"unusual/newfunc",
}
analysistest.Run(t, analysistest.TestData(), New(), pkgs...)
Expand Down
53 changes: 41 additions & 12 deletions pkg/analyzer/facts.go
Expand Up @@ -8,10 +8,10 @@ import (
)

func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) {
if f.Recv == nil {
if f.Recv == nil || len(f.Recv.List) != 1 {
return "", false
}
if f.Name.Name != "Error" {
if f.Name == nil || f.Name.Name != "Error" {
return "", false
}

Expand All @@ -26,13 +26,24 @@ func isMethodError(f *ast.FuncDecl) (typeName string, ok bool) {

var receiverType string

switch rt := f.Recv.List[0].Type.(type) {
case *ast.Ident:
receiverType = rt.Name
case *ast.StarExpr:
if i, ok := rt.X.(*ast.Ident); ok {
receiverType = i.Name
unwrapIdentName := func(e ast.Expr) string {
switch v := e.(type) {
case *ast.Ident:
return v.Name
case *ast.IndexExpr:
if i, ok := v.X.(*ast.Ident); ok {
return i.Name
}
}
return ""
}

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

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

return receiverType, returnType.Name == "string"
Expand Down Expand Up @@ -100,7 +111,7 @@ var knownErrConstructors = stringSet{
"errors.NewAssertionErrorWithWrappedErrf": {},
}

func isSentinelError( //nolint:gocognit
func isSentinelError( //nolint:gocognit,gocyclo
v *ast.ValueSpec,
pkgAliases map[string]string,
allTypes, errorTypes, errorFuncs stringSet,
Expand Down Expand Up @@ -151,6 +162,7 @@ func isSentinelError( //nolint:gocognit
// var ErrEndOfFile = newErrEndOfFile()
// var ErrEndOfFile = new(EndOfFileError)
// const ErrEndOfFile = constError("end of file")
// var statusCodeError = new(SomePtrError[string])
case *ast.Ident:
if isErrorType(fun.Name, allTypes, errorTypes) {
return varName, true
Expand All @@ -161,8 +173,13 @@ func isSentinelError( //nolint:gocognit
}

if fun.Name == "new" && len(vv.Args) == 1 {
if i, ok := vv.Args[0].(*ast.Ident); ok {
switch i := vv.Args[0].(type) {
case *ast.Ident:
return varName, isErrorType(i.Name, allTypes, errorTypes)
case *ast.IndexExpr:
if ii, ok := i.X.(*ast.Ident); ok {
return varName, isErrorType(ii.Name, allTypes, errorTypes)
}
}
}

Expand All @@ -172,19 +189,31 @@ func isSentinelError( //nolint:gocognit
}

// var ErrEndOfFile = &EndOfFileError{}
// var ErrOK = &SomePtrError[string]{Code: "200 OK"}
case *ast.UnaryExpr:
if vv.Op == token.AND { // &
if lit, ok := vv.X.(*ast.CompositeLit); ok {
if i, ok := lit.Type.(*ast.Ident); ok {
switch i := lit.Type.(type) {
case *ast.Ident:
return varName, isErrorType(i.Name, allTypes, errorTypes)
case *ast.IndexExpr:
if ii, ok := i.X.(*ast.Ident); ok {
return varName, isErrorType(ii.Name, allTypes, errorTypes)
}
}
}
}

// var ErrEndOfFile = EndOfFileError{}
// var ErrNotFound = SomeError[string]{Code: "Not Found"}
case *ast.CompositeLit:
if i, ok := vv.Type.(*ast.Ident); ok {
switch i := vv.Type.(type) {
case *ast.Ident:
return varName, isErrorType(i.Name, allTypes, errorTypes)
case *ast.IndexExpr:
if ii, ok := i.X.(*ast.Ident); ok {
return varName, isErrorType(ii.Name, allTypes, errorTypes)
}
}
}

Expand Down
30 changes: 30 additions & 0 deletions pkg/analyzer/testdata/src/unusual/generics/generic_types.go
@@ -0,0 +1,30 @@
package generics

type NotErrorGeneric[T float64 | int] struct {
Limit T
}

type SomeError[T ~string] struct{ Code T }

func (e SomeError[T]) Error() string { return string(e.Code) }

type SomePtrError[T ~string] struct{ Code T }

func (e *SomePtrError[T]) Error() string { return string(e.Code) }

type someErr[T ~string] struct{ Code T } // want "the type name `someErr` should conform to the `xxxError` format"
func (e someErr[T]) Error() string { return string(e.Code) }

type SomePtrErr[T ~string] struct{ Code T } // want "the type name `SomePtrErr` should conform to the `XxxError` format"
func (e *SomePtrErr[T]) Error() string { return string(e.Code) }

var (
ErrOK = &SomePtrError[string]{Code: "200 OK"}
okErr = &SomePtrError[string]{Code: "200 OK"} // want "the variable name `okErr` should conform to the `errXxx` format"

ErrNotFound = SomeError[string]{Code: "Not Found"}
NotFoundErr = SomeError[string]{Code: "Not Found"} // want "the variable name `NotFoundErr` should conform to the `ErrXxx` format"

statusCodeError = new(SomePtrError[string]) // want "the variable name `statusCodeError` should conform to the `errXxx` format"
ExplicitError error = new(SomePtrError[string]) // want "the variable name `ExplicitError` should conform to the `ErrXxx` format"
)