7 "honnef.co/go/tools/go/ir"
8 "honnef.co/go/tools/go/ir/irutil"
9 "honnef.co/go/tools/internal/passes/buildir"
11 "golang.org/x/tools/go/analysis"
16 func (*IsPure) AFact() {}
17 func (d *IsPure) String() string { return "is pure" }
19 type PurityResult map[*types.Func]*IsPure
21 var Purity = &analysis.Analyzer{
23 Doc: "Mark pure functions",
25 Requires: []*analysis.Analyzer{buildir.Analyzer},
26 FactTypes: []analysis.Fact{(*IsPure)(nil)},
27 ResultType: reflect.TypeOf(PurityResult{}),
30 var pureStdlib = map[string]struct{}{
38 "strings.Replace": {},
40 "strings.ToLower": {},
41 "strings.ToLowerSpecial": {},
42 "strings.ToTitle": {},
43 "strings.ToTitleSpecial": {},
44 "strings.ToUpper": {},
45 "strings.ToUpperSpecial": {},
47 "strings.TrimFunc": {},
48 "strings.TrimLeft": {},
49 "strings.TrimLeftFunc": {},
50 "strings.TrimPrefix": {},
51 "strings.TrimRight": {},
52 "strings.TrimRightFunc": {},
53 "strings.TrimSpace": {},
54 "strings.TrimSuffix": {},
55 "(*net/http.Request).WithContext": {},
58 func purity(pass *analysis.Pass) (interface{}, error) {
59 seen := map[*ir.Function]struct{}{}
60 irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
61 var check func(fn *ir.Function) (ret bool)
62 check = func(fn *ir.Function) (ret bool) {
63 if fn.Object() == nil {
64 // TODO(dh): support closures
67 if pass.ImportObjectFact(fn.Object(), new(IsPure)) {
71 // Function is in another package but wasn't marked as
72 // pure, ergo it isn't pure
76 if _, ok := seen[fn]; ok {
83 pass.ExportObjectFact(fn.Object(), &IsPure{})
87 if irutil.IsStub(fn) {
91 if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok {
95 if fn.Signature.Results().Len() == 0 {
96 // A function with no return values is empty or is doing some
97 // work we cannot see (for example because of build tags);
98 // don't consider it pure.
102 for _, param := range fn.Params {
103 // TODO(dh): this may not be strictly correct. pure code
104 // can, to an extent, operate on non-basic types.
105 if _, ok := param.Type().Underlying().(*types.Basic); !ok {
110 // Don't consider external functions pure.
111 if fn.Blocks == nil {
114 checkCall := func(common *ir.CallCommon) bool {
115 if common.IsInvoke() {
118 builtin, ok := common.Value.(*ir.Builtin)
120 if common.StaticCallee() != fn {
121 if common.StaticCallee() == nil {
124 if !check(common.StaticCallee()) {
129 switch builtin.Name() {
137 for _, b := range fn.Blocks {
138 for _, ins := range b.Instrs {
139 switch ins := ins.(type) {
141 if !checkCall(ins.Common()) {
145 if !checkCall(&ins.Call) {
169 for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
173 out := PurityResult{}
174 for _, fact := range pass.AllObjectFacts() {
175 out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure)