Skip to content

Commit

Permalink
fix(receiver-naming): distinguish types with parameters (#692)
Browse files Browse the repository at this point in the history
* fix(receiver-naming): distinguish types with parameters

* chore: run tests using supported Go versions matrix
  • Loading branch information
tie authored Jun 18, 2022
1 parent 76ef1d7 commit dc30eb1
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Build and Test
name: Build
on:
workflow_dispatch:
pull_request:
types: [opened, edited, synchronize, reopened]

Expand All @@ -11,10 +12,3 @@ jobs:
- uses: actions/checkout@v2
- name: build
run: make build
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: test
run: make test
1 change: 1 addition & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
name: Lint
on:
workflow_dispatch:
pull_request:
types: [opened, edited, synchronize, reopened]

Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Test
on:
workflow_dispatch:
pull_request:
types: [opened, edited, synchronize, reopened]

jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go-version:
- 1.16.x
- 1.17.x
- 1.18.x
steps:
- name: Checkout code
uses: actions/checkout@v3.0.2
- name: Set up Go
uses: actions/setup-go@v3.2.0
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: '**/go.sum'
- name: Run tests
run: go test -race ./...
29 changes: 29 additions & 0 deletions internal/typeparams/typeparams.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Package typeparams provides utilities for working with Go ASTs with support
// for type parameters when built with Go 1.18 and higher.
package typeparams

import (
"go/ast"
)

// Enabled reports whether type parameters are enabled in the current build
// environment.
func Enabled() bool {
return enabled
}

// ReceiverType returns the named type of the method receiver, sans "*" and type
// parameters, or "invalid-type" if fn.Recv is ill formed.
func ReceiverType(fn *ast.FuncDecl) string {
e := fn.Recv.List[0].Type
if s, ok := e.(*ast.StarExpr); ok {
e = s.X
}
if enabled {
e = unpackIndexExpr(e)
}
if id, ok := e.(*ast.Ident); ok {
return id.Name
}
return "invalid-type"
}
12 changes: 12 additions & 0 deletions internal/typeparams/typeparams_go117.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build !go1.18
// +build !go1.18

package typeparams

import (
"go/ast"
)

const enabled = false

func unpackIndexExpr(e ast.Expr) ast.Expr { return e }
20 changes: 20 additions & 0 deletions internal/typeparams/typeparams_go118.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build go1.18
// +build go1.18

package typeparams

import (
"go/ast"
)

const enabled = true

func unpackIndexExpr(e ast.Expr) ast.Expr {
switch e := e.(type) {
case *ast.IndexExpr:
return e.X
case *ast.IndexListExpr:
return e.X
}
return e
}
19 changes: 3 additions & 16 deletions lint/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sync"

"golang.org/x/tools/go/gcexportdata"

"github.com/mgechev/revive/internal/typeparams"
)

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

// receiverType returns the named type of the method receiver, sans "*",
// or "invalid-type" if fn.Recv is ill formed.
func receiverType(fn *ast.FuncDecl) string {
switch e := fn.Recv.List[0].Type.(type) {
case *ast.Ident:
return e.Name
case *ast.StarExpr:
if id, ok := e.X.(*ast.Ident); ok {
return id.Name
}
}
// The parser accepts much more than just the legal forms.
return "invalid-type"
}

func (p *Package) lint(rules []Rule, config Config, failures chan Failure) {
p.scanSortable()
var wg sync.WaitGroup
Expand Down
3 changes: 2 additions & 1 deletion rule/exported.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"unicode"
"unicode/utf8"

"github.com/mgechev/revive/internal/typeparams"
"github.com/mgechev/revive/lint"
)

Expand Down Expand Up @@ -116,7 +117,7 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
if fn.Recv != nil && len(fn.Recv.List) > 0 {
// method
kind = "method"
recv := receiverType(fn)
recv := typeparams.ReceiverType(fn)
if !w.checkPrivateReceivers && !ast.IsExported(recv) {
// receiver is unexported
return
Expand Down
3 changes: 2 additions & 1 deletion rule/receiver-naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"go/ast"

"github.com/mgechev/revive/internal/typeparams"
"github.com/mgechev/revive/lint"
)

Expand Down Expand Up @@ -65,7 +66,7 @@ func (w lintReceiverName) Visit(n ast.Node) ast.Visitor {
})
return w
}
recv := receiverType(fn)
recv := typeparams.ReceiverType(fn)
if prev, ok := w.typeReceiver[recv]; ok && prev != name {
w.onFailure(lint.Failure{
Node: n,
Expand Down
3 changes: 2 additions & 1 deletion rule/unexported-return.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"go/ast"
"go/types"

"github.com/mgechev/revive/internal/typeparams"
"github.com/mgechev/revive/lint"
)

Expand Down Expand Up @@ -55,7 +56,7 @@ func (w lintUnexportedReturn) Visit(n ast.Node) ast.Visitor {
thing := "func"
if fn.Recv != nil && len(fn.Recv.List) > 0 {
thing = "method"
if !ast.IsExported(receiverType(fn)) {
if !ast.IsExported(typeparams.ReceiverType(fn)) {
// Don't report exported methods of unexported types,
// such as private implementations of sort.Interface.
return nil
Expand Down
13 changes: 0 additions & 13 deletions rule/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,6 @@ var commonMethods = map[string]bool{
"Unwrap": true,
}

func receiverType(fn *ast.FuncDecl) string {
switch e := fn.Recv.List[0].Type.(type) {
case *ast.Ident:
return e.Name
case *ast.StarExpr:
if id, ok := e.X.(*ast.Ident); ok {
return id.Name
}
}
// The parser accepts much more than just the legal forms.
return "invalid-type"
}

var knownNameExceptions = map[string]bool{
"LastInsertId": true, // must match database/sql
"kWh": true,
Expand Down
15 changes: 15 additions & 0 deletions test/receiver-naming_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test

import (
"testing"

"github.com/mgechev/revive/internal/typeparams"
"github.com/mgechev/revive/rule"
)

func TestReceiverNamingTypeParams(t *testing.T) {
if !typeparams.Enabled() {
t.Skip("type parameters are not enabled in the current build environment")
}
testRule(t, "receiver-naming-issue-669", &rule.ReceiverNamingRule{})
}
37 changes: 37 additions & 0 deletions testdata/receiver-naming-issue-669.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package fixtures

type gen1[T any] struct{}

func (g gen1[T]) f1() {}

func (g gen1[U]) f2() {}

func (n gen1[T]) f3() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/

func (n gen1[U]) f4() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/

func (n gen1[V]) f5() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/

func (n *gen1[T]) f6() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/

func (n *gen1[U]) f7() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/

func (n *gen1[V]) f8() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen1/

type gen2[T1, T2 any] struct{}

func (g gen2[T1, T2]) f1() {}

func (g gen2[U1, U2]) f2() {}

func (n gen2[T1, T2]) f3() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

func (n gen2[U1, U2]) f4() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

func (n gen2[V1, V2]) f5() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

func (n *gen2[T1, T2]) f6() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

func (n *gen2[U1, U2]) f7() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

func (n *gen2[V1, V2]) f8() {} // MATCH /receiver name n should be consistent with previous receiver name g for gen2/

0 comments on commit dc30eb1

Please sign in to comment.