9
9
"go/ast"
10
10
"go/token"
11
11
"go/types"
12
- "os"
13
- "strings"
12
+ "slices"
14
13
15
14
"golang.org/x/tools/go/analysis"
16
15
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -20,28 +19,26 @@ import (
20
19
"golang.org/x/tools/internal/refactor/inline"
21
20
)
22
21
23
- const Doc = `inline calls to functions with "inlineme " doc comment`
22
+ const Doc = `inline calls to functions with "//go:fix inline " doc comment`
24
23
25
24
var Analyzer = & analysis.Analyzer {
26
25
Name : "inline" ,
27
26
Doc : Doc ,
28
27
URL : "https://pkg.go.dev/golang.org/x/tools/internal/refactor/inline/analyzer" ,
29
28
Run : run ,
30
- FactTypes : []analysis.Fact {new (inlineMeFact )},
29
+ FactTypes : []analysis.Fact {new (goFixInlineFact )},
31
30
Requires : []* analysis.Analyzer {inspect .Analyzer },
32
31
}
33
32
34
- func run (pass * analysis.Pass ) (interface {} , error ) {
33
+ func run (pass * analysis.Pass ) (any , error ) {
35
34
// Memoize repeated calls for same file.
36
- // TODO(adonovan): the analysis.Pass should abstract this (#62292)
37
- // as the driver may not be reading directly from the file system.
38
35
fileContent := make (map [string ][]byte )
39
36
readFile := func (node ast.Node ) ([]byte , error ) {
40
37
filename := pass .Fset .File (node .Pos ()).Name ()
41
38
content , ok := fileContent [filename ]
42
39
if ! ok {
43
40
var err error
44
- content , err = os .ReadFile (filename )
41
+ content , err = pass .ReadFile (filename )
45
42
if err != nil {
46
43
return nil , err
47
44
}
@@ -50,40 +47,37 @@ func run(pass *analysis.Pass) (interface{}, error) {
50
47
return content , nil
51
48
}
52
49
53
- // Pass 1: find functions annotated with an "inlineme"
54
- // comment, and export a fact for each one.
50
+ // Pass 1: find functions annotated with a "//go:fix inline"
51
+ // comment (the syntax proposed by #32816),
52
+ // and export a fact for each one.
55
53
inlinable := make (map [* types.Func ]* inline.Callee ) // memoization of fact import (nil => no fact)
56
54
for _ , file := range pass .Files {
57
55
for _ , decl := range file .Decls {
58
- if decl , ok := decl .(* ast.FuncDecl ); ok {
59
- // TODO(adonovan): this is just a placeholder.
60
- // Use the precise go:fix syntax in the proposal.
61
- // Beware that //go: comments are treated specially
62
- // by (*ast.CommentGroup).Text().
63
- // TODO(adonovan): alternatively, consider using
64
- // the universal annotation mechanism sketched in
65
- // https://go.dev/cl/489835 (which doesn't yet have
66
- // a proper proposal).
67
- if strings .Contains (decl .Doc .Text (), "inlineme" ) {
68
- content , err := readFile (file )
69
- if err != nil {
70
- pass .Reportf (decl .Doc .Pos (), "invalid inlining candidate: cannot read source file: %v" , err )
71
- continue
72
- }
73
- callee , err := inline .AnalyzeCallee (discard , pass .Fset , pass .Pkg , pass .TypesInfo , decl , content )
74
- if err != nil {
75
- pass .Reportf (decl .Doc .Pos (), "invalid inlining candidate: %v" , err )
76
- continue
77
- }
78
- fn := pass .TypesInfo .Defs [decl .Name ].(* types.Func )
79
- pass .ExportObjectFact (fn , & inlineMeFact {callee })
80
- inlinable [fn ] = callee
56
+ if decl , ok := decl .(* ast.FuncDecl ); ok &&
57
+ slices .ContainsFunc (directives (decl .Doc ), func (d * directive ) bool {
58
+ return d .Tool == "go" && d .Name == "fix" && d .Args == "inline"
59
+ }) {
60
+
61
+ content , err := readFile (decl )
62
+ if err != nil {
63
+ pass .Reportf (decl .Doc .Pos (), "invalid inlining candidate: cannot read source file: %v" , err )
64
+ continue
65
+ }
66
+ callee , err := inline .AnalyzeCallee (discard , pass .Fset , pass .Pkg , pass .TypesInfo , decl , content )
67
+ if err != nil {
68
+ pass .Reportf (decl .Doc .Pos (), "invalid inlining candidate: %v" , err )
69
+ continue
81
70
}
71
+ fn := pass .TypesInfo .Defs [decl .Name ].(* types.Func )
72
+ pass .ExportObjectFact (fn , & goFixInlineFact {callee })
73
+ inlinable [fn ] = callee
82
74
}
83
75
}
84
76
}
85
77
86
78
// Pass 2. Inline each static call to an inlinable function.
79
+ //
80
+ // TODO(adonovan): handle multiple diffs that each add the same import.
87
81
inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
88
82
nodeFilter := []ast.Node {
89
83
(* ast .File )(nil ),
@@ -100,7 +94,7 @@ func run(pass *analysis.Pass) (interface{}, error) {
100
94
// Inlinable?
101
95
callee , ok := inlinable [fn ]
102
96
if ! ok {
103
- var fact inlineMeFact
97
+ var fact goFixInlineFact
104
98
if pass .ImportObjectFact (fn , & fact ) {
105
99
callee = fact .Callee
106
100
inlinable [fn ] = callee
@@ -129,6 +123,16 @@ func run(pass *analysis.Pass) (interface{}, error) {
129
123
pass .Reportf (call .Lparen , "%v" , err )
130
124
return
131
125
}
126
+ if res .Literalized {
127
+ // Users are not fond of inlinings that literalize
128
+ // f(x) to func() { ... }(), so avoid them.
129
+ //
130
+ // (Unfortunately the inliner is very timid,
131
+ // and often literalizes when it cannot prove that
132
+ // reducing the call is safe; the user of this tool
133
+ // has no indication of what the problem is.)
134
+ return
135
+ }
132
136
got := res .Content
133
137
134
138
// Suggest the "fix".
@@ -156,9 +160,11 @@ func run(pass *analysis.Pass) (interface{}, error) {
156
160
return nil , nil
157
161
}
158
162
159
- type inlineMeFact struct { Callee * inline.Callee }
163
+ // A goFixInlineFact is exported for each function marked "//go:fix inline".
164
+ // It holds information about the callee to support inlining.
165
+ type goFixInlineFact struct { Callee * inline.Callee }
160
166
161
- func (f * inlineMeFact ) String () string { return "inlineme " + f .Callee .String () }
162
- func (* inlineMeFact ) AFact () {}
167
+ func (f * goFixInlineFact ) String () string { return "goFixInline " + f .Callee .String () }
168
+ func (* goFixInlineFact ) AFact () {}
163
169
164
170
func discard (string , ... any ) {}
0 commit comments