Skip to content

Commit 47123ba

Browse files
adonovangopherbot
authored andcommitted
internal/typesinternal: merge HasSideEffects -> NoEffects
Apparently we wrote this function the same way twice. Also: - add a couple of missing cases (FuncType, ChanType). - implement the CallsPureBuiltin optimization, moved from internal/refactor/inline. Change-Id: I832c710784b7df211d4542c36e3696e97257b8be Reviewed-on: https://go-review.googlesource.com/c/tools/+/711323 Auto-Submit: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Robert Findley <rfindley@google.com>
1 parent 95e71b9 commit 47123ba

File tree

9 files changed

+66
-77
lines changed

9 files changed

+66
-77
lines changed

go/analysis/passes/assign/assign.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"golang.org/x/tools/go/ast/inspector"
2222
"golang.org/x/tools/internal/astutil"
2323
"golang.org/x/tools/internal/refactor"
24+
"golang.org/x/tools/internal/typesinternal"
2425
)
2526

2627
//go:embed doc.go
@@ -62,8 +63,8 @@ func run(pass *analysis.Pass) (any, error) {
6263
isSelfAssign := false
6364
var le string
6465

65-
if !analysisutil.HasSideEffects(info, lhs) &&
66-
!analysisutil.HasSideEffects(info, rhs) &&
66+
if typesinternal.NoEffects(info, lhs) &&
67+
typesinternal.NoEffects(info, rhs) &&
6768
!isMapIndex(info, lhs) &&
6869
reflect.TypeOf(lhs) == reflect.TypeOf(rhs) { // short-circuit the heavy-weight gofmt check
6970

go/analysis/passes/bools/bools.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import (
1313

1414
"golang.org/x/tools/go/analysis"
1515
"golang.org/x/tools/go/analysis/passes/inspect"
16-
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
1716
"golang.org/x/tools/go/ast/inspector"
1817
"golang.org/x/tools/internal/astutil"
18+
"golang.org/x/tools/internal/typesinternal"
1919
)
2020

2121
const Doc = "check for common mistakes involving boolean operators"
@@ -84,7 +84,7 @@ func (op boolOp) commutativeSets(info *types.Info, e *ast.BinaryExpr, seen map[*
8484
i := 0
8585
var sets [][]ast.Expr
8686
for j := 0; j <= len(exprs); j++ {
87-
if j == len(exprs) || analysisutil.HasSideEffects(info, exprs[j]) {
87+
if j == len(exprs) || !typesinternal.NoEffects(info, exprs[j]) {
8888
if i < j {
8989
sets = append(sets, exprs[i:j])
9090
}

go/analysis/passes/internal/analysisutil/util.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,13 @@
77
package analysisutil
88

99
import (
10-
"go/ast"
1110
"go/token"
12-
"go/types"
1311
"os"
1412

1513
"golang.org/x/tools/go/analysis"
1614
"golang.org/x/tools/internal/analysisinternal"
1715
)
1816

19-
// HasSideEffects reports whether evaluation of e has side effects.
20-
func HasSideEffects(info *types.Info, e ast.Expr) bool {
21-
safe := true
22-
ast.Inspect(e, func(node ast.Node) bool {
23-
switch n := node.(type) {
24-
case *ast.CallExpr:
25-
typVal := info.Types[n.Fun]
26-
switch {
27-
case typVal.IsType():
28-
// Type conversion, which is safe.
29-
case typVal.IsBuiltin():
30-
// Builtin func, conservatively assumed to not
31-
// be safe for now.
32-
safe = false
33-
return false
34-
default:
35-
// A non-builtin func or method call.
36-
// Conservatively assume that all of them have
37-
// side effects for now.
38-
safe = false
39-
return false
40-
}
41-
case *ast.UnaryExpr:
42-
if n.Op == token.ARROW {
43-
safe = false
44-
return false
45-
}
46-
}
47-
return true
48-
})
49-
return !safe
50-
}
51-
5217
// ReadFile reads a file and adds it to the FileSet
5318
// so that we can report errors against it using lineStart.
5419
func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {

go/analysis/passes/modernize/testdata/src/reflecttypefor/reflecttypefor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var (
1515
_ = reflect.TypeOf((*error)(nil)) // want "reflect.TypeOf call can be simplified using TypeFor"
1616
_ = reflect.TypeOf(io.Reader(nil)) // nope (likely a mistake)
1717
_ = reflect.TypeOf((*io.Reader)(nil)) // want "reflect.TypeOf call can be simplified using TypeFor"
18-
_ = reflect.TypeOf(*new(time.Time)) // nope (false negative of noEffects)
18+
_ = reflect.TypeOf(*new(time.Time)) // want "reflect.TypeOf call can be simplified using TypeFor"
1919
_ = reflect.TypeOf(time.Time{}) // want "reflect.TypeOf call can be simplified using TypeFor"
2020
_ = reflect.TypeOf(time.Duration(0)) // want "reflect.TypeOf call can be simplified using TypeFor"
2121
)

go/analysis/passes/modernize/testdata/src/reflecttypefor/reflecttypefor.go.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var (
1515
_ = reflect.TypeFor[*error]() // want "reflect.TypeOf call can be simplified using TypeFor"
1616
_ = reflect.TypeOf(io.Reader(nil)) // nope (likely a mistake)
1717
_ = reflect.TypeFor[*io.Reader]() // want "reflect.TypeOf call can be simplified using TypeFor"
18-
_ = reflect.TypeOf(*new(time.Time)) // nope (false negative of noEffects)
18+
_ = reflect.TypeFor[time.Time]() // want "reflect.TypeOf call can be simplified using TypeFor"
1919
_ = reflect.TypeFor[time.Time]() // want "reflect.TypeOf call can be simplified using TypeFor"
2020
_ = reflect.TypeFor[time.Duration]() // want "reflect.TypeOf call can be simplified using TypeFor"
2121
)

internal/refactor/inline/calleefx.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"go/ast"
1111
"go/token"
1212
"go/types"
13+
14+
"golang.org/x/tools/internal/typesinternal"
1315
)
1416

1517
const (
@@ -180,7 +182,7 @@ func calleefx(info *types.Info, body *ast.BlockStmt, paramInfos map[*types.Var]*
180182
// The pure built-ins have no effects beyond
181183
// those of their operands (not even memory reads).
182184
// All other calls have unknown effects.
183-
if !callsPureBuiltin(info, n) {
185+
if !typesinternal.CallsPureBuiltin(info, n) {
184186
unknown() // arbitrary effects
185187
}
186188
}

internal/refactor/inline/inline.go

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2539,7 +2539,7 @@ func (st *state) effects(info *types.Info, expr ast.Expr) bool {
25392539
case *ast.CallExpr:
25402540
if info.Types[n.Fun].IsType() {
25412541
// A conversion T(x) has only the effect of its operand.
2542-
} else if !callsPureBuiltin(info, n) {
2542+
} else if !typesinternal.CallsPureBuiltin(info, n) {
25432543
// A handful of built-ins have no effect
25442544
// beyond those of their arguments.
25452545
// All other calls (including append, copy, recover)
@@ -2635,7 +2635,7 @@ func pure(info *types.Info, assign1 func(*types.Var) bool, e ast.Expr) bool {
26352635
}
26362636

26372637
// Calls to some built-ins are as pure as their arguments.
2638-
if callsPureBuiltin(info, e) {
2638+
if typesinternal.CallsPureBuiltin(info, e) {
26392639
for _, arg := range e.Args {
26402640
if !pure(arg) {
26412641
return false
@@ -2712,24 +2712,6 @@ func pure(info *types.Info, assign1 func(*types.Var) bool, e ast.Expr) bool {
27122712
return pure(e)
27132713
}
27142714

2715-
// callsPureBuiltin reports whether call is a call of a built-in
2716-
// function that is a pure computation over its operands (analogous to
2717-
// a + operator). Because it does not depend on program state, it may
2718-
// be evaluated at any point--though not necessarily at multiple
2719-
// points (consider new, make).
2720-
func callsPureBuiltin(info *types.Info, call *ast.CallExpr) bool {
2721-
if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok {
2722-
if b, ok := info.ObjectOf(id).(*types.Builtin); ok {
2723-
switch b.Name() {
2724-
case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min":
2725-
return true
2726-
}
2727-
// Not: append clear close copy delete panic print println recover
2728-
}
2729-
}
2730-
return false
2731-
}
2732-
27332715
// duplicable reports whether it is appropriate for the expression to
27342716
// be freely duplicated.
27352717
//

internal/typesinternal/fx.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,46 @@ func NoEffects(info *types.Info, expr ast.Expr) bool {
1919
switch v := n.(type) {
2020
case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr,
2121
*ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr,
22-
*ast.StarExpr, *ast.CompositeLit, *ast.ArrayType, *ast.StructType,
23-
*ast.MapType, *ast.InterfaceType, *ast.KeyValueExpr:
24-
// No effect
22+
*ast.StarExpr, *ast.CompositeLit,
23+
// non-expressions that may appear within expressions
24+
*ast.KeyValueExpr,
25+
*ast.FieldList,
26+
*ast.Field,
27+
*ast.Ellipsis,
28+
*ast.IndexListExpr:
29+
// No effect.
30+
31+
case *ast.ArrayType,
32+
*ast.StructType,
33+
*ast.ChanType,
34+
*ast.FuncType,
35+
*ast.MapType,
36+
*ast.InterfaceType:
37+
// Type syntax: no effects, recursively.
38+
// Prune descent.
39+
return false
40+
2541
case *ast.UnaryExpr:
26-
// Channel send <-ch has effects
42+
// Channel send <-ch has effects.
2743
if v.Op == token.ARROW {
2844
noEffects = false
2945
}
46+
3047
case *ast.CallExpr:
31-
// Type conversion has no effects
48+
// Type conversion has no effects.
3249
if !info.Types[v.Fun].IsType() {
33-
// TODO(adonovan): Add a case for built-in functions without side
34-
// effects (by using callsPureBuiltin from tools/internal/refactor/inline)
35-
36-
noEffects = false
50+
if CallsPureBuiltin(info, v) {
51+
// A call such as len(e) has no effects of its
52+
// own, though the subexpression e might.
53+
} else {
54+
noEffects = false
55+
}
3756
}
57+
3858
case *ast.FuncLit:
3959
// A FuncLit has no effects, but do not descend into it.
4060
return false
61+
4162
default:
4263
// All other expressions have effects
4364
noEffects = false
@@ -47,3 +68,21 @@ func NoEffects(info *types.Info, expr ast.Expr) bool {
4768
})
4869
return noEffects
4970
}
71+
72+
// CallsPureBuiltin reports whether call is a call of a built-in
73+
// function that is a pure computation over its operands (analogous to
74+
// a + operator). Because it does not depend on program state, it may
75+
// be evaluated at any point--though not necessarily at multiple
76+
// points (consider new, make).
77+
func CallsPureBuiltin(info *types.Info, call *ast.CallExpr) bool {
78+
if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok {
79+
if b, ok := info.ObjectOf(id).(*types.Builtin); ok {
80+
switch b.Name() {
81+
case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min":
82+
return true
83+
}
84+
// Not: append clear close copy delete panic print println recover
85+
}
86+
}
87+
return false
88+
}

go/analysis/passes/internal/analysisutil/util_test.go renamed to internal/typesinternal/fx_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// Copyright 2021 The Go Authors. All rights reserved.
1+
// Copyright 2025 The Go Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
package analysisutil_test
5+
package typesinternal_test
66

77
import (
88
"go/ast"
@@ -11,10 +11,10 @@ import (
1111
"go/types"
1212
"testing"
1313

14-
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
14+
"golang.org/x/tools/internal/typesinternal"
1515
)
1616

17-
func TestHasSideEffects(t *testing.T) {
17+
func TestNoEffects(t *testing.T) {
1818
src := `package p
1919
2020
type T int
@@ -45,8 +45,8 @@ func _() {
4545
if !ok {
4646
return true
4747
}
48-
if got := analysisutil.HasSideEffects(info, call); got != false {
49-
t.Errorf("HasSideEffects(%s) = true, want false", types.ExprString(call))
48+
if !typesinternal.NoEffects(info, call) {
49+
t.Errorf("NoEffects(%s) = false", types.ExprString(call))
5050
}
5151
return true
5252
})

0 commit comments

Comments
 (0)