Commit c55505ba authored by Daniel Martí's avatar Daniel Martí

cmd/vet: type conversions never have side effects

Make the hasSideEffects func use type information to see if a CallExpr
is a type conversion or not. In case it is, there cannot be any side
effects.

Now that vet always has type information, we can afford to use it here.
Update the tests and remove the TODO there too.

Change-Id: I74fdacf830aedf2371e67ba833802c414178caf1
Reviewed-on: https://go-review.googlesource.com/79536Reviewed-by: default avatarRobert Griesemer <gri@golang.org>
parent c2ccc481
...@@ -37,7 +37,7 @@ func checkAssignStmt(f *File, node ast.Node) { ...@@ -37,7 +37,7 @@ func checkAssignStmt(f *File, node ast.Node) {
} }
for i, lhs := range stmt.Lhs { for i, lhs := range stmt.Lhs {
rhs := stmt.Rhs[i] rhs := stmt.Rhs[i]
if hasSideEffects(lhs) || hasSideEffects(rhs) { if hasSideEffects(f, lhs) || hasSideEffects(f, rhs) {
continue // expressions may not be equal continue // expressions may not be equal
} }
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) { if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
......
...@@ -9,6 +9,7 @@ package main ...@@ -9,6 +9,7 @@ package main
import ( import (
"go/ast" "go/ast"
"go/token" "go/token"
"go/types"
) )
func init() { func init() {
...@@ -31,7 +32,7 @@ func checkBool(f *File, n ast.Node) { ...@@ -31,7 +32,7 @@ func checkBool(f *File, n ast.Node) {
return return
} }
comm := op.commutativeSets(e) comm := op.commutativeSets(f, e)
for _, exprs := range comm { for _, exprs := range comm {
op.checkRedundant(f, exprs) op.checkRedundant(f, exprs)
op.checkSuspect(f, exprs) op.checkSuspect(f, exprs)
...@@ -53,14 +54,14 @@ var ( ...@@ -53,14 +54,14 @@ var (
// expressions in e that are connected by op. // expressions in e that are connected by op.
// For example, given 'a || b || f() || c || d' with the or op, // For example, given 'a || b || f() || c || d' with the or op,
// commutativeSets returns {{b, a}, {d, c}}. // commutativeSets returns {{b, a}, {d, c}}.
func (op boolOp) commutativeSets(e *ast.BinaryExpr) [][]ast.Expr { func (op boolOp) commutativeSets(f *File, e *ast.BinaryExpr) [][]ast.Expr {
exprs := op.split(e) exprs := op.split(e)
// Partition the slice of expressions into commutative sets. // Partition the slice of expressions into commutative sets.
i := 0 i := 0
var sets [][]ast.Expr var sets [][]ast.Expr
for j := 0; j <= len(exprs); j++ { for j := 0; j <= len(exprs); j++ {
if j == len(exprs) || hasSideEffects(exprs[j]) { if j == len(exprs) || hasSideEffects(f, exprs[j]) {
if i < j { if i < j {
sets = append(sets, exprs[i:j]) sets = append(sets, exprs[i:j])
} }
...@@ -136,16 +137,26 @@ func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) { ...@@ -136,16 +137,26 @@ func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) {
} }
// hasSideEffects reports whether evaluation of e has side effects. // hasSideEffects reports whether evaluation of e has side effects.
func hasSideEffects(e ast.Expr) bool { func hasSideEffects(f *File, e ast.Expr) bool {
safe := true safe := true
ast.Inspect(e, func(node ast.Node) bool { ast.Inspect(e, func(node ast.Node) bool {
switch n := node.(type) { switch n := node.(type) {
// Using CallExpr here will catch conversions
// as well as function and method invocations.
// We'll live with the false negatives for now.
case *ast.CallExpr: case *ast.CallExpr:
// Don't call Type.Underlying(), since its lack
// lets us see the NamedFuncType(x) type
// conversion as a *types.Named.
_, ok := f.pkg.types[n.Fun].Type.(*types.Signature)
if ok {
// Conservatively assume that all function and
// method calls have side effects for
// now. This will include func type
// conversions, but it's ok given that
// this is the conservative side.
safe = false safe = false
return false return false
}
// It's a type conversion, which cannot
// have side effects.
case *ast.UnaryExpr: case *ast.UnaryExpr:
if n.Op == token.ARROW { if n.Op == token.ARROW {
safe = false safe = false
......
...@@ -8,6 +8,10 @@ package testdata ...@@ -8,6 +8,10 @@ package testdata
import "io" import "io"
type T int
type FT func() int
func RatherStupidConditions() { func RatherStupidConditions() {
var f, g func() int var f, g func() int
if f() == 0 || f() == 0 { // OK f might have side effects if f() == 0 || f() == 0 { // OK f might have side effects
...@@ -16,7 +20,15 @@ func RatherStupidConditions() { ...@@ -16,7 +20,15 @@ func RatherStupidConditions() {
} }
_ = f == nil || f == nil // ERROR "redundant or: f == nil || f == nil" _ = f == nil || f == nil // ERROR "redundant or: f == nil || f == nil"
_ = i == byte(1) || i == byte(1) // TODO conversions are treated as if they may have side effects _ = i == byte(1) || i == byte(1) // ERROR "redundant or: i == byte(1) || i == byte(1)"
_ = i == T(2) || i == T(2) // ERROR "redundant or: i == T(2) || i == T(2)"
_ = FT(f) == nil || FT(f) == nil // ERROR "redundant or: FT(f) == nil || FT(f) == nil"
// TODO: distinguish from an actual func call
_ = (func() int)(f) == nil || (func() int)(f) == nil
var namedFuncVar FT
_ = namedFuncVar() == namedFuncVar() // OK; still func calls
var c chan int var c chan int
_ = 0 == <-c || 0 == <-c // OK subsequent receives may yield different values _ = 0 == <-c || 0 == <-c // OK subsequent receives may yield different values
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment