Skip to content

Commit dc30eb1

Browse files
authored
fix(receiver-naming): distinguish types with parameters (#692)
* fix(receiver-naming): distinguish types with parameters * chore: run tests using supported Go versions matrix
1 parent 76ef1d7 commit dc30eb1

File tree

13 files changed

+153
-40
lines changed

13 files changed

+153
-40
lines changed
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
name: Build and Test
1+
name: Build
22
on:
3+
workflow_dispatch:
34
pull_request:
45
types: [opened, edited, synchronize, reopened]
56

@@ -11,10 +12,3 @@ jobs:
1112
- uses: actions/checkout@v2
1213
- name: build
1314
run: make build
14-
test:
15-
name: Test
16-
runs-on: ubuntu-latest
17-
steps:
18-
- uses: actions/checkout@v2
19-
- name: test
20-
run: make test

.github/workflows/lint.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: Lint
22
on:
3+
workflow_dispatch:
34
pull_request:
45
types: [opened, edited, synchronize, reopened]
56

.github/workflows/test.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Test
2+
on:
3+
workflow_dispatch:
4+
pull_request:
5+
types: [opened, edited, synchronize, reopened]
6+
7+
jobs:
8+
test:
9+
name: Test
10+
runs-on: ubuntu-latest
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
go-version:
15+
- 1.16.x
16+
- 1.17.x
17+
- 1.18.x
18+
steps:
19+
- name: Checkout code
20+
uses: actions/[email protected]
21+
- name: Set up Go
22+
uses: actions/[email protected]
23+
with:
24+
go-version: ${{ matrix.go-version }}
25+
cache: true
26+
cache-dependency-path: '**/go.sum'
27+
- name: Run tests
28+
run: go test -race ./...

internal/typeparams/typeparams.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Package typeparams provides utilities for working with Go ASTs with support
2+
// for type parameters when built with Go 1.18 and higher.
3+
package typeparams
4+
5+
import (
6+
"go/ast"
7+
)
8+
9+
// Enabled reports whether type parameters are enabled in the current build
10+
// environment.
11+
func Enabled() bool {
12+
return enabled
13+
}
14+
15+
// ReceiverType returns the named type of the method receiver, sans "*" and type
16+
// parameters, or "invalid-type" if fn.Recv is ill formed.
17+
func ReceiverType(fn *ast.FuncDecl) string {
18+
e := fn.Recv.List[0].Type
19+
if s, ok := e.(*ast.StarExpr); ok {
20+
e = s.X
21+
}
22+
if enabled {
23+
e = unpackIndexExpr(e)
24+
}
25+
if id, ok := e.(*ast.Ident); ok {
26+
return id.Name
27+
}
28+
return "invalid-type"
29+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build !go1.18
2+
// +build !go1.18
3+
4+
package typeparams
5+
6+
import (
7+
"go/ast"
8+
)
9+
10+
const enabled = false
11+
12+
func unpackIndexExpr(e ast.Expr) ast.Expr { return e }
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//go:build go1.18
2+
// +build go1.18
3+
4+
package typeparams
5+
6+
import (
7+
"go/ast"
8+
)
9+
10+
const enabled = true
11+
12+
func unpackIndexExpr(e ast.Expr) ast.Expr {
13+
switch e := e.(type) {
14+
case *ast.IndexExpr:
15+
return e.X
16+
case *ast.IndexListExpr:
17+
return e.X
18+
}
19+
return e
20+
}

lint/package.go

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"sync"
88

99
"golang.org/x/tools/go/gcexportdata"
10+
11+
"github.com/mgechev/revive/internal/typeparams"
1012
)
1113

1214
// Package represents a package in the project.
@@ -146,7 +148,7 @@ func (w *walker) Visit(n ast.Node) ast.Visitor {
146148
return w
147149
}
148150
// TODO(dsymonds): We could check the signature to be more precise.
149-
recv := receiverType(fn)
151+
recv := typeparams.ReceiverType(fn)
150152
if i, ok := w.nmap[fn.Name.Name]; ok {
151153
w.has[recv] |= i
152154
}
@@ -174,21 +176,6 @@ func (p *Package) scanSortable() {
174176
}
175177
}
176178

