Files
odidere/vendor/honnef.co/go/tools/simple/s1001/s1001.go
2026-06-06 01:17:29 +00:00

191 lines
4.8 KiB
Go

package s1001
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"honnef.co/go/tools/pattern"
"golang.org/x/tools/go/analysis"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1001",
Run: run,
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
},
Doc: &lint.RawDocumentation{
Title: `Replace for loop with call to copy`,
Text: `
Use \'copy()\' for copying elements from one slice to another. For
arrays of identical size, you can use simple assignment.`,
Before: `
for i, x := range src {
dst[i] = x
}`,
After: `copy(dst, src)`,
Since: "2017.1",
// MergeIfAll because the types of src and dst might be different under different build tags.
// You shouldn't write code like that…
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
var (
checkLoopCopyQ = pattern.MustParse(`
(Or
(RangeStmt
key@(Ident _) value@(Ident _) ":=" src
[(AssignStmt (IndexExpr dst key) "=" value)])
(RangeStmt
key@(Ident _) nil ":=" src
[(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))])
(ForStmt
(AssignStmt key@(Ident _) ":=" (IntegerLiteral "0"))
(BinaryExpr key "<" (CallExpr (Symbol "len") [src]))
(IncDecStmt key "++")
[(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))]))`)
)
func run(pass *analysis.Pass) (any, error) {
// TODO revisit once range doesn't require a structural type
isInvariant := func(k, v types.Object, node ast.Expr) bool {
if code.MayHaveSideEffects(pass, node, nil) {
return false
}
invariant := true
ast.Inspect(node, func(node ast.Node) bool {
if node, ok := node.(*ast.Ident); ok {
obj := pass.TypesInfo.ObjectOf(node)
if obj == k || obj == v {
// don't allow loop bodies like 'a[i][i] = v'
invariant = false
return false
}
}
return true
})
return invariant
}
var elType func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool)
elType = func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool) {
switch typ := T.Underlying().(type) {
case *types.Slice:
return typ.Elem(), false, false, true
case *types.Array:
return typ.Elem(), true, false, true
case *types.Pointer:
el, isArray, _, ok = elType(typ.Elem())
return el, isArray, true, ok
default:
return nil, false, false, false
}
}
for node, m := range code.Matches(pass, checkLoopCopyQ) {
src := m.State["src"].(ast.Expr)
dst := m.State["dst"].(ast.Expr)
k := pass.TypesInfo.ObjectOf(m.State["key"].(*ast.Ident))
var v types.Object
if value, ok := m.State["value"]; ok {
v = pass.TypesInfo.ObjectOf(value.(*ast.Ident))
}
if !isInvariant(k, v, dst) {
continue
}
if !isInvariant(k, v, src) {
// For example: 'for i := range foo()'
continue
}
Tsrc := pass.TypesInfo.TypeOf(src)
Tdst := pass.TypesInfo.TypeOf(dst)
TsrcElem, TsrcArray, TsrcPointer, ok := elType(Tsrc)
if !ok {
continue
}
if TsrcPointer {
Tsrc = Tsrc.Underlying().(*types.Pointer).Elem()
}
TdstElem, TdstArray, TdstPointer, ok := elType(Tdst)
if !ok {
continue
}
if TdstPointer {
Tdst = Tdst.Underlying().(*types.Pointer).Elem()
}
if !types.Identical(TsrcElem, TdstElem) {
continue
}
if TsrcArray && TdstArray && types.Identical(Tsrc, Tdst) {
if TsrcPointer {
src = &ast.StarExpr{
X: src,
}
}
if TdstPointer {
dst = &ast.StarExpr{
X: dst,
}
}
r := &ast.AssignStmt{
Lhs: []ast.Expr{dst},
Rhs: []ast.Expr{src},
Tok: token.ASSIGN,
}
report.Report(pass, node, "should copy arrays using assignment instead of using a loop",
report.FilterGenerated(),
report.ShortRange(),
report.Fixes(edit.Fix("Replace loop with assignment", edit.ReplaceWithNode(pass.Fset, node, r))))
} else {
tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy")
if err == nil && tv.IsBuiltin() {
to := "to"
from := "from"
src := m.State["src"].(ast.Expr)
if TsrcArray {
from = "from[:]"
src = &ast.SliceExpr{
X: src,
}
}
dst := m.State["dst"].(ast.Expr)
if TdstArray {
to = "to[:]"
dst = &ast.SliceExpr{
X: dst,
}
}
r := &ast.CallExpr{
Fun: &ast.Ident{Name: "copy"},
Args: []ast.Expr{dst, src},
}
opts := []report.Option{
report.ShortRange(),
report.FilterGenerated(),
report.Fixes(edit.Fix("Replace loop with call to copy()", edit.ReplaceWithNode(pass.Fset, node, r))),
}
report.Report(pass, node, fmt.Sprintf("should use copy(%s, %s) instead of a loop", to, from), opts...)
}
}
}
return nil, nil
}