Skip to content

Commit 2a3720d

Browse files
committed
chore: Improve sortables detection
1 parent 3bc2404 commit 2a3720d

File tree

1 file changed

+57
-12
lines changed

1 file changed

+57
-12
lines changed

lint/package.go

+57-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package lint
22

33
import (
4+
"fmt"
45
"go/ast"
56
"go/importer"
67
"go/token"
@@ -31,7 +32,6 @@ type Package struct {
3132
var (
3233
trueValue = 1
3334
falseValue = 2
34-
notSet = 3
3535

3636
go121 = goversion.Must(goversion.NewVersion("1.21"))
3737
go122 = goversion.Must(goversion.NewVersion("1.22"))
@@ -111,6 +111,11 @@ func (p *Package) TypeCheck() error {
111111
astFiles = append(astFiles, f.AST)
112112
}
113113

114+
if anyFile == nil {
115+
// this is unlikely to happen, but technically guarantees anyFile to not be nil
116+
return fmt.Errorf("no ast.File found")
117+
}
118+
114119
typesPkg, err := check(config, anyFile.AST.Name.Name, p.fset, astFiles, info)
115120

116121
// Remember the typechecking info, even if config.Check failed,
@@ -135,7 +140,7 @@ func check(config *types.Config, n string, fset *token.FileSet, astFiles []*ast.
135140
return config.Check(n, fset, astFiles, info)
136141
}
137142

138-
// TypeOf returns the type of an expression.
143+
// TypeOf returns the type of expression.
139144
func (p *Package) TypeOf(expr ast.Expr) types.Type {
140145
if p.typesInfo == nil {
141146
return nil
@@ -148,32 +153,72 @@ type walker struct {
148153
has map[string]int
149154
}
150155

156+
// bitfield for which methods exist on each type.
157+
const (
158+
bfLen = 1 << iota
159+
bfLess
160+
bfSwap
161+
)
162+
151163
func (w *walker) Visit(n ast.Node) ast.Visitor {
152164
fn, ok := n.(*ast.FuncDecl)
153165
if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 {
154166
return w
155167
}
156-
// TODO(dsymonds): We could check the signature to be more precise.
168+
157169
recv := typeparams.ReceiverType(fn)
158-
if i, ok := w.nmap[fn.Name.Name]; ok {
159-
w.has[recv] |= i
170+
171+
// Ensure the method signature matches expectations.
172+
switch fn.Name.Name {
173+
case "Len":
174+
if fn.Type.Params.NumFields() == 0 && fn.Type.Results.NumFields() == 1 {
175+
resultType := fn.Type.Results.List[0].Type
176+
if _, ok := resultType.(*ast.Ident); ok && resultType.(*ast.Ident).Name == "int" {
177+
w.has[recv] |= bfLen
178+
}
179+
}
180+
case "Less":
181+
if fn.Type.Params.NumFields() == 2 && fn.Type.Results.NumFields() == 1 {
182+
param1 := fn.Type.Params.List[0].Type
183+
var param2 ast.Expr
184+
if len(fn.Type.Params.List) == 2 {
185+
param2 = fn.Type.Params.List[1].Type
186+
} else {
187+
param2 = param1
188+
}
189+
resultType := fn.Type.Results.List[0].Type
190+
191+
// Ensure parameters have the same type and the result is a bool.
192+
if typesEqual(param1, param2) && isBool(resultType) {
193+
w.has[recv] |= bfLess
194+
}
195+
}
196+
case "Swap":
197+
if fn.Type.Params.NumFields() == 2 && fn.Type.Results.NumFields() == 0 {
198+
w.has[recv] |= bfSwap
199+
}
160200
}
161201
return w
162202
}
163203

204+
func typesEqual(a, b ast.Expr) bool {
205+
identA, okA := a.(*ast.Ident)
206+
identB, okB := b.(*ast.Ident)
207+
return okA && okB && identA.Name == identB.Name
208+
}
209+
210+
func isBool(t ast.Expr) bool {
211+
ident, ok := t.(*ast.Ident)
212+
return ok && ident.Name == "bool"
213+
}
214+
164215
func (p *Package) scanSortable() {
165216
p.sortable = map[string]bool{}
166217

167-
// bitfield for which methods exist on each type.
168-
const (
169-
bfLen = 1 << iota
170-
bfLess
171-
bfSwap
172-
)
173218
nmap := map[string]int{"Len": bfLen, "Less": bfLess, "Swap": bfSwap}
174219
has := map[string]int{}
175220
for _, f := range p.files {
176-
ast.Walk(&walker{nmap, has}, f.AST)
221+
ast.Walk(&walker{nmap: nmap, has: has}, f.AST)
177222
}
178223
for typ, ms := range has {
179224
if ms == bfLen|bfLess|bfSwap {

0 commit comments

Comments
 (0)