Skip to content

Commit 2839096

Browse files
committed
gopls/internal/analysis/gofix: generic aliases
Support inlining generic aliases. For golang/go#32816. Change-Id: Ic65e6fb30d65ee0f7d6e0093fd882a675de71da4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/651617 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 0efa5e5 commit 2839096

File tree

3 files changed

+96
-19
lines changed

3 files changed

+96
-19
lines changed

gopls/internal/analysis/gofix/gofix.go

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"go/ast"
1010
"go/token"
1111
"go/types"
12+
"slices"
1213
"strings"
1314

1415
_ "embed"
@@ -140,11 +141,6 @@ func (a *analyzer) findAlias(spec *ast.TypeSpec, declInline bool) {
140141
}
141142
}
142143

143-
if spec.TypeParams != nil {
144-
// TODO(jba): handle generic aliases
145-
return
146-
}
147-
148144
// Remember that this is an inlinable alias.
149145
typ := &goFixInlineAliasFact{}
150146
lhs := a.pass.TypesInfo.Defs[spec.Name].(*types.TypeName)
@@ -294,7 +290,7 @@ func (a *analyzer) inlineCall(call *ast.CallExpr, cur cursor.Cursor) {
294290
}
295291

296292
// If tn is the TypeName of an inlinable alias, suggest inlining its use at cur.
297-
func (a *analyzer) inlineAlias(tn *types.TypeName, cur cursor.Cursor) {
293+
func (a *analyzer) inlineAlias(tn *types.TypeName, curId cursor.Cursor) {
298294
inalias, ok := a.inlinableAliases[tn]
299295
if !ok {
300296
var fact goFixInlineAliasFact
@@ -307,12 +303,17 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, cur cursor.Cursor) {
307303
return // nope
308304
}
309305

310-
// Get the alias's RHS. It has everything we need to format the replacement text.
311-
rhs := tn.Type().(*types.Alias).Rhs()
312-
306+
alias := tn.Type().(*types.Alias)
307+
// Remember the names of the alias's type params. When we check for shadowing
308+
// later, we'll ignore these because they won't appear in the replacement text.
309+
typeParamNames := map[*types.TypeName]bool{}
310+
for tp := range alias.TypeParams().TypeParams() {
311+
typeParamNames[tp.Obj()] = true
312+
}
313+
rhs := alias.Rhs()
313314
curPath := a.pass.Pkg.Path()
314-
curFile := currentFile(cur)
315-
n := cur.Node().(*ast.Ident)
315+
curFile := currentFile(curId)
316+
id := curId.Node().(*ast.Ident)
316317
// We have an identifier A here (n), possibly qualified by a package
317318
// identifier (sel.n), and an inlinable "type A = rhs" elsewhere.
318319
//
@@ -324,6 +325,10 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, cur cursor.Cursor) {
324325
edits []analysis.TextEdit
325326
)
326327
for _, tn := range typenames(rhs) {
328+
// Ignore the type parameters of the alias: they won't appear in the result.
329+
if typeParamNames[tn] {
330+
continue
331+
}
327332
var pkgPath, pkgName string
328333
if pkg := tn.Pkg(); pkg != nil {
329334
pkgPath = pkg.Path()
@@ -333,9 +338,9 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, cur cursor.Cursor) {
333338
// The name is in the current package or the universe scope, so no import
334339
// is required. Check that it is not shadowed (that is, that the type
335340
// it refers to in rhs is the same one it refers to at n).
336-
scope := a.pass.TypesInfo.Scopes[curFile].Innermost(n.Pos()) // n's scope
337-
_, obj := scope.LookupParent(tn.Name(), n.Pos()) // what qn.name means in n's scope
338-
if obj != tn { // shadowed
341+
scope := a.pass.TypesInfo.Scopes[curFile].Innermost(id.Pos()) // n's scope
342+
_, obj := scope.LookupParent(tn.Name(), id.Pos()) // what qn.name means in n's scope
343+
if obj != tn {
339344
return
340345
}
341346
} else if !analysisinternal.CanImport(a.pass.Pkg.Path(), pkgPath) {
@@ -345,15 +350,40 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, cur cursor.Cursor) {
345350
// Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns
346351
// with the package path for use by the TypeString qualifier below.
347352
_, prefix, eds := analysisinternal.AddImport(
348-
a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), n.Pos())
353+
a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos())
349354
importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".")
350355
edits = append(edits, eds...)
351356
}
352357
}
353-
// If n is qualified by a package identifier, we'll need the full selector expression.
354-
var expr ast.Expr = n
355-
if e, _ := cur.Edge(); e == edge.SelectorExpr_Sel {
356-
expr = cur.Parent().Node().(ast.Expr)
358+
// Find the complete identifier, which may take any of these forms:
359+
// Id
360+
// Id[T]
361+
// Id[K, V]
362+
// pkg.Id
363+
// pkg.Id[T]
364+
// pkg.Id[K, V]
365+
var expr ast.Expr = id
366+
if e, _ := curId.Edge(); e == edge.SelectorExpr_Sel {
367+
curId = curId.Parent()
368+
expr = curId.Node().(ast.Expr)
369+
}
370+
// If expr is part of an IndexExpr or IndexListExpr, we'll need that node.
371+
// Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
372+
switch ek, _ := curId.Edge(); ek {
373+
case edge.IndexExpr_X:
374+
expr = curId.Parent().Node().(*ast.IndexExpr)
375+
case edge.IndexListExpr_X:
376+
expr = curId.Parent().Node().(*ast.IndexListExpr)
377+
}
378+
t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
379+
if targs := t.TypeArgs(); targs.Len() > 0 {
380+
// Instantiate the alias with the type args from this use.
381+
// For example, given type A = M[K, V], compute the type of the use
382+
// A[int, Foo] as M[int, Foo].
383+
// Don't validate instantiation: it can't panic unless we have a bug,
384+
// in which case seeing the stack trace via telemetry would be helpful.
385+
instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false)
386+
rhs = instAlias.(*types.Alias).Rhs()
357387
}
358388
// To get the replacement text, render the alias RHS using the package prefixes
359389
// we assigned above.

