Skip to content

Commit 5d7ca8a

Browse files
committed
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>
1 parent 48a2cc8 commit 5d7ca8a

File tree

4 files changed

+141
-19
lines changed

4 files changed

+141
-19
lines changed

go/ssa/create.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,16 @@ import (
2424
// mode controls diagnostics and checking during SSA construction.
2525
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
2626
prog := &Program{
27-
Fset: fset,
28-
imported: make(map[string]*Package),
29-
packages: make(map[*types.Package]*Package),
30-
thunks: make(map[selectionKey]*Function),
31-
bounds: make(map[boundsKey]*Function),
32-
mode: mode,
33-
canon: newCanonizer(),
34-
ctxt: typeparams.NewContext(),
35-
instances: make(map[*Function]*instanceSet),
27+
Fset: fset,
28+
imported: make(map[string]*Package),
29+
packages: make(map[*types.Package]*Package),
30+
thunks: make(map[selectionKey]*Function),
31+
bounds: make(map[boundsKey]*Function),
32+
mode: mode,
33+
canon: newCanonizer(),
34+
ctxt: typeparams.NewContext(),
35+
instances: make(map[*Function]*instanceSet),
36+
parameterized: tpWalker{seen: make(map[types.Type]bool)},
3637
}
3738

3839
h := typeutil.MakeHasher() // protected by methodsMu, in effect

go/ssa/methods.go

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ package ssa
99
import (
1010
"fmt"
1111
"go/types"
12+
13+
"golang.org/x/tools/internal/typeparams"
1214
)
1315

1416
// MethodValue returns the Function implementing method sel, building
1517
// wrapper methods on demand. It returns nil if sel denotes an
16-
// abstract (interface) method.
18+
// abstract (interface or parameterized) method.
1719
//
1820
// Precondition: sel.Kind() == MethodVal.
1921
//
@@ -26,17 +28,26 @@ func (prog *Program) MethodValue(sel *types.Selection) *Function {
2628
}
2729
T := sel.Recv()
2830
if isInterface(T) {
29-
return nil // abstract method
31+
return nil // abstract method (interface)
3032
}
3133
if prog.mode&LogSource != 0 {
3234
defer logStack("MethodValue %s %v", T, sel)()
3335
}
3436

37+
var m *Function
3538
b := builder{created: &creator{}}
39+
3640
prog.methodsMu.Lock()
37-
m := prog.addMethod(prog.createMethodSet(T), sel, b.created)
41+
// Checks whether a type param is reachable from T.
42+
// This is an expensive check. May need to be optimized later.
43+
if !prog.parameterized.isParameterized(T) {
44+
m = prog.addMethod(prog.createMethodSet(T), sel, b.created)
45+
}
3846
prog.methodsMu.Unlock()
3947

48+
if m == nil {
49+
return nil // abstract method (generic)
50+
}
4051
for !b.done() {
4152
b.buildCreated()
4253
b.needsRuntimeTypes()
@@ -64,6 +75,11 @@ type methodSet struct {
6475
// Precondition: T is a concrete type, e.g. !isInterface(T) and not parameterized.
6576
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
6677
func (prog *Program) createMethodSet(T types.Type) *methodSet {
78+
if prog.mode&SanityCheckFunctions != 0 {
79+
if isInterface(T) || prog.parameterized.isParameterized(T) {
80+
panic("type is interface or parameterized")
81+
}
82+
}
6783
mset, ok := prog.methodSets.At(T).(*methodSet)
6884
if !ok {
6985
mset = &methodSet{mapping: make(map[string]*Function)}
@@ -73,6 +89,7 @@ func (prog *Program) createMethodSet(T types.Type) *methodSet {
7389
}
7490

7591
// Adds any created functions to cr.
92+
// Precondition: T is a concrete type, e.g. !isInterface(T) and not parameterized.
7693
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
7794
func (prog *Program) addMethod(mset *methodSet, sel *types.Selection, cr *creator) *Function {
7895
if sel.Kind() == types.MethodExpr {
@@ -144,7 +161,7 @@ func (prog *Program) declaredFunc(obj *types.Func) *Function {
144161
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
145162
// Precondition: T is not parameterized.
146163
//
147-
// Thread-safe. (Called via emitConv from multiple builder goroutines.)
164+
// Thread-safe. (Called via Package.build from multiple builder goroutines.)
148165
//
149166
// TODO(adonovan): make this faster. It accounts for 20% of SSA build time.
150167
//
@@ -156,6 +173,7 @@ func (prog *Program) needMethodsOf(T types.Type, cr *creator) {
156173
}
157174

158175
// Precondition: T is not a method signature (*Signature with Recv()!=nil).
176+
// Precondition: T is not parameterized.
159177
// Recursive case: skip => don't create methods for T.
160178
//
161179
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
@@ -241,6 +259,12 @@ func (prog *Program) needMethods(T types.Type, skip bool, cr *creator) {
241259
prog.needMethods(t.At(i).Type(), false, cr)
242260
}
243261

262+
case *typeparams.TypeParam:
263+
panic(T) // type parameters are always abstract.
264+
265+
case *typeparams.Union:
266+
// nop
267+
244268
default:
245269
panic(T)
246270
}

go/ssa/methods_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2022 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 ssa_test
6+
7+
import (
8+
"go/ast"
9+
"go/parser"
10+
"go/token"
11+
"go/types"
12+
"testing"
13+
14+
"golang.org/x/tools/go/ssa"
15+
"golang.org/x/tools/go/ssa/ssautil"
16+
"golang.org/x/tools/internal/typeparams"
17+
)
18+
19+
// Tests that MethodValue returns the expected method.
20+
func TestMethodValue(t *testing.T) {
21+
if !typeparams.Enabled {
22+
t.Skip("TestMethodValue requires type parameters")
23+
}
24+
input := `
25+
package p
26+
27+
type I interface{ M() }
28+
29+
type S int
30+
func (S) M() {}
31+
type R[T any] struct{ S }
32+
33+
var i I
34+
var s S
35+
var r R[string]
36+
37+
func selections[T any]() {
38+
_ = i.M
39+
_ = s.M
40+
_ = r.M
41+
42+
var v R[T]
43+
_ = v.M
44+
}
45+
`
46+
47+
// Parse the file.
48+
fset := token.NewFileSet()
49+
f, err := parser.ParseFile(fset, "input.go", input, 0)
50+
if err != nil {
51+
t.Error(err)
52+
return
53+
}
54+
55+
// Build an SSA program from the parsed file.
56+
p, info, err := ssautil.BuildPackage(&types.Config{}, fset,
57+
types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
58+
if err != nil {
59+
t.Error(err)
60+
return
61+
}
62+
63+
// Collect all of the *types.Selection in the function "selections".
64+
var selections []*types.Selection
65+
for _, decl := range f.Decls {
66+
if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == "selections" {
67+
for _, stmt := range fn.Body.List {
68+
if assign, ok := stmt.(*ast.AssignStmt); ok {
69+
sel := assign.Rhs[0].(*ast.SelectorExpr)
70+
selections = append(selections, info.Selections[sel])
71+
}
72+
}
73+
}
74+
}
75+
76+
wants := map[string]string{
77+
"method (p.S) M()": "(p.S).M",
78+
"method (p.R[string]) M()": "(p.R[string]).M",
79+
"method (p.I) M()": "nil", // interface
80+
"method (p.R[T]) M()": "nil", // parameterized
81+
}
82+
if len(wants) != len(selections) {
83+
t.Fatalf("Wanted %d selections. got %d", len(wants), len(selections))
84+
}
85+
for _, selection := range selections {
86+
var got string
87+
if m := p.Prog.MethodValue(selection); m != nil {
88+
got = m.String()
89+
} else {
90+
got = "nil"
91+
}
92+
if want := wants[selection.String()]; want != got {
93+
t.Errorf("p.Prog.MethodValue(%s) expected %q. got %q", selection, want, got)
94+
}
95+
}
96+
}

go/ssa/ssa.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@ type Program struct {
3030
canon *canonizer // type canonicalization map
3131
ctxt *typeparams.Context // cache for type checking instantiations
3232

33-
methodsMu sync.Mutex // guards the following maps:
34-
methodSets typeutil.Map // maps type to its concrete methodSet
35-
runtimeTypes typeutil.Map // types for which rtypes are needed
36-
bounds map[boundsKey]*Function // bounds for curried x.Method closures
37-
thunks map[selectionKey]*Function // thunks for T.Method expressions
38-
instances map[*Function]*instanceSet // instances of generic functions
33+
methodsMu sync.Mutex // guards the following maps:
34+
methodSets typeutil.Map // maps type to its concrete methodSet
35+
runtimeTypes typeutil.Map // types for which rtypes are needed
36+
bounds map[boundsKey]*Function // bounds for curried x.Method closures
37+
thunks map[selectionKey]*Function // thunks for T.Method expressions
38+
instances map[*Function]*instanceSet // instances of generic functions
39+
parameterized tpWalker // determines whether a type is parameterized.
3940
}
4041

4142
// A Package is a single analyzed Go package containing Members for

0 commit comments

Comments
 (0)