7 "honnef.co/go/tools/go/ast/astutil"
8 "honnef.co/go/tools/go/ir"
9 "honnef.co/go/tools/go/ir/irutil"
10 "honnef.co/go/tools/internal/passes/buildir"
12 "golang.org/x/tools/go/analysis"
15 func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
16 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
17 cb := func(node ast.Node) bool {
18 rng, ok := node.(*ast.RangeStmt)
19 if !ok || !astutil.IsBlank(rng.Key) {
23 v, _ := fn.ValueForExpr(rng.X)
25 // Check that we're converting from string to []rune
26 val, _ := v.(*ir.Convert)
30 Tsrc, ok := val.X.Type().Underlying().(*types.Basic)
31 if !ok || Tsrc.Kind() != types.String {
34 Tdst, ok := val.Type().(*types.Slice)
38 TdstElem, ok := Tdst.Elem().(*types.Basic)
39 if !ok || TdstElem.Kind() != types.Int32 {
43 // Check that the result of the conversion is only used to
45 refs := val.Referrers()
50 // Expect two refs: one for obtaining the length of the slice,
51 // one for accessing the elements
52 if len(irutil.FilterDebug(*refs)) != 2 {
53 // TODO(dh): right now, we check that only one place
54 // refers to our slice. This will miss cases such as
55 // ranging over the slice twice. Ideally, we'd ensure that
56 // the slice is only used for ranging over (without
57 // accessing the key), but that is harder to do because in
58 // IR form, ranging over a slice looks like an ordinary
59 // loop with index increments and slice accesses. We'd
60 // have to look at the associated AST node to check that
61 // it's a range statement.
65 pass.Reportf(rng.Pos(), "should range over string, not []rune(string)")
69 if source := fn.Source(); source != nil {
70 ast.Inspect(source, cb)