Skip to content

Commit

Permalink
go/ssa: fix *SelectorExpr within *IndexExpr handling
Browse files Browse the repository at this point in the history
When a *IndexExpr or *IndexListExpr expr is over a *SelectorExpr and
expr denotes an instantiation, build expr as the *SelectorExpr.

Fixes golang/go#52834

Change-Id: I9a69ac28a6e8fb0ee9eb45db8675872b75d69a0f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/405555
gopls-CI: kokoro <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Run-TryBot: Tim King <[email protected]>
Reviewed-by: Zvonimir Pavlinovic <[email protected]>
Reviewed-by: Alan Donovan <[email protected]>
  • Loading branch information
timothy-king committed May 16, 2022
1 parent 29d48d6 commit 1e55371
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
15 changes: 7 additions & 8 deletions go/ssa/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,16 +840,15 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
panic("unexpected expression-relative selector")

case *typeparams.IndexListExpr:
if ident, ok := e.X.(*ast.Ident); ok {
// IndexListExpr is an instantiation. It will be handled by the *Ident case.
return b.expr(fn, ident)
// f[X, Y] must be a generic function
if !instance(fn.info, e.X) {
panic("unexpected expression-could not match index list to instantiation")
}
return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases.

case *ast.IndexExpr:
if ident, ok := e.X.(*ast.Ident); ok {
if _, ok := typeparams.GetInstances(fn.info)[ident]; ok {
// If the IndexExpr is an instantiation, it will be handled by the *Ident case.
return b.expr(fn, ident)
}
if instance(fn.info, e.X) {
return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases.
}
// not a generic instantiation.
switch t := fn.typeOf(e.X).Underlying().(type) {
Expand Down
54 changes: 54 additions & 0 deletions go/ssa/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"strings"
"testing"

"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
Expand Down Expand Up @@ -823,3 +824,56 @@ func sliceMax(s []int) []int { return s[a():b():c()] }
})
}
}

// TestGenericFunctionSelector ensures generic functions from other packages can be selected.
func TestGenericFunctionSelector(t *testing.T) {
if !typeparams.Enabled {
t.Skip("TestGenericFunctionSelector uses type parameters.")
}

pkgs := map[string]map[string]string{
"main": {"m.go": `package main; import "a"; func main() { a.F[int](); a.G[int,string](); a.H(0) }`},
"a": {"a.go": `package a; func F[T any](){}; func G[S, T any](){}; func H[T any](a T){} `},
}

for _, mode := range []ssa.BuilderMode{
ssa.SanityCheckFunctions,
ssa.SanityCheckFunctions | ssa.InstantiateGenerics,
} {
conf := loader.Config{
Build: buildutil.FakeContext(pkgs),
}
conf.Import("main")

lprog, err := conf.Load()
if err != nil {
t.Errorf("Load failed: %s", err)
}
if lprog == nil {
t.Fatalf("Load returned nil *Program")
}
// Create and build SSA
prog := ssautil.CreateProgram(lprog, mode)
p := prog.Package(lprog.Package("main").Pkg)
p.Build()

var callees []string // callees of the CallInstruction.String() in main().
for _, b := range p.Func("main").Blocks {
for _, i := range b.Instrs {
if call, ok := i.(ssa.CallInstruction); ok {
if callee := call.Common().StaticCallee(); call != nil {
callees = append(callees, callee.String())
} else {
t.Errorf("CallInstruction without StaticCallee() %q", call)
}
}
}
}
sort.Strings(callees) // ignore the order in the code.

want := "[a.F[[int]] a.G[[int string]] a.H[[int]]]"
if got := fmt.Sprint(callees); got != want {
t.Errorf("Expected main() to contain calls %v. got %v", want, got)
}
}
}
18 changes: 18 additions & 0 deletions go/ssa/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,24 @@ func recvAsFirstArg(sig *types.Signature) *types.Signature {
return typeparams.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic())
}

// instance returns whether an expression is a simple or qualified identifier
// that is a generic instantiation.
func instance(info *types.Info, expr ast.Expr) bool {
// Compare the logic here against go/types.instantiatedIdent,
// which also handles *IndexExpr and *IndexListExpr.
var id *ast.Ident
switch x := expr.(type) {
case *ast.Ident:
id = x
case *ast.SelectorExpr:
id = x.Sel
default:
return false
}
_, ok := typeparams.GetInstances(info)[id]
return ok
}

// instanceArgs returns the Instance[id].TypeArgs as a slice.
func instanceArgs(info *types.Info, id *ast.Ident) []types.Type {
targList := typeparams.GetInstances(info)[id].TypeArgs
Expand Down

0 comments on commit 1e55371

Please sign in to comment.