Skip to content

Commit 1c52ccd

Browse files
committed
gopls/internal/analysis/gofix: inline most aliases
Support inlining an alias with an arbitrary right-hand side. The type checker gives us almost everything we need to inline an alias; the only thing missing is the bit that says that a //go:fix directive was present. So the fact is an empty struct. Skip aliases that mention arrays. The array length expression isn't represented, and it may refer to other values, so inlining it would incorrectly decouple the inlined expression from the original. For golang/go#32816. Change-Id: I2e5ff1bd69a0f88cd7cb396dee8d4b426988d1cc Reviewed-on: https://go-review.googlesource.com/c/tools/+/650755 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 4e0c888 commit 1c52ccd

File tree

8 files changed

+439
-77
lines changed

8 files changed

+439
-77
lines changed

gopls/internal/analysis/gofix/gofix.go

+155-65
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"go/ast"
1010
"go/token"
1111
"go/types"
12+
"strings"
1213

1314
_ "embed"
1415

@@ -118,32 +119,31 @@ func (a *analyzer) findAlias(spec *ast.TypeSpec, declInline bool) {
118119
a.pass.Reportf(spec.Pos(), "invalid //go:fix inline directive: not a type alias")
119120
return
120121
}
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+
121139
if spec.TypeParams != nil {
122140
// TODO(jba): handle generic aliases
123141
return
124142
}
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{}
136146
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-
}
147147
a.inlinableAliases[lhs] = typ
148148
// Create a fact only if the LHS is exported and defined at top level.
149149
// 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) {
302302
if inalias == nil {
303303
return // nope
304304
}
305-
curFile := currentFile(cur)
306305

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()
310308

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)
316311
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()
325327
}
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.
328339
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...)
329347
}
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())
341348
}
342349
// If n is qualified by a package identifier, we'll need the full selector expression.
343350
var expr ast.Expr = n
344351
if e, _ := cur.Edge(); e == edge.SelectorExpr_Sel {
345352
expr = cur.Parent().Node().(ast.Expr)
346353
}
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
348447
}
349448

350449
// If con is an inlinable constant, suggest inlining its use at cur.
@@ -481,20 +580,11 @@ func (c *goFixInlineConstFact) String() string {
481580
func (*goFixInlineConstFact) AFact() {}
482581

483582
// 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{}
496585

497-
func (*goFixInlineAliasFact) AFact() {}
586+
func (c *goFixInlineAliasFact) String() string { return "goFixInline alias" }
587+
func (*goFixInlineAliasFact) AFact() {}
498588

499589
func discard(string, ...any) {}
500590

0 commit comments

Comments
 (0)