func CouldBeMutated(info *types.Info, body ast.Node, dst ast.Expr) bool {
	if IsImmutable(info, dst) {  
		return false
	}
	if typep.IsPointer(info.TypeOf(dst)) {
		return true
	}
	var isDst func(x ast.Expr) bool
	if dst, ok := dst.(*ast.Ident); ok {
		obj := info.ObjectOf(dst)
		if obj == nil {
			return true  
		}
		isDst = func(x ast.Expr) bool {
			id, ok := x.(*ast.Ident)
			return ok && id.Name == dst.Name && info.ObjectOf(id) == obj
		}
	} else {
		isDst = func(x ast.Expr) bool {
			return astequal.Expr(dst, x)
		}
	}
	return ContainsNode(body, func(n ast.Node) bool {
		switch n := n.(type) {
		case *ast.UnaryExpr:
			if n.Op == token.AND && isDst(n.X) {
				return true  
			}
		case *ast.AssignStmt:
			for _, lhs := range n.Lhs {
				if isDst(lhs) {
					return true
				}
			}
		case *ast.IncDecStmt:
			return isDst(n.X)
		}
		return false
	})
}
CouldBeMutated reports whether dst can be modified inside body.

Note that it does not take already existing pointers to dst.
An example of safe and correct usage is checking of something
that was just defined, so the dst is a result of that definition.
package lintutil
import (
	"go/ast"
	"go/token"
	"go/types"
	"github.com/go-toolsmith/astequal"
	"github.com/go-toolsmith/astp"
	"github.com/go-toolsmith/typep"
)
func IsImmutable(info *types.Info, n ast.Expr) bool {
	if astp.IsBasicLit(n) {
		return true
	}
	tv, ok := info.Types[n]
	return ok && !tv.Assignable() && !tv.Addressable()
}