|
9 | 9 | "go/ast"
|
10 | 10 | "go/token"
|
11 | 11 | "go/types"
|
| 12 | + "strings" |
12 | 13 |
|
13 | 14 | _ "embed"
|
14 | 15 |
|
@@ -118,32 +119,31 @@ func (a *analyzer) findAlias(spec *ast.TypeSpec, declInline bool) {
|
118 | 119 | a.pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: not a type alias")
|
119 | 120 | return
|
120 | 121 | }
|
| 122 | + |
| 123 | + // Disallow inlines of type expressions containing array types. |
| 124 | + // Given an array type like [N]int where N is a named constant, go/types provides |
| 125 | + // only the value of the constant as an int64. So inlining A in this code: |
| 126 | + // |
| 127 | + // const N = 5 |
| 128 | + // type A = [N]int |
| 129 | + // |
| 130 | + // would result in [5]int, breaking the connection with N. |
| 131 | + // TODO(jba): accept type expressions where the array size is a literal integer |
| 132 | + for n := range ast.Preorder(spec.Type) { |
| 133 | + if ar, ok := n.(*ast.ArrayType); ok && ar.Len != nil { |
| 134 | + a.pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: array types not supported") |
| 135 | + return |
| 136 | + } |
| 137 | + } |
| 138 | + |
121 | 139 | if spec.TypeParams != nil {
|
122 | 140 | // TODO(jba): handle generic aliases
|
123 | 141 | return
|
124 | 142 | }
|
125 |
| - // The alias must refer to another named type. |
126 |
| - // TODO(jba): generalize to more type expressions. |
127 |
| - var rhsID *ast.Ident |
128 |
| - switch e := ast.Unparen(spec.Type).(type) { |
129 |
| - case *ast.Ident: |
130 |
| - rhsID = e |
131 |
| - case *ast.SelectorExpr: |
132 |
| - rhsID = e.Sel |
133 |
| - default: |
134 |
| - return |
135 |
| - } |
| 143 | + |
| 144 | + // Remember that this is an inlinable alias. |
| 145 | + typ := &goFixInlineAliasFact{} |
136 | 146 | lhs := a.pass.TypesInfo.Defs[spec.Name].(*types.TypeName)
|
137 |
| - // more (jba): test one alias pointing to another alias |
138 |
| - rhs := a.pass.TypesInfo.Uses[rhsID].(*types.TypeName) |
139 |
| - typ := &goFixInlineAliasFact{ |
140 |
| - RHSName: rhs.Name(), |
141 |
| - RHSPkgName: rhs.Pkg().Name(), |
142 |
| - RHSPkgPath: rhs.Pkg().Path(), |
143 |
| - } |
144 |
| - if rhs.Pkg() == a.pass.Pkg { |
145 |
| - typ.rhsObj = rhs |
146 |
| - } |
147 | 147 | a.inlinableAliases[lhs] = typ
|
148 | 148 | // Create a fact only if the LHS is exported and defined at top level.
|
149 | 149 | // We create a fact even if the RHS is non-exported,
|
@@ -302,49 +302,148 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, cur cursor.Cursor) {
|
302 | 302 | if inalias == nil {
|
303 | 303 | return // nope
|
304 | 304 | }
|
305 |
| - curFile := currentFile(cur) |
306 | 305 |
|
307 |
| - // We have an identifier A here (n), possibly qualified by a package identifier (sel.X, |
308 |
| - // where sel is the parent of X), // and an inlinable "type A = B" elsewhere (inali). |
309 |
| - // Consider replacing A with B. |
| 306 | + // Get the alias's RHS. It has everything we need to format the replacement text. |
| 307 | + rhs := tn.Type().(*types.Alias).Rhs() |
310 | 308 |
|
311 |
| - // Check that the expression we are inlining (B) means the same thing |
312 |
| - // (refers to the same object) in n's scope as it does in A's scope. |
313 |
| - // If the RHS is not in the current package, AddImport will handle |
314 |
| - // shadowing, so we only need to worry about when both expressions |
315 |
| - // are in the current package. |
| 309 | + curPath := a.pass.Pkg.Path() |
| 310 | + curFile := currentFile(cur) |
316 | 311 | n := cur.Node().(*ast.Ident)
|
317 |
| - if a.pass.Pkg.Path() == inalias.RHSPkgPath { |
318 |
| - // fcon.rhsObj is the object referred to by B in the definition of A. |
319 |
| - scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope |
320 |
| - _, obj := scope.LookupParent(inalias.RHSName, n.Pos()) // what "B" means in n's scope |
321 |
| - if obj == nil { |
322 |
| - // Should be impossible: if code at n can refer to the LHS, |
323 |
| - // it can refer to the RHS. |
324 |
| - panic(fmt.Sprintf("no object for inlinable alias %s RHS %s", n.Name, inalias.RHSName)) |
| 312 | + // We have an identifier A here (n), possibly qualified by a package |
| 313 | + // identifier (sel.n), and an inlinable "type A = rhs" elsewhere. |
| 314 | + // |
| 315 | + // We can replace A with rhs if no name in rhs is shadowed at n's position, |
| 316 | + // and every package in rhs is importable by the current package. |
| 317 | + |
| 318 | + var ( |
| 319 | + importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix |
| 320 | + edits []analysis.TextEdit |
| 321 | + ) |
| 322 | + for _, tn := range typenames(rhs) { |
| 323 | + var pkgPath, pkgName string |
| 324 | + if pkg := tn.Pkg(); pkg != nil { |
| 325 | + pkgPath = pkg.Path() |
| 326 | + pkgName = pkg.Name() |
325 | 327 | }
|
326 |
| - if obj != inalias.rhsObj { |
327 |
| - // "B" means something different here than at the inlinable const's scope. |
| 328 | + if pkgPath == "" || pkgPath == curPath { |
| 329 | + // The name is in the current package or the universe scope, so no import |
| 330 | + // is required. Check that it is not shadowed (that is, that the type |
| 331 | + // it refers to in rhs is the same one it refers to at n). |
| 332 | + scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope |
| 333 | + _, obj := scope.LookupParent(tn.Name(), n.Pos()) // what qn.name means in n's scope |
| 334 | + if obj != tn { // shadowed |
| 335 | + return |
| 336 | + } |
| 337 | + } else if !analysisinternal.CanImport(a.pass.Pkg.Path(), pkgPath) { |
| 338 | + // If this package can't see the package of this part of rhs, we can't inline. |
328 | 339 | return
|
| 340 | + } else if _, ok := importPrefixes[pkgPath]; !ok { |
| 341 | + // Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns |
| 342 | + // with the package path for use by the TypeString qualifier below. |
| 343 | + _, prefix, eds := analysisinternal.AddImport( |
| 344 | + a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), n.Pos()) |
| 345 | + importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".") |
| 346 | + edits = append(edits, eds...) |
329 | 347 | }
|
330 |
| - } else if !analysisinternal.CanImport(a.pass.Pkg.Path(), inalias.RHSPkgPath) { |
331 |
| - // If this package can't see the RHS's package, we can't inline. |
332 |
| - return |
333 |
| - } |
334 |
| - var ( |
335 |
| - importPrefix string |
336 |
| - edits []analysis.TextEdit |
337 |
| - ) |
338 |
| - if inalias.RHSPkgPath != a.pass.Pkg.Path() { |
339 |
| - _, importPrefix, edits = analysisinternal.AddImport( |
340 |
| - a.pass.TypesInfo, curFile, inalias.RHSPkgName, inalias.RHSPkgPath, inalias.RHSName, n.Pos()) |
341 | 348 | }
|
342 | 349 | // If n is qualified by a package identifier, we'll need the full selector expression.
|
343 | 350 | var expr ast.Expr = n
|
344 | 351 | if e, _ := cur.Edge(); e == edge.SelectorExpr_Sel {
|
345 | 352 | expr = cur.Parent().Node().(ast.Expr)
|
346 | 353 | }
|
347 |
| - a.reportInline("type alias", "Type alias", expr, edits, importPrefix+inalias.RHSName) |
| 354 | + // To get the replacement text, render the alias RHS using the package prefixes |
| 355 | + // we assigned above. |
| 356 | + newText := types.TypeString(rhs, func(p *types.Package) string { |
| 357 | + if p == a.pass.Pkg { |
| 358 | + return "" |
| 359 | + } |
| 360 | + if prefix, ok := importPrefixes[p.Path()]; ok { |
| 361 | + return prefix |
| 362 | + } |
| 363 | + panic(fmt.Sprintf("in %q, package path %q has no import prefix", rhs, p.Path())) |
| 364 | + }) |
| 365 | + a.reportInline("type alias", "Type alias", expr, edits, newText) |
| 366 | +} |
| 367 | + |
| 368 | +// typenames returns the TypeNames for types within t (including t itself) that have |
| 369 | +// them: basic types, named types and alias types. |
| 370 | +// The same name may appear more than once. |
| 371 | +func typenames(t types.Type) []*types.TypeName { |
| 372 | + var tns []*types.TypeName |
| 373 | + |
| 374 | + var visit func(types.Type) |
| 375 | + |
| 376 | + // TODO(jba): when typesinternal.NamedOrAlias adds TypeArgs, replace this type literal with it. |
| 377 | + namedOrAlias := func(t interface { |
| 378 | + TypeArgs() *types.TypeList |
| 379 | + Obj() *types.TypeName |
| 380 | + }) { |
| 381 | + tns = append(tns, t.Obj()) |
| 382 | + args := t.TypeArgs() |
| 383 | + // TODO(jba): replace with TypeList.Types when this file is at 1.24. |
| 384 | + for i := range args.Len() { |
| 385 | + visit(args.At(i)) |
| 386 | + } |
| 387 | + } |
| 388 | + |
| 389 | + visit = func(t types.Type) { |
| 390 | + switch t := t.(type) { |
| 391 | + case *types.Basic: |
| 392 | + tns = append(tns, types.Universe.Lookup(t.Name()).(*types.TypeName)) |
| 393 | + case *types.Named: |
| 394 | + namedOrAlias(t) |
| 395 | + case *types.Alias: |
| 396 | + namedOrAlias(t) |
| 397 | + case *types.TypeParam: |
| 398 | + tns = append(tns, t.Obj()) |
| 399 | + case *types.Pointer: |
| 400 | + visit(t.Elem()) |
| 401 | + case *types.Slice: |
| 402 | + visit(t.Elem()) |
| 403 | + case *types.Array: |
| 404 | + visit(t.Elem()) |
| 405 | + case *types.Chan: |
| 406 | + visit(t.Elem()) |
| 407 | + case *types.Map: |
| 408 | + visit(t.Key()) |
| 409 | + visit(t.Elem()) |
| 410 | + case *types.Struct: |
| 411 | + // TODO(jba): replace with Struct.Fields when this file is at 1.24. |
| 412 | + for i := range t.NumFields() { |
| 413 | + visit(t.Field(i).Type()) |
| 414 | + } |
| 415 | + case *types.Signature: |
| 416 | + // Ignore the receiver: although it may be present, it has no meaning |
| 417 | + // in a type expression. |
| 418 | + // Ditto for receiver type params. |
| 419 | + // Also, function type params cannot appear in a type expression. |
| 420 | + if t.TypeParams() != nil { |
| 421 | + panic("Signature.TypeParams in type expression") |
| 422 | + } |
| 423 | + visit(t.Params()) |
| 424 | + visit(t.Results()) |
| 425 | + case *types.Interface: |
| 426 | + for i := range t.NumEmbeddeds() { |
| 427 | + visit(t.EmbeddedType(i)) |
| 428 | + } |
| 429 | + for i := range t.NumExplicitMethods() { |
| 430 | + visit(t.ExplicitMethod(i).Type()) |
| 431 | + } |
| 432 | + case *types.Tuple: |
| 433 | + // TODO(jba): replace with Tuple.Variables when this file is at 1.24. |
| 434 | + for i := range t.Len() { |
| 435 | + visit(t.At(i).Type()) |
| 436 | + } |
| 437 | + case *types.Union: |
| 438 | + panic("Union in type expression") |
| 439 | + default: |
| 440 | + panic(fmt.Sprintf("unknown type %T", t)) |
| 441 | + } |
| 442 | + } |
| 443 | + |
| 444 | + visit(t) |
| 445 | + |
| 446 | + return tns |
348 | 447 | }
|
349 | 448 |
|
350 | 449 | // If con is an inlinable constant, suggest inlining its use at cur.
|
@@ -481,20 +580,11 @@ func (c *goFixInlineConstFact) String() string {
|
481 | 580 | func (*goFixInlineConstFact) AFact() {}
|
482 | 581 |
|
483 | 582 | // A goFixInlineAliasFact is exported for each type alias marked "//go:fix inline".
|
484 |
| -// It holds information about an inlinable type alias. Gob-serializable. |
485 |
| -type goFixInlineAliasFact struct { |
486 |
| - // Information about "type LHSName = RHSName". |
487 |
| - RHSName string |
488 |
| - RHSPkgPath string |
489 |
| - RHSPkgName string |
490 |
| - rhsObj types.Object // for current package |
491 |
| -} |
492 |
| - |
493 |
| -func (c *goFixInlineAliasFact) String() string { |
494 |
| - return fmt.Sprintf("goFixInline alias %q.%s", c.RHSPkgPath, c.RHSName) |
495 |
| -} |
| 583 | +// It holds no information; its mere existence demonstrates that an alias is inlinable. |
| 584 | +type goFixInlineAliasFact struct{} |
496 | 585 |
|
497 |
| -func (*goFixInlineAliasFact) AFact() {} |
| 586 | +func (c *goFixInlineAliasFact) String() string { return "goFixInline alias" } |
| 587 | +func (*goFixInlineAliasFact) AFact() {} |
498 | 588 |
|
499 | 589 | func discard(string, ...any) {}
|
500 | 590 |
|
|
0 commit comments