Skip to content

Commit 62daff4

Browse files
hyangahadonovan
authored andcommitted
go/analysis/passes/inline: fix panic in inlineAlias with instantiated generic alias
When the inline analyzer encounters a type alias like type Alias[T any] = []T, it needs to perform two main steps for inlining: 1. Collect Imports: Identify every package mentioned in the Right-Hand Side (RHS) of the alias to ensure the caller's file has the necessary import statements. 2. Generate Text: Use types.TypeString to render the RHS into a string to replace the alias usage. The bug was that the analyzer collected imports from the original (generic) RHS but then generated the replacement text using the instantiated RHS. Consider a case: * Package A: type Alias[T any] = []T * Package B: type MyType int * Main: var _ A.Alias[B.MyType] The original RHS is just []T (no extra packages). However, when instantiated for the caller, the RHS becomes []B.MyType. Because the analyzer had already finished its "Collect Imports" phase using the generic []T, it didn't realize it needed to add an import for Package B. When types.TypeString eventually tried to render []B.MyType, it looked for a registered import prefix for Package B, found none, and panicked with "package path has no import prefix". Moving the import collection logic to after the RHS instantiation ensures that all necessary packages from the instantiated RHS are identified and imported, preventing the 'package path has no import prefix' panic. Fixes golang/go#77844 Change-Id: I45db22a62d371804b837bf0957b25625e9413487 Reviewed-on: https://go-review.googlesource.com/c/tools/+/749800 Reviewed-by: Alan Donovan <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent fcb6088 commit 62daff4

3 files changed

Lines changed: 77 additions & 31 deletions

File tree

go/analysis/passes/inline/inline.go

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -315,12 +315,43 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
315315
curPath := a.pass.Pkg.Path()
316316
curFile := astutil.EnclosingFile(curId)
317317
id := curId.Node().(*ast.Ident)
318+
319+
// Find the complete identifier, which may take any of these forms:
320+
// Id
321+
// Id[T]
322+
// Id[K, V]
323+
// pkg.Id
324+
// pkg.Id[T]
325+
// pkg.Id[K, V]
326+
var expr ast.Expr = id
327+
if curId.ParentEdgeKind() == edge.SelectorExpr_Sel {
328+
curId = curId.Parent()
329+
expr = curId.Node().(ast.Expr)
330+
}
331+
// If expr is part of an IndexExpr or IndexListExpr, we'll need that node.
332+
// Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
333+
switch curId.ParentEdgeKind() {
334+
case edge.IndexExpr_X:
335+
expr = curId.Parent().Node().(*ast.IndexExpr)
336+
case edge.IndexListExpr_X:
337+
expr = curId.Parent().Node().(*ast.IndexListExpr)
338+
}
339+
t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
340+
if targs := t.TypeArgs(); targs.Len() > 0 {
341+
// Instantiate the alias with the type args from this use.
342+
// For example, given type A = M[K, V], compute the type of the use
343+
// A[int, Foo] as M[int, Foo].
344+
// Don't validate instantiation: it can't panic unless we have a bug,
345+
// in which case seeing the stack trace via telemetry would be helpful.
346+
instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false)
347+
rhs = instAlias.(*types.Alias).Rhs()
348+
}
349+
318350
// We have an identifier A here (n), possibly qualified by a package
319351
// identifier (sel.n), and an inlinable "type A = rhs" elsewhere.
320352
//
321353
// We can replace A with rhs if no name in rhs is shadowed at n's position,
322354
// and every package in rhs is importable by the current package.
323-
324355
var (
325356
importPrefixes = map[string]string{curPath: ""} // from pkg path to prefix
326357
edits []analysis.TextEdit
@@ -349,43 +380,15 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
349380
return
350381
} else if _, ok := importPrefixes[pkgPath]; !ok {
351382
// Use AddImport to add pkgPath if it's not there already. Associate the prefix it assigns
383+
// with the prefix it assigns
352384
// with the package path for use by the TypeString qualifier below.
353385
prefix, eds := refactor.AddImport(
354386
a.pass.TypesInfo, curFile, pkgName, pkgPath, tn.Name(), id.Pos())
355387
importPrefixes[pkgPath] = strings.TrimSuffix(prefix, ".")
356388
edits = append(edits, eds...)
357389
}
358390
}
359-
// Find the complete identifier, which may take any of these forms:
360-
// Id
361-
// Id[T]
362-
// Id[K, V]
363-
// pkg.Id
364-
// pkg.Id[T]
365-
// pkg.Id[K, V]
366-
var expr ast.Expr = id
367-
if curId.ParentEdgeKind() == edge.SelectorExpr_Sel {
368-
curId = curId.Parent()
369-
expr = curId.Node().(ast.Expr)
370-
}
371-
// If expr is part of an IndexExpr or IndexListExpr, we'll need that node.
372-
// Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
373-
switch curId.ParentEdgeKind() {
374-
case edge.IndexExpr_X:
375-
expr = curId.Parent().Node().(*ast.IndexExpr)
376-
case edge.IndexListExpr_X:
377-
expr = curId.Parent().Node().(*ast.IndexListExpr)
378-
}
379-
t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
380-
if targs := t.TypeArgs(); targs.Len() > 0 {
381-
// Instantiate the alias with the type args from this use.
382-
// For example, given type A = M[K, V], compute the type of the use
383-
// A[int, Foo] as M[int, Foo].
384-
// Don't validate instantiation: it can't panic unless we have a bug,
385-
// in which case seeing the stack trace via telemetry would be helpful.
386-
instAlias, _ := types.Instantiate(nil, alias, slices.Collect(targs.Types()), false)
387-
rhs = instAlias.(*types.Alias).Rhs()
388-
}
391+
389392
// To get the replacement text, render the alias RHS using the package prefixes
390393
// we assigned above.
391394
newText := types.TypeString(rhs, func(p *types.Package) string {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package inline
6+
7+
import (
8+
"golang.org/x/tools/go/analysis/analysistest"
9+
"golang.org/x/tools/internal/testfiles"
10+
"testing"
11+
)
12+
13+
func TestIssue77844(t *testing.T) {
14+
dir := testfiles.ExtractTxtarFileToTmp(t, "testdata/src/issue77844.txtar")
15+
analysistest.Run(t, dir, Analyzer, "example.com/main")
16+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-- go.mod --
2+
module example.com
3+
4+
go 1.24
5+
6+
-- lib/lib.go --
7+
package lib
8+
9+
//go:fix inline
10+
type Alias[T any] = []T
11+
12+
-- other/other.go --
13+
package other
14+
15+
type Other int
16+
17+
-- main/main.go --
18+
package main
19+
20+
import (
21+
"example.com/lib"
22+
"example.com/other"
23+
)
24+
25+
func _() {
26+
var _ lib.Alias[other.Other] // want "Type alias lib.Alias.other.Other. should be inlined"
27+
}

0 commit comments

Comments
 (0)