Skip to content

Commit

Permalink
Merge pull request #11 from Antonboom/generics-support
Browse files Browse the repository at this point in the history
Support generics
  • Loading branch information
Antonboom committed May 26, 2022
2 parents a39484c + 5738501 commit 9daa382
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 19 deletions.
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"
)

0 comments on commit 9daa382

Please sign in to comment.