7 "golang.org/x/tools/go/analysis"
8 "honnef.co/go/tools/functions"
9 "honnef.co/go/tools/internal/passes/buildir"
10 "honnef.co/go/tools/ir"
15 func (*IsPure) AFact() {}
16 func (d *IsPure) String() string { return "is pure" }
18 type PurityResult map[*types.Func]*IsPure
20 var Purity = &analysis.Analyzer{
22 Doc: "Mark pure functions",
24 Requires: []*analysis.Analyzer{buildir.Analyzer},
25 FactTypes: []analysis.Fact{(*IsPure)(nil)},
26 ResultType: reflect.TypeOf(PurityResult{}),
29 var pureStdlib = map[string]struct{}{
37 "strings.Replace": {},
39 "strings.ToLower": {},
40 "strings.ToLowerSpecial": {},
41 "strings.ToTitle": {},
42 "strings.ToTitleSpecial": {},
43 "strings.ToUpper": {},
44 "strings.ToUpperSpecial": {},
46 "strings.TrimFunc": {},
47 "strings.TrimLeft": {},
48 "strings.TrimLeftFunc": {},
49 "strings.TrimPrefix": {},
50 "strings.TrimRight": {},
51 "strings.TrimRightFunc": {},
52 "strings.TrimSpace": {},
53 "strings.TrimSuffix": {},
54 "(*net/http.Request).WithContext": {},
57 func purity(pass *analysis.Pass) (interface{}, error) {
58 seen := map[*ir.Function]struct{}{}
59 irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
60 var check func(fn *ir.Function) (ret bool)
61 check = func(fn *ir.Function) (ret bool) {
62 if fn.Object() == nil {
63 // TODO(dh): support closures
66 if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
70 // Function is in another package but wasn't marked as
71 // pure, ergo it isn't pure
75 if _, ok := seen[fn]; ok {
82 pass.ExportObjectFact(fn.Object(), &IsPure{})
86 if functions.IsStub(fn) {
90 if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
94 if fn.Signature.Results().Len() == 0 {
95 // A function with no return values is empty or is doing some
96 // work we cannot see (for example because of build tags);
97 // don't consider it pure.
101 for _, param := range fn.Params {
102 // TODO(dh): this may not be strictly correct. pure code
103 // can, to an extent, operate on non-basic types.
104 if _, ok := param.Type().Underlying().(*types.Basic); !ok {
109 // Don't consider external functions pure.
110 if fn.Blocks == nil {
113 checkCall := func(common *ir.CallCommon) bool {
114 if common.IsInvoke() {
117 builtin, ok := common.Value.(*ir.Builtin)
119 if common.StaticCallee() != fn {
120 if common.StaticCallee() == nil {
123 if !check(common.StaticCallee()) {
128 switch builtin.Name() {
136 for _, b := range fn.Blocks {
137 for _, ins := range b.Instrs {
138 switch ins := ins.(type) {
140 if !checkCall(ins.Common()) {
144 if !checkCall(&ins.Call) {
168 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
172 out := PurityResult{}
173 for _, fact := range pass.AllObjectFacts() {
174 out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)