Skip to content

Commit

Permalink
ruleguard/typematch: improve function type matching (#358)
Browse files Browse the repository at this point in the history
  • Loading branch information
quasilyte authored Jan 16, 2022
1 parent 60b3b25 commit 1e92ea5
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 9 deletions.
29 changes: 29 additions & 0 deletions analyzer/testdata/src/filtertest/f1.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ func _() {
fileTest("f1.go") // want `true`
}

func detectFunc() {
var fn func()

{
typeTest((func(int) bool)(nil), "is predicate func") // want `true`
typeTest((func(string) bool)(nil), "is predicate func") // want `true`
typeTest((func() bool)(nil), "is predicate func")
typeTest((func(int) string)(nil), "is predicate func")
typeTest(fn, "is predicate func")
typeTest(&fn, "is predicate func")
typeTest(10, "is predicate func")
typeTest("str", "is predicate func")
}

{
typeTest((func(int) bool)(nil), "is func") // want `true`
typeTest((func(string) bool)(nil), "is func") // want `true`
typeTest((func() bool)(nil), "is func") // want `true`
typeTest((func(int) string)(nil), "is func") // want `true`
typeTest((func())(nil), "is func") // want `true`
typeTest(func() {}, "is func") // want `true`
typeTest(fn, "is func") // want `true`
typeTest(&fn, "is func")
typeTest(53, "is func")
typeTest([]int{1}, "is func")

}
}

func detectObject() {
var vec vector2D

Expand Down
8 changes: 8 additions & 0 deletions analyzer/testdata/src/filtertest/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,12 @@ func testRules(m dsl.Matcher) {
m.Match(`typeTest($x, $y, "same type sizes")`).
Where(m["x"].Type.Size == m["y"].Type.Size).
Report(`true`)

m.Match(`typeTest($x, "is predicate func")`).
Where(m["x"].Type.Is(`func ($_) bool`)).
Report(`true`)

m.Match(`typeTest($x, "is func")`).
Where(m["x"].Type.Is(`func ($*_) $*_`)).
Report(`true`)
}
15 changes: 8 additions & 7 deletions ruleguard/typematch/patternop_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 39 additions & 2 deletions ruleguard/typematch/typematch.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
opArray
opMap
opChan
opFuncNoSeq
opFunc
opStructNoSeq
opStruct
Expand Down Expand Up @@ -253,6 +254,7 @@ func parseExpr(ctx *Context, e ast.Expr) *pattern {
return parseExpr(ctx, e.X)

case *ast.FuncType:
hasSeq := false
var params []*pattern
var results []*pattern
if e.Params != nil {
Expand All @@ -264,6 +266,9 @@ func parseExpr(ctx *Context, e ast.Expr) *pattern {
if len(field.Names) != 0 {
return nil
}
if p.op == opVarSeq {
hasSeq = true
}
params = append(params, p)
}
}
Expand All @@ -276,11 +281,18 @@ func parseExpr(ctx *Context, e ast.Expr) *pattern {
if len(field.Names) != 0 {
return nil
}
if p.op == opVarSeq {
hasSeq = true
}
results = append(results, p)
}
}
op := opFuncNoSeq
if hasSeq {
op = opFunc
}
return &pattern{
op: opFunc,
op: op,
value: len(params),
subs: append(params, results...),
}
Expand Down Expand Up @@ -485,7 +497,7 @@ func (p *Pattern) matchIdentical(sub *pattern, typ types.Type) bool {
path := strings.SplitAfter(obj.Pkg().Path(), "/vendor/")
return path[len(path)-1] == pkgPath && typeName == obj.Name()

case opFunc:
case opFuncNoSeq:
typ, ok := typ.(*types.Signature)
if !ok {
return false
Expand All @@ -511,6 +523,24 @@ func (p *Pattern) matchIdentical(sub *pattern, typ types.Type) bool {
}
return true

case opFunc:
typ, ok := typ.(*types.Signature)
if !ok {
return false
}
numParams := sub.value.(int)
params := sub.subs[:numParams]
results := sub.subs[numParams:]
adapter := tupleFielder{x: typ.Params()}
if !p.matchIdenticalFielder(params, &adapter) {
return false
}
adapter.x = typ.Results()
if !p.matchIdenticalFielder(results, &adapter) {
return false
}
return true

case opStructNoSeq:
typ, ok := typ.(*types.Struct)
if !ok {
Expand Down Expand Up @@ -549,3 +579,10 @@ type fielder interface {
Field(i int) *types.Var
NumFields() int
}

type tupleFielder struct {
x *types.Tuple
}

func (tup *tupleFielder) Field(i int) *types.Var { return tup.x.At(i) }
func (tup *tupleFielder) NumFields() int { return tup.x.Len() }
17 changes: 17 additions & 0 deletions ruleguard/typematch/typematch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,18 @@ func TestIdentical(t *testing.T) {
{`func($_) int`, types.NewSignature(nil, types.NewTuple(intVar), types.NewTuple(intVar), false)},
{`func($_) int`, types.NewSignature(nil, types.NewTuple(stringVar), types.NewTuple(intVar), false)},

{`func($*_) int`, types.NewSignature(nil, types.NewTuple(stringVar), types.NewTuple(intVar), false)},
{`func($*_) int`, types.NewSignature(nil, nil, types.NewTuple(intVar), false)},
{`func($*_) $_`, types.NewSignature(nil, nil, types.NewTuple(intVar), false)},

{`func($t, $t)`, types.NewSignature(nil, types.NewTuple(stringVar, stringVar), nil, false)},
{`func($t, $t)`, types.NewSignature(nil, types.NewTuple(intVar, intVar), nil, false)},

// Any func.
{`func($*_) $*_`, types.NewSignature(nil, nil, nil, false)},
{`func($*_) $*_`, types.NewSignature(nil, types.NewTuple(stringVar, stringVar), nil, false)},
{`func($*_) $*_`, types.NewSignature(nil, types.NewTuple(stringVar), types.NewTuple(intVar), false)},

{`struct{}`, typeEstruct},
{`struct{int}`, types.NewStruct([]*types.Var{intVar}, nil)},
{`struct{string; int}`, types.NewStruct([]*types.Var{stringVar, intVar}, nil)},
Expand Down Expand Up @@ -197,6 +206,14 @@ func TestIdenticalNegative(t *testing.T) {
{`func($t, $t)`, types.NewSignature(nil, types.NewTuple(intVar, stringVar), nil, false)},
{`func($t, $t)`, types.NewSignature(nil, types.NewTuple(stringVar, intVar), nil, false)},

{`func($*_) int`, types.NewSignature(nil, types.NewTuple(stringVar), types.NewTuple(stringVar), false)},
{`func($*_) int`, types.NewSignature(nil, nil, nil, false)},
{`func($*_) $_`, types.NewSignature(nil, nil, nil, false)},

// Any func negative.
{`func($*_) $*_`, typeInt},
{`func($*_) $*_`, types.NewTuple(intVar)},

{`struct{}`, typeInt},
{`struct{}`, types.NewStruct([]*types.Var{intVar}, nil)},
{`struct{int}`, typeEstruct},
Expand Down

0 comments on commit 1e92ea5

Please sign in to comment.