177-
// receiverType returns the named type of the method receiver, sans "*",
178-
// or "invalid-type" if fn.Recv is ill formed.
179-
func receiverType(fn *ast.FuncDecl) string {
180-
switch e := fn.Recv.List[0].Type.(type) {
181-
case *ast.Ident:
182-
return e.Name
183-
case *ast.StarExpr:
184-
if id, ok := e.X.(*ast.Ident); ok {
185-
return id.Name
186-
}
187-
}
188-
// The parser accepts much more than just the legal forms.
189-
return "invalid-type"
190-
}
191-
192179
func (p *Package) lint(rules []Rule, config Config, failures chan Failure) {
193180
p.scanSortable()
194181
var wg sync.WaitGroup

rule/exported.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"unicode"
1010
"unicode/utf8"
1111

12+
"github.com/mgechev/revive/internal/typeparams"
1213
"github.com/mgechev/revive/lint"
1314
)
1415

@@ -116,7 +117,7 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
116117
if fn.Recv != nil && len(fn.Recv.List) > 0 {
117118
// method
118119
kind = "method"
119-
recv := receiverType(fn)
120+
recv := typeparams.ReceiverType(fn)
120121
if !w.checkPrivateReceivers && !ast.IsExported(recv) {
121122
// receiver is unexported
122123
return

rule/receiver-naming.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"go/ast"
66

7+
"github.com/mgechev/revive/internal/typeparams"
78
"github.com/mgechev/revive/lint"
89
)
910

@@ -65,7 +66,7 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
6566
})
6667
return w
6768
}
68-
recv := receiverType(fn)
69+
recv := typeparams.ReceiverType(fn)
6970
if prev, ok := w.typeReceiver[recv]; ok && prev != name {
7071
w.onFailure(lint.Failure{
7172
Node: n,

rule/unexported-return.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"go/ast"
66
"go/types"
77

8+
"github.com/mgechev/revive/internal/typeparams"
89
"github.com/mgechev/revive/lint"
910
)
1011

@@ -55,7 +56,7 @@ func (w lintUnexportedReturn) Visit(n ast.Node) ast.Visitor {
5556
thing := "func"
5657
if fn.Recv != nil && len(fn.Recv.List) > 0 {
5758
thing = "method"
58-
if !ast.IsExported(receiverType(fn)) {
59+
if !ast.IsExported(typeparams.ReceiverType(fn)) {
5960
// Don't report exported methods of unexported types,
6061
// such as private implementations of sort.Interface.
6162
return nil

rule/utils.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,6 @@ var commonMethods = map[string]bool{
2626
"Unwrap": true,
2727
}
2828

29-
func receiverType(fn *ast.FuncDecl) string {
30-
switch e := fn.Recv.List[0].Type.(type) {
31-
case *ast.Ident:
32-
return e.Name
33-
case *ast.StarExpr:
34-
if id, ok := e.X.(*ast.Ident); ok {
35-
return id.Name
36-
}
37-
}
38-
// The parser accepts much more than just the legal forms.
39-
return "invalid-type"
40-
}
41-
4229
var knownNameExceptions = map[string]bool{
4330
"LastInsertId": true, // must match database/sql
4431
"kWh": true,

test/receiver-naming_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mgechev/revive/internal/typeparams"
7+
"github.com/mgechev/revive/rule"
8+
)
9+
10+
func TestReceiverNamingTypeParams(t *testing.T) {
11+
if !typeparams.Enabled() {
12+
t.Skip("type parameters are not enabled in the current build environment")
13+
}
14+
testRule(t, "receiver-naming-issue-669", &rule.ReceiverNamingRule{})
15+
}

testdata/receiver-naming-issue-669.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package fixtures
2+
3+
type gen1[T any] struct{}
4+
5+
func (g gen1[T]) f1() {}
6+
7+
func (g gen1[U]) f2() {}
8+
9+
func (n gen1[T]) f3() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
10+
11+
func (n gen1[U]) f4() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
12+
13+
func (n gen1[V]) f5() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
14+
15+
func (n *gen1[T]) f6() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
16+
17+
func (n *gen1[U]) f7() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
18+
19+
func (n *gen1[V]) f8() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/
20+
21+
type gen2[T1, T2 any] struct{}
22+
23+
func (g gen2[T1, T2]) f1() {}
24+
25+
func (g gen2[U1, U2]) f2() {}
26+
27+
func (n gen2[T1, T2]) f3() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
28+
29+
func (n gen2[U1, U2]) f4() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
30+
31+
func (n gen2[V1, V2]) f5() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
32+
33+
func (n *gen2[T1, T2]) f6() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
34+
35+
func (n *gen2[U1, U2]) f7() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/
36+
37+
func (n *gen2[V1, V2]) f8() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

0 commit comments

Comments
 (0)