@@ -53,86 +53,85 @@ func run(pass *analysis.Pass) (any, error) {
53
53
// and export a fact for each one.
54
54
inlinableFuncs := make (map [* types.Func ]* inline.Callee ) // memoization of fact import (nil => no fact)
55
55
inlinableConsts := make (map [* types.Const ]* goFixInlineConstFact )
56
- for _ , file := range pass .Files {
57
- for _ , decl := range file .Decls {
58
- switch decl := decl .(type ) {
59
- case * ast.FuncDecl :
60
- if hasInlineDirective (decl .Doc ) {
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
70
- }
71
- fn := pass .TypesInfo .Defs [decl .Name ].(* types.Func )
72
- pass .ExportObjectFact (fn , & goFixInlineFuncFact {callee })
73
- inlinableFuncs [fn ] = callee
74
- }
75
56
76
- case * ast.GenDecl :
77
- if decl .Tok != token .CONST {
78
- continue
57
+ inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
58
+ nodeFilter := []ast.Node {(* ast .FuncDecl )(nil ), (* ast .GenDecl )(nil )}
59
+ inspect .Preorder (nodeFilter , func (n ast.Node ) {
60
+ switch decl := n .(type ) {
61
+ case * ast.FuncDecl :
62
+ if hasInlineDirective (decl .Doc ) {
63
+ content , err := readFile (decl )
64
+ if err != nil {
65
+ pass .Reportf (decl .Doc .Pos (), "invalid inlining candidate: cannot read source file: %v" , err )
66
+ return
79
67
}
80
- // Accept inline directives on the entire decl as well as individual specs.
81
- declInline := hasInlineDirective (decl .Doc )
82
- for _ , spec := range decl .Specs {
83
- spec := spec .(* ast.ValueSpec ) // guaranteed by Tok == CONST
84
- if declInline || hasInlineDirective (spec .Doc ) {
85
- for i , name := range spec .Names {
86
- if i >= len (spec .Values ) {
87
- // Possible following an iota.
88
- break
89
- }
90
- val := spec .Values [i ]
91
- var rhsID * ast.Ident
92
- switch e := val .(type ) {
93
- case * ast.Ident :
94
- if e .Name == "iota" {
95
- continue
96
- }
97
- rhsID = e
98
- case * ast.SelectorExpr :
99
- rhsID = e .Sel
100
- default :
101
- pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is not the name of another constant" )
68
+ callee , err := inline .AnalyzeCallee (discard , pass .Fset , pass .Pkg , pass .TypesInfo , decl , content )
69
+ if err != nil {
70
+ pass .Reportf (decl .Doc .Pos (), "invalid inlining candidate: %v" , err )
71
+ return
72
+ }
73
+ fn := pass .TypesInfo .Defs [decl .Name ].(* types.Func )
74
+ pass .ExportObjectFact (fn , & goFixInlineFuncFact {callee })
75
+ inlinableFuncs [fn ] = callee
76
+ }
77
+
78
+ case * ast.GenDecl :
79
+ if decl .Tok != token .CONST {
80
+ return
81
+ }
82
+ // Accept inline directives on the entire decl as well as individual specs.
83
+ declInline := hasInlineDirective (decl .Doc )
84
+ for _ , spec := range decl .Specs {
85
+ spec := spec .(* ast.ValueSpec ) // guaranteed by Tok == CONST
86
+ if declInline || hasInlineDirective (spec .Doc ) {
87
+ for i , name := range spec .Names {
88
+ if i >= len (spec .Values ) {
89
+ // Possible following an iota.
90
+ break
91
+ }
92
+ val := spec .Values [i ]
93
+ var rhsID * ast.Ident
94
+ switch e := val .(type ) {
95
+ case * ast.Ident :
96
+ // Constants defined with the predeclared iota cannot be inlined.
97
+ if pass .TypesInfo .Uses [e ] == builtinIota {
98
+ pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is iota" )
102
99
continue
103
100
}
104
- lhs := pass .TypesInfo .Defs [name ].(* types.Const )
105
- rhs := pass .TypesInfo .Uses [rhsID ].(* types.Const ) // must be so in a well-typed program
106
- con := & goFixInlineConstFact {
107
- RHSName : rhs .Name (),
108
- RHSPkgPath : rhs .Pkg ().Path (),
109
- }
110
- inlinableConsts [lhs ] = con
111
- // Create a fact only if the LHS is exported and defined at top level.
112
- // We create a fact even if the RHS is non-exported,
113
- // so we can warn about uses in other packages.
114
- if lhs .Exported () && typesinternal .IsPackageLevel (lhs ) {
115
- pass .ExportObjectFact (lhs , con )
116
- }
101
+ rhsID = e
102
+ case * ast.SelectorExpr :
103
+ rhsID = e .Sel
104
+ default :
105
+ pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is not the name of another constant" )
106
+ continue
107
+ }
108
+ lhs := pass .TypesInfo .Defs [name ].(* types.Const )
109
+ rhs := pass .TypesInfo .Uses [rhsID ].(* types.Const ) // must be so in a well-typed program
110
+ con := & goFixInlineConstFact {
111
+ RHSName : rhs .Name (),
112
+ RHSPkgPath : rhs .Pkg ().Path (),
113
+ }
114
+ if rhs .Pkg () == pass .Pkg {
115
+ con .rhsObj = rhs
116
+ }
117
+ inlinableConsts [lhs ] = con
118
+ // Create a fact only if the LHS is exported and defined at top level.
119
+ // We create a fact even if the RHS is non-exported,
120
+ // so we can warn uses in other packages.
121
+ if lhs .Exported () && typesinternal .IsPackageLevel (lhs ) {
122
+ pass .ExportObjectFact (lhs , con )
117
123
}
118
124
}
119
125
}
120
- // TODO(jba): in user doc, warn that a comments within a spec, as in
121
- // const a,
122
- // //go:fix inline
123
- // b = 1, 2
124
- // will go unnoticed.
125
- // (They appear only in File.Comments, and it doesn't seem worthwhile to wade through those.)
126
126
}
127
127
}
128
- }
128
+ })
129
129
130
130
// Pass 2. Inline each static call to an inlinable function,
131
131
// and each reference to an inlinable constant.
132
132
//
133
133
// TODO(adonovan): handle multiple diffs that each add the same import.
134
- inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
135
- nodeFilter := []ast.Node {
134
+ nodeFilter = []ast.Node {
136
135
(* ast .File )(nil ),
137
136
(* ast .CallExpr )(nil ),
138
137
(* ast .Ident )(nil ),
@@ -218,7 +217,6 @@ func run(pass *analysis.Pass) (any, error) {
218
217
if con , ok := pass .TypesInfo .Uses [n ].(* types.Const ); ok {
219
218
incon , ok := inlinableConsts [con ]
220
219
if ! ok {
221
- // TODO(jba): call ImportObjectFact.
222
220
var fact goFixInlineConstFact
223
221
if pass .ImportObjectFact (con , & fact ) {
224
222
incon = & fact
@@ -228,9 +226,29 @@ func run(pass *analysis.Pass) (any, error) {
228
226
if incon == nil {
229
227
return // nope
230
228
}
229
+ //
231
230
// We have an identifier A here (n),
232
231
// and an inlinable "const A = B" elsewhere (incon).
233
- // Suggest replacing A with B.
232
+ // Consider replacing A with B.
233
+ // Check that the expression we are inlining (B) means the same thing
234
+ // (refers to the same object) in n's scope as it does in A's scope.
235
+ if incon .rhsObj != nil {
236
+ // Both expressions are in the current package.
237
+ // incon.rhsObj is the object referred to by B in the definition of A.
238
+ scope := pass .TypesInfo .Scopes [currentFile ].Innermost (n .Pos ()) // n's scope
239
+ _ , obj := scope .LookupParent (incon .RHSName , n .Pos ()) // what "B" means in n's scope
240
+ if obj == nil {
241
+ // Should be impossible: if code at n can refer to the LHS,
242
+ // it can refer to the RHS.
243
+ panic (fmt .Sprintf ("no object for inlinable const %s RHS %s" , n .Name , incon .RHSName ))
244
+ }
245
+ if obj != incon .rhsObj {
246
+ // "B" means something different here than at the inlinable const's scope
247
+ return
248
+ }
249
+ } else {
250
+ // TODO(jba): handle the cross-package case by checking the package ID.
251
+ }
234
252
importPrefix := ""
235
253
if incon .RHSPkgPath != con .Pkg ().Path () {
236
254
importID := maybeAddImportPath (currentFile , incon .RHSPkgPath )
@@ -266,8 +284,7 @@ func hasInlineDirective(cg *ast.CommentGroup) bool {
266
284
}
267
285
268
286
func maybeAddImportPath (f * ast.File , path string ) string {
269
- // TODO(jba): implement this in terms of existing functions.
270
- // TODO(adonovan): tell jba which functions.
287
+ // TODO(jba): implement this in terms of analysisinternal.AddImport(info, file, pos, path, localname).
271
288
return "unimp"
272
289
}
273
290
@@ -278,4 +295,21 @@ type goFixInlineFuncFact struct{ Callee *inline.Callee }
278
295
func (f * goFixInlineFuncFact ) String () string { return "goFixInline " + f .Callee .String () }
279
296
func (* goFixInlineFuncFact ) AFact () {}
280
297
298
+ // A goFixInlineConstFact is exported for each constant marked "//go:fix inline".
299
+ // It holds information about an inlinable constant. Gob-serializable.
300
+ type goFixInlineConstFact struct {
301
+ // Information about "const LHSName = RHSName".
302
+ RHSName string
303
+ RHSPkgPath string
304
+ rhsObj types.Object // for current package
305
+ }
306
+
307
+ func (c * goFixInlineConstFact ) String () string {
308
+ return fmt .Sprintf ("goFixInline const %q.%s" , c .RHSPkgPath , c .RHSName )
309
+ }
310
+
311
+ func (* goFixInlineConstFact ) AFact () {}
312
+
281
313
func discard (string , ... any ) {}
314
+
315
+ var builtinIota = types .Universe .Lookup ("iota" )
0 commit comments