| // Copyright 2025 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 typesinternal | |
| import ( | |
| "go/ast" | |
| "go/token" | |
| "go/types" | |
| ) | |
| // NoEffects reports whether the expression has no side effects, i.e., it | |
| // does not modify the memory state. This function is conservative: it may | |
| // return false even when the expression has no effect. | |
| func NoEffects(info *types.Info, expr ast.Expr) bool { | |
| noEffects := true | |
| ast.Inspect(expr, func(n ast.Node) bool { | |
| switch v := n.(type) { | |
| case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr, | |
| *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr, | |
| *ast.StarExpr, *ast.CompositeLit, | |
| // non-expressions that may appear within expressions | |
| *ast.KeyValueExpr, | |
| *ast.FieldList, | |
| *ast.Field, | |
| *ast.Ellipsis, | |
| *ast.IndexListExpr: | |
| // No effect. | |
| case *ast.ArrayType, | |
| *ast.StructType, | |
| *ast.ChanType, | |
| *ast.FuncType, | |
| *ast.MapType, | |
| *ast.InterfaceType: | |
| // Type syntax: no effects, recursively. | |
| // Prune descent. | |
| return false | |
| case *ast.UnaryExpr: | |
| // Channel send <-ch has effects. | |
| if v.Op == token.ARROW { | |
| noEffects = false | |
| } | |
| case *ast.CallExpr: | |
| // Type conversion has no effects. | |
| if !info.Types[v.Fun].IsType() { | |
| if CallsPureBuiltin(info, v) { | |
| // A call such as len(e) has no effects of its | |
| // own, though the subexpression e might. | |
| } else { | |
| noEffects = false | |
| } | |
| } | |
| case *ast.FuncLit: | |
| // A FuncLit has no effects, but do not descend into it. | |
| return false | |
| default: | |
| // All other expressions have effects | |
| noEffects = false | |
| } | |
| return noEffects | |
| }) | |
| return noEffects | |
| } | |
| // CallsPureBuiltin reports whether call is a call of a built-in | |
| // function that is a pure computation over its operands (analogous to | |
| // a + operator). Because it does not depend on program state, it may | |
| // be evaluated at any point--though not necessarily at multiple | |
| // points (consider new, make). | |
| func CallsPureBuiltin(info *types.Info, call *ast.CallExpr) bool { | |
| if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok { | |
| if b, ok := info.ObjectOf(id).(*types.Builtin); ok { | |
| switch b.Name() { | |
| case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min": | |
| return true | |
| } | |
| // Not: append clear close copy delete panic print println recover | |
| } | |
| } | |
| return false | |
| } |