gopls/internal/analysis/gofix/testdata/src/a/a.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,25 @@ func _[P any]() {
164164
_ = x
165165
_ = y
166166
}
167+
168+
// generic type aliases
169+
170+
//go:fix inline
171+
type (
172+
Mapset[T comparable] = map[T]bool // want Mapset: `goFixInline alias`
173+
Pair[X, Y any] = struct { // want Pair: `goFixInline alias`
174+
X X
175+
Y Y
176+
}
177+
)
178+
179+
var _ Mapset[int] // want `Type alias Mapset\[int\] should be inlined`
180+
181+
var _ Pair[T, string] // want `Type alias Pair\[T, string\] should be inlined`
182+
183+
func _[V any]() {
184+
//go:fix inline
185+
type M[K comparable] = map[K]V
186+
187+
var _ M[int] // want `Type alias M\[int\] should be inlined`
188+
}

gopls/internal/analysis/gofix/testdata/src/a/a.go.golden

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,28 @@ func _[P any]() {
165165
_ = x
166166
_ = y
167167
}
168+
169+
// generic type aliases
170+
171+
//go:fix inline
172+
type (
173+
Mapset[T comparable] = map[T]bool // want Mapset: `goFixInline alias`
174+
Pair[X, Y any] = struct { // want Pair: `goFixInline alias`
175+
X X
176+
Y Y
177+
}
178+
)
179+
180+
var _ map[int]bool // want `Type alias Mapset\[int\] should be inlined`
181+
182+
var _ struct {
183+
X T
184+
Y string
185+
} // want `Type alias Pair\[T, string\] should be inlined`
186+
187+
func _[V any]() {
188+
//go:fix inline
189+
type M[K comparable] = map[K]V
190+
191+
var _ map[int]V // want `Type alias M\[int\] should be inlined`
192+
}

0 commit comments

Comments
 (0)