go/ssa: return nil on parameterized types on MethodValue.
MethodValue now checks if a type is parameterized types and returns nil instead of proceeding. Parameterized types are not runtime types so they should not have method sets created or be added to Prog.methodSet. This is similar to what MethodValue previously did for interfaces.
Updates golang/go#48525
Change-Id: Ib9026ddc0167fd71afd3e5c194aadf20411b9cdf
Reviewed-on: https://go-review.googlesource.com/c/tools/+/400515
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/go/ssa/create.go b/go/ssa/create.go
index 0ab2fe1..403baae 100644
--- a/go/ssa/create.go
+++ b/go/ssa/create.go
@@ -24,15 +24,16 @@
// mode controls diagnostics and checking during SSA construction.
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
- Fset: fset,
- imported: make(map[string]*Package),
- packages: make(map[*types.Package]*Package),
- thunks: make(map[selectionKey]*Function),
- bounds: make(map[boundsKey]*Function),
- mode: mode,
- canon: newCanonizer(),
- ctxt: typeparams.NewContext(),
- instances: make(map[*Function]*instanceSet),
+ Fset: fset,
+ imported: make(map[string]*Package),
+ packages: make(map[*types.Package]*Package),
+ thunks: make(map[selectionKey]*Function),
+ bounds: make(map[boundsKey]*Function),
+ mode: mode,
+ canon: newCanonizer(),
+ ctxt: typeparams.NewContext(),
+ instances: make(map[*Function]*instanceSet),
+ parameterized: tpWalker{seen: make(map[types.Type]bool)},
}
h := typeutil.MakeHasher() // protected by methodsMu, in effect
diff --git a/go/ssa/methods.go b/go/ssa/methods.go
index 7955ed3..aedb41f 100644
--- a/go/ssa/methods.go
+++ b/go/ssa/methods.go
@@ -9,11 +9,13 @@
import (
"fmt"
"go/types"
+
+ "golang.org/x/tools/internal/typeparams"
)
// MethodValue returns the Function implementing method sel, building
// wrapper methods on demand. It returns nil if sel denotes an
-// abstract (interface) method.
+// abstract (interface or parameterized) method.
//
// Precondition: sel.Kind() == MethodVal.
//
@@ -26,17 +28,26 @@
}
T := sel.Recv()
if isInterface(T) {
- return nil // abstract method
+ return nil // abstract method (interface)
}
if prog.mode&LogSource != 0 {
defer logStack("MethodValue %s %v", T, sel)()
}
+ var m *Function
b := builder{created: &creator{}}
+
prog.methodsMu.Lock()
- m := prog.addMethod(prog.createMethodSet(T), sel, b.created)
+ // Checks whether a type param is reachable from T.
+ // This is an expensive check. May need to be optimized later.
+ if !prog.parameterized.isParameterized(T) {
+ m = prog.addMethod(prog.createMethodSet(T), sel, b.created)
+ }
prog.methodsMu.Unlock()
+ if m == nil {
+ return nil // abstract method (generic)
+ }
for !b.done() {
b.buildCreated()
b.needsRuntimeTypes()
@@ -64,6 +75,11 @@
// Precondition: T is a concrete type, e.g. !isInterface(T) and not parameterized.
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) createMethodSet(T types.Type) *methodSet {
+ if prog.mode&SanityCheckFunctions != 0 {
+ if isInterface(T) || prog.parameterized.isParameterized(T) {
+ panic("type is interface or parameterized")
+ }
+ }
mset, ok := prog.methodSets.At(T).(*methodSet)
if !ok {
mset = &methodSet{mapping: make(map[string]*Function)}
@@ -73,6 +89,7 @@
}
// Adds any created functions to cr.
+// Precondition: T is a concrete type, e.g. !isInterface(T) and not parameterized.
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection, cr *creator) *Function {
if sel.Kind() == types.MethodExpr {
@@ -144,7 +161,7 @@
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
// Precondition: T is not parameterized.
//
-// Thread-safe. (Called via emitConv from multiple builder goroutines.)
+// Thread-safe. (Called via Package.build from multiple builder goroutines.)
//
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
//
@@ -156,6 +173,7 @@
}
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
+// Precondition: T is not parameterized.
// Recursive case: skip => don't create methods for T.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
@@ -241,6 +259,12 @@
prog.needMethods(t.At(i).Type(), false, cr)
}
+ case *typeparams.TypeParam:
+ panic(T) // type parameters are always abstract.
+
+ case *typeparams.Union:
+ // nop
+
default:
panic(T)
}
diff --git a/go/ssa/methods_test.go b/go/ssa/methods_test.go
new file mode 100644
index 0000000..8391cf6
--- /dev/null
+++ b/go/ssa/methods_test.go
@@ -0,0 +1,96 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ssa_test
+
+import (
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "go/types"
+ "testing"
+
+ "golang.org/x/tools/go/ssa"
+ "golang.org/x/tools/go/ssa/ssautil"
+ "golang.org/x/tools/internal/typeparams"
+)
+
+// Tests that MethodValue returns the expected method.
+func TestMethodValue(t *testing.T) {
+ if !typeparams.Enabled {
+ t.Skip("TestMethodValue requires type parameters")
+ }
+ input := `
+package p
+
+type I interface{ M() }
+
+type S int
+func (S) M() {}
+type R[T any] struct{ S }
+
+var i I
+var s S
+var r R[string]
+
+func selections[T any]() {
+ _ = i.M
+ _ = s.M
+ _ = r.M
+
+ var v R[T]
+ _ = v.M
+}
+`
+
+ // Parse the file.
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "input.go", input, 0)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ // Build an SSA program from the parsed file.
+ p, info, err := ssautil.BuildPackage(&types.Config{}, fset,
+ types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ // Collect all of the *types.Selection in the function "selections".
+ var selections []*types.Selection
+ for _, decl := range f.Decls {
+ if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == "selections" {
+ for _, stmt := range fn.Body.List {
+ if assign, ok := stmt.(*ast.AssignStmt); ok {
+ sel := assign.Rhs[0].(*ast.SelectorExpr)
+ selections = append(selections, info.Selections[sel])
+ }
+ }
+ }
+ }
+
+ wants := map[string]string{
+ "method (p.S) M()": "(p.S).M",
+ "method (p.R[string]) M()": "(p.R[string]).M",
+ "method (p.I) M()": "nil", // interface
+ "method (p.R[T]) M()": "nil", // parameterized
+ }
+ if len(wants) != len(selections) {
+ t.Fatalf("Wanted %d selections. got %d", len(wants), len(selections))
+ }
+ for _, selection := range selections {
+ var got string
+ if m := p.Prog.MethodValue(selection); m != nil {
+ got = m.String()
+ } else {
+ got = "nil"
+ }
+ if want := wants[selection.String()]; want != got {
+ t.Errorf("p.Prog.MethodValue(%s) expected %q. got %q", selection, want, got)
+ }
+ }
+}
diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go
index 97c745b..fe3f497 100644
--- a/go/ssa/ssa.go
+++ b/go/ssa/ssa.go
@@ -30,12 +30,13 @@
canon *canonizer // type canonicalization map
ctxt *typeparams.Context // cache for type checking instantiations
- methodsMu sync.Mutex // guards the following maps:
- methodSets typeutil.Map // maps type to its concrete methodSet
- runtimeTypes typeutil.Map // types for which rtypes are needed
- bounds map[boundsKey]*Function // bounds for curried x.Method closures
- thunks map[selectionKey]*Function // thunks for T.Method expressions
- instances map[*Function]*instanceSet // instances of generic functions
+ methodsMu sync.Mutex // guards the following maps:
+ methodSets typeutil.Map // maps type to its concrete methodSet
+ runtimeTypes typeutil.Map // types for which rtypes are needed
+ bounds map[boundsKey]*Function // bounds for curried x.Method closures
+ thunks map[selectionKey]*Function // thunks for T.Method expressions
+ instances map[*Function]*instanceSet // instances of generic functions
+ parameterized tpWalker // determines whether a type is parameterized.
}
// A Package is a single analyzed Go package containing Members for