@@ -63,8 +63,11 @@ func run(pass *analysis.Pass) (any, error) {
63
63
// Pass 1: find functions and constants annotated with an appropriate "//go:fix"
64
64
// comment (the syntax proposed by #32816),
65
65
// and export a fact for each one.
66
- inlinableFuncs := make (map [* types.Func ]* inline.Callee ) // memoization of fact import (nil => no fact)
67
- inlinableConsts := make (map [* types.Const ]* goFixInlineConstFact )
66
+ var (
67
+ inlinableFuncs = make (map [* types.Func ]* inline.Callee ) // memoization of fact import (nil => no fact)
68
+ inlinableConsts = make (map [* types.Const ]* goFixInlineConstFact )
69
+ inlinableAliases = make (map [* types.TypeName ]* goFixInlineAliasFact )
70
+ )
68
71
69
72
inspect := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
70
73
nodeFilter := []ast.Node {(* ast .FuncDecl )(nil ), (* ast .GenDecl )(nil )}
@@ -89,52 +92,96 @@ func run(pass *analysis.Pass) (any, error) {
89
92
inlinableFuncs [fn ] = callee
90
93
91
94
case * ast.GenDecl :
92
- if decl .Tok != token .CONST {
95
+ if decl .Tok != token .CONST && decl . Tok != token . TYPE {
93
96
return
94
97
}
95
98
declInline := hasFixInline (decl .Doc )
96
99
// Accept inline directives on the entire decl as well as individual specs.
97
100
for _ , spec := range decl .Specs {
98
- spec := spec .(* ast.ValueSpec ) // guaranteed by Tok == CONST
99
- specInline := hasFixInline (spec .Doc )
100
- if declInline || specInline {
101
- for i , name := range spec .Names {
102
- if i >= len (spec .Values ) {
103
- // Possible following an iota.
104
- break
105
- }
106
- val := spec .Values [i ]
107
- var rhsID * ast.Ident
108
- switch e := val .(type ) {
109
- case * ast.Ident :
110
- // Constants defined with the predeclared iota cannot be inlined.
111
- if pass .TypesInfo .Uses [e ] == builtinIota {
112
- pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is iota" )
101
+ switch spec := spec .(type ) {
102
+ case * ast.TypeSpec : // Tok == TYPE
103
+ if ! declInline && ! hasFixInline (spec .Doc ) {
104
+ continue
105
+ }
106
+ if ! spec .Assign .IsValid () {
107
+ pass .Reportf (spec .Pos (), "invalid //go:fix inline directive: not a type alias" )
108
+ continue
109
+ }
110
+ if spec .TypeParams != nil {
111
+ // TODO(jba): handle generic aliases
112
+ continue
113
+ }
114
+ // The alias must refer to another named type.
115
+ // TODO(jba): generalize to more type expressions.
116
+ var rhsID * ast.Ident
117
+ switch e := ast .Unparen (spec .Type ).(type ) {
118
+ case * ast.Ident :
119
+ rhsID = e
120
+ case * ast.SelectorExpr :
121
+ rhsID = e .Sel
122
+ default :
123
+ continue
124
+ }
125
+ lhs := pass .TypesInfo .Defs [spec .Name ].(* types.TypeName )
126
+ // more (jba): test one alias pointing to another alias
127
+ rhs := pass .TypesInfo .Uses [rhsID ].(* types.TypeName )
128
+ typ := & goFixInlineAliasFact {
129
+ RHSName : rhs .Name (),
130
+ RHSPkgName : rhs .Pkg ().Name (),
131
+ RHSPkgPath : rhs .Pkg ().Path (),
132
+ }
133
+ if rhs .Pkg () == pass .Pkg {
134
+ typ .rhsObj = rhs
135
+ }
136
+ inlinableAliases [lhs ] = typ
137
+ // Create a fact only if the LHS is exported and defined at top level.
138
+ // We create a fact even if the RHS is non-exported,
139
+ // so we can warn about uses in other packages.
140
+ if lhs .Exported () && typesinternal .IsPackageLevel (lhs ) {
141
+ pass .ExportObjectFact (lhs , typ )
142
+ }
143
+
144
+ case * ast.ValueSpec : // Tok == CONST
145
+ specInline := hasFixInline (spec .Doc )
146
+ if declInline || specInline {
147
+ for i , name := range spec .Names {
148
+ if i >= len (spec .Values ) {
149
+ // Possible following an iota.
150
+ break
151
+ }
152
+ val := spec .Values [i ]
153
+ var rhsID * ast.Ident
154
+ switch e := val .(type ) {
155
+ case * ast.Ident :
156
+ // Constants defined with the predeclared iota cannot be inlined.
157
+ if pass .TypesInfo .Uses [e ] == builtinIota {
158
+ pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is iota" )
159
+ continue
160
+ }
161
+ rhsID = e
162
+ case * ast.SelectorExpr :
163
+ rhsID = e .Sel
164
+ default :
165
+ pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is not the name of another constant" )
113
166
continue
114
167
}
115
- rhsID = e
116
- case * ast.SelectorExpr :
117
- rhsID = e .Sel
118
- default :
119
- pass .Reportf (val .Pos (), "invalid //go:fix inline directive: const value is not the name of another constant" )
120
- continue
121
- }
122
- lhs := pass .TypesInfo .Defs [name ].(* types.Const )
123
- rhs := pass .TypesInfo .Uses [rhsID ].(* types.Const ) // must be so in a well-typed program
124
- con := & goFixInlineConstFact {
125
- RHSName : rhs .Name (),
126
- RHSPkgName : rhs .Pkg ().Name (),
127
- RHSPkgPath : rhs .Pkg ().Path (),
128
- }
129
- if rhs .Pkg () == pass .Pkg {
130
- con .rhsObj = rhs
131
- }
132
- inlinableConsts [lhs ] = con
133
- // Create a fact only if the LHS is exported and defined at top level.
134
- // We create a fact even if the RHS is non-exported,
135
- // so we can warn uses in other packages.
136
- if lhs .Exported () && typesinternal .IsPackageLevel (lhs ) {
137
- pass .ExportObjectFact (lhs , con )
168
+ lhs := pass .TypesInfo .Defs [name ].(* types.Const )
169
+ rhs := pass .TypesInfo .Uses [rhsID ].(* types.Const ) // must be so in a well-typed program
170
+ con := & goFixInlineConstFact {
171
+ RHSName : rhs .Name (),
172
+ RHSPkgName : rhs .Pkg ().Name (),
173
+ RHSPkgPath : rhs .Pkg ().Path (),
174
+ }
175
+ if rhs .Pkg () == pass .Pkg {
176
+ con .rhsObj = rhs
177
+ }
178
+ inlinableConsts [lhs ] = con
179
+ // Create a fact only if the LHS is exported and defined at top level.
180
+ // We create a fact even if the RHS is non-exported,
181
+ // so we can warn about uses in other packages.
182
+ if lhs .Exported () && typesinternal .IsPackageLevel (lhs ) {
183
+ pass .ExportObjectFact (lhs , con )
184
+ }
138
185
}
139
186
}
140
187
}
@@ -143,7 +190,7 @@ func run(pass *analysis.Pass) (any, error) {
143
190
})
144
191
145
192
// Pass 2. Inline each static call to an inlinable function
146
- // and each reference to an inlinable constant.
193
+ // and each reference to an inlinable constant or type alias .
147
194
//
148
195
// TODO(adonovan): handle multiple diffs that each add the same import.
149
196
for cur := range cursor .Root (inspect ).Preorder ((* ast .CallExpr )(nil ), (* ast .Ident )(nil )) {
@@ -218,6 +265,65 @@ func run(pass *analysis.Pass) (any, error) {
218
265
}
219
266
220
267
case * ast.Ident :
268
+ // If the identifier is a use of an inlinable type alias, suggest inlining it.
269
+ // TODO(jba): much of this code is shared with the constant case, below.
270
+ // Try to factor more of it out, unless it will change anyway when we move beyond simple RHS's.
271
+ if ali , ok := pass .TypesInfo .Uses [n ].(* types.TypeName ); ok {
272
+ inalias , ok := inlinableAliases [ali ]
273
+ if ! ok {
274
+ var fact goFixInlineAliasFact
275
+ if pass .ImportObjectFact (ali , & fact ) {
276
+ inalias = & fact
277
+ inlinableAliases [ali ] = inalias
278
+ }
279
+ }
280
+ if inalias == nil {
281
+ continue // nope
282
+ }
283
+ curFile := currentFile (cur )
284
+
285
+ // We have an identifier A here (n), possibly qualified by a package identifier (sel.X,
286
+ // where sel is the parent of X), // and an inlinable "type A = B" elsewhere (inali).
287
+ // Consider replacing A with B.
288
+
289
+ // Check that the expression we are inlining (B) means the same thing
290
+ // (refers to the same object) in n's scope as it does in A's scope.
291
+ // If the RHS is not in the current package, AddImport will handle
292
+ // shadowing, so we only need to worry about when both expressions
293
+ // are in the current package.
294
+ if pass .Pkg .Path () == inalias .RHSPkgPath {
295
+ // fcon.rhsObj is the object referred to by B in the definition of A.
296
+ scope := pass .TypesInfo .Scopes [curFile ].Innermost (n .Pos ()) // n's scope
297
+ _ , obj := scope .LookupParent (inalias .RHSName , n .Pos ()) // what "B" means in n's scope
298
+ if obj == nil {
299
+ // Should be impossible: if code at n can refer to the LHS,
300
+ // it can refer to the RHS.
301
+ panic (fmt .Sprintf ("no object for inlinable alias %s RHS %s" , n .Name , inalias .RHSName ))
302
+ }
303
+ if obj != inalias .rhsObj {
304
+ // "B" means something different here than at the inlinable const's scope.
305
+ continue
306
+ }
307
+ } else if ! analysisinternal .CanImport (pass .Pkg .Path (), inalias .RHSPkgPath ) {
308
+ // If this package can't see the RHS's package, we can't inline.
309
+ continue
310
+ }
311
+ var (
312
+ importPrefix string
313
+ edits []analysis.TextEdit
314
+ )
315
+ if inalias .RHSPkgPath != pass .Pkg .Path () {
316
+ _ , importPrefix , edits = analysisinternal .AddImport (
317
+ pass .TypesInfo , curFile , inalias .RHSPkgName , inalias .RHSPkgPath , inalias .RHSName , n .Pos ())
318
+ }
319
+ // If n is qualified by a package identifier, we'll need the full selector expression.
320
+ var expr ast.Expr = n
321
+ if e , _ := cur .Edge (); e == edge .SelectorExpr_Sel {
322
+ expr = cur .Parent ().Node ().(ast.Expr )
323
+ }
324
+ reportInline (pass , "type alias" , "Type alias" , expr , edits , importPrefix + inalias .RHSName )
325
+ continue
326
+ }
221
327
// If the identifier is a use of an inlinable constant, suggest inlining it.
222
328
if con , ok := pass .TypesInfo .Uses [n ].(* types.Const ); ok {
223
329
incon , ok := inlinableConsts [con ]
@@ -233,14 +339,10 @@ func run(pass *analysis.Pass) (any, error) {
233
339
}
234
340
235
341
// If n is qualified by a package identifier, we'll need the full selector expression.
236
- var sel * ast.SelectorExpr
237
- if e , _ := cur .Edge (); e == edge .SelectorExpr_Sel {
238
- sel = cur .Parent ().Node ().(* ast.SelectorExpr )
239
- }
240
342
curFile := currentFile (cur )
241
343
242
- // We have an identifier A here (n), possibly qualified by a package identifier (sel.X) ,
243
- // and an inlinable "const A = B" elsewhere (fcon ).
344
+ // We have an identifier A here (n), possibly qualified by a package identifier (sel.X,
345
+ // where sel is the parent of n), // and an inlinable "const A = B" elsewhere (incon ).
244
346
// Consider replacing A with B.
245
347
246
348
// Check that the expression we are inlining (B) means the same thing
@@ -249,7 +351,7 @@ func run(pass *analysis.Pass) (any, error) {
249
351
// shadowing, so we only need to worry about when both expressions
250
352
// are in the current package.
251
353
if pass .Pkg .Path () == incon .RHSPkgPath {
252
- // fcon .rhsObj is the object referred to by B in the definition of A.
354
+ // incon .rhsObj is the object referred to by B in the definition of A.
253
355
scope := pass .TypesInfo .Scopes [curFile ].Innermost (n .Pos ()) // n's scope
254
356
_ , obj := scope .LookupParent (incon .RHSName , n .Pos ()) // what "B" means in n's scope
255
357
if obj == nil {
@@ -273,38 +375,38 @@ func run(pass *analysis.Pass) (any, error) {
273
375
_ , importPrefix , edits = analysisinternal .AddImport (
274
376
pass .TypesInfo , curFile , incon .RHSPkgName , incon .RHSPkgPath , incon .RHSName , n .Pos ())
275
377
}
276
- var (
277
- pos = n .Pos ()
278
- end = n .End ()
279
- name = n .Name
280
- )
281
- // Replace the entire SelectorExpr if there is one.
282
- if sel != nil {
283
- pos = sel .Pos ()
284
- end = sel .End ()
285
- name = sel .X .(* ast.Ident ).Name + "." + n .Name
378
+ // If n is qualified by a package identifier, we'll need the full selector expression.
379
+ var expr ast.Expr = n
380
+ if e , _ := cur .Edge (); e == edge .SelectorExpr_Sel {
381
+ expr = cur .Parent ().Node ().(ast.Expr )
286
382
}
287
- edits = append (edits , analysis.TextEdit {
288
- Pos : pos ,
289
- End : end ,
290
- NewText : []byte (importPrefix + incon .RHSName ),
291
- })
292
- pass .Report (analysis.Diagnostic {
293
- Pos : pos ,
294
- End : end ,
295
- Message : fmt .Sprintf ("Constant %s should be inlined" , name ),
296
- SuggestedFixes : []analysis.SuggestedFix {{
297
- Message : fmt .Sprintf ("Inline constant %s" , name ),
298
- TextEdits : edits ,
299
- }},
300
- })
383
+ reportInline (pass , "constant" , "Constant" , expr , edits , importPrefix + incon .RHSName )
301
384
}
302
385
}
303
386
}
304
387
305
388
return nil , nil
306
389
}
307
390
391
+ // reportInline reports a diagnostic for fixing an inlinable name.
392
+ func reportInline (pass * analysis.Pass , kind , capKind string , ident ast.Expr , edits []analysis.TextEdit , newText string ) {
393
+ edits = append (edits , analysis.TextEdit {
394
+ Pos : ident .Pos (),
395
+ End : ident .End (),
396
+ NewText : []byte (newText ),
397
+ })
398
+ name := analysisinternal .Format (pass .Fset , ident )
399
+ pass .Report (analysis.Diagnostic {
400
+ Pos : ident .Pos (),
401
+ End : ident .End (),
402
+ Message : fmt .Sprintf ("%s %s should be inlined" , capKind , name ),
403
+ SuggestedFixes : []analysis.SuggestedFix {{
404
+ Message : fmt .Sprintf ("Inline %s %s" , kind , name ),
405
+ TextEdits : edits ,
406
+ }},
407
+ })
408
+ }
409
+
308
410
// hasFixInline reports the presence of a "//go:fix inline" directive
309
411
// in the comments.
310
412
func hasFixInline (cg * ast.CommentGroup ) bool {
@@ -339,6 +441,22 @@ func (c *goFixInlineConstFact) String() string {
339
441
340
442
func (* goFixInlineConstFact ) AFact () {}
341
443
444
+ // A goFixInlineAliasFact is exported for each type alias marked "//go:fix inline".
445
+ // It holds information about an inlinable type alias. Gob-serializable.
446
+ type goFixInlineAliasFact struct {
447
+ // Information about "type LHSName = RHSName".
448
+ RHSName string
449
+ RHSPkgPath string
450
+ RHSPkgName string
451
+ rhsObj types.Object // for current package
452
+ }
453
+
454
+ func (c * goFixInlineAliasFact ) String () string {
455
+ return fmt .Sprintf ("goFixInline alias %q.%s" , c .RHSPkgPath , c .RHSName )
456
+ }
457
+
458
+ func (* goFixInlineAliasFact ) AFact () {}
459
+
342
460
func discard (string , ... any ) {}
343
461
344
462
var builtinIota = types .Universe .Lookup ("iota" )
0 commit comments