golang bools 代码
文件路径:/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/bools/bools.go
/ Copyright 2014 The Go Authors. All rights reserved.
/ Use of this source code is governed by a BSD-style
/ license that can be found in the LICENSE file.
/ Package bools defines an Analyzer that detects common mistakes
/ involving boolean operators.
package bools
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
)
const Doc = "check for common mistakes involving boolean operators"
var Analyzer = &analysis.Analyzer{
Name: "bools",
Doc: Doc,
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
(*ast.BinaryExpr)(nil),
}
seen := make(map[*ast.BinaryExpr]bool)
inspect.Preorder(nodeFilter, func(n ast.Node) {
e := n.(*ast.BinaryExpr)
if seen[e] {
/ Already processed as a subexpression of an earlier node.
return
}
var op boolOp
switch e.Op {
case token.LOR:
op = or
case token.LAND:
op = and
default:
return
}
comm := op.commutativeSets(pass.TypesInfo, e, seen)
for _, exprs := range comm {
op.checkRedundant(pass, exprs)
op.checkSuspect(pass, exprs)
}
})
return nil, nil
}
type boolOp struct {
name string
tok token.Token / token corresponding to this operator
badEq token.Token / token corresponding to the equality test that should not be used with this operator
}
var (
or = boolOp{"or", token.LOR, token.NEQ}
and = boolOp{"and", token.LAND, token.EQL}
)
/ commutativeSets returns all side effect free sets of
/ expressions in e that are connected by op.
/ For example, given 'a || b || f() || c || d' with the or op,
/ commutativeSets returns {{b, a}, {d, c}}.
/ commutativeSets adds any expanded BinaryExprs to seen.
func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*ast.BinaryExpr]bool) [][]ast.Expr {
exprs := op.split(e, seen)
/ Partition the slice of expressions into commutative sets.
i := 0
var sets [][]ast.Expr
for j := 0; j <= len(exprs); j++ {
if j == len(exprs) || hasSideEffects(info, exprs[j]) {
if i < j {
sets = append(sets, exprs[i:j])
}
i = j + 1
}
}
return sets
}
/ checkRedundant checks for expressions of the form
/
/ e && e
/ e || e
/
/ Exprs must contain only side effect free expressions.
func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
seen := make(map[string]bool)
for _, e := range exprs {
efmt := analysisutil.Format(pass.Fset, e)
if seen[efmt] {
pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
} else {
seen[efmt] = true
}
}
}
/ checkSuspect checks for expressions of the form
/
/ x != c1 || x != c2
/ x == c1 && x == c2
/
/ where c1 and c2 are constant expressions.
/ If c1 and c2 are the same then it's redundant;
/ if c1 and c2 are different then it's always true or always false.
/ Exprs must contain only side effect free expressions.
func (op boolOp) checkSuspect(pass *analysis.Pass, exprs []ast.Expr) {
/ seen maps from expressions 'x' to equality expressions 'x != c'.
seen := make(map[string]string)
for _, e := range exprs {
bin, ok := e.(*ast.BinaryExpr)
if !ok || bin.Op != op.badEq {
continue
}
/ In order to avoid false positives, restrict to cases
/ in which one of the operands is constant. We're then
/ interested in the other operand.
/ In the rare case in which both operands are constant
/ (e.g. runtime.GOOS and "windows"), we'll only catch
/ mistakes if the LHS is repeated, which is how most
/ code is written.
var x ast.Expr
switch {
case pass.TypesInfo.Types[bin.Y].Value != nil:
x = bin.X
case pass.TypesInfo.Types[bin.X].Value != nil:
x = bin.Y
default:
continue
}
/ e is of the form 'x != c' or 'x == c'.
xfmt := analysisutil.Format(pass.Fset, x)
efmt := analysisutil.Format(pass.Fset, e)
if prev, found := seen[xfmt]; found {
/ checkRedundant handles the case in which efmt == prev.
if efmt != prev {
pass.ReportRangef(e, "suspect %s: %s %s %s", op.name, efmt, op.tok, prev)
}
} else {
seen[xfmt] = efmt
}
}
}
/ hasSideEffects reports whether evaluation of e has side effects.
func hasSideEffects(info *types.Info, e ast.Expr) bool {
safe := true
ast.Inspect(e, func(node ast.Node) bool {
switch n := node.(type) {
case *ast.CallExpr:
typVal := info.Types[n.Fun]
switch {
case typVal.IsType():
/ Type conversion, which is safe.
case typVal.IsBuiltin():
/ Builtin func, conservatively assumed to not
/ be safe for now.
safe = false
return false
default:
/ A non-builtin func or method call.
/ Conservatively assume that all of them have
/ side effects for now.
safe = false
return false
}
case *ast.UnaryExpr:
if n.Op == token.ARROW {
safe = false
return false
}
}
return true
})
return !safe
}
/ split returns a slice of all subexpressions in e that are connected by op.
/ For example, given 'a || (b || c) || d' with the or op,
/ split returns []{d, c, b, a}.
/ seen[e] is already true; any newly processed exprs are added to seen.
func (op boolOp) split(e ast.Expr, seen map[*ast.BinaryExpr]bool) (exprs []ast.Expr) {
for {
e = unparen(e)
if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok {
seen[b] = true
exprs = append(exprs, op.split(b.Y, seen)...)
e = b.X
} else {
exprs = append(exprs, e)
break
}
}
return
}
/ unparen returns e with any enclosing parentheses stripped.
func unparen(e ast.Expr) ast.Expr {
for {
p, ok := e.(*ast.ParenExpr)
if !ok {
return e
}
e = p.X
}
}