go/types: record inferred type arguments for parameterized calls

In api.go, introduce a new type `Inferred` which collects type arguments
and signature in one place and which can be extended if need be.

Change-Id: I014dd52641843e85ad408fdaa07eda121cf1c41c
This commit is contained in:
Robert Griesemer 2020-03-08 13:29:55 -07:00
parent 2eed31c177
commit 07a7684507
4 changed files with 98 additions and 28 deletions

View File

@ -171,10 +171,10 @@ type Info struct {
// qualified identifiers are collected in the Uses map.
Types map[ast.Expr]TypeAndValue
// Inferred maps calls of parameterized functions which use
// type inferrence to the inferred signature of the function
// called.
Inferred map[*ast.CallExpr]*Signature
// Inferred maps calls of parameterized functions that use
// type inferrence to the inferred type arguments and signature
// of the function called.
Inferred map[*ast.CallExpr]Inferred
// Defs maps identifiers to the objects they define (including
// package names, dots "." of dot-imports, and blank "_" identifiers).
@ -332,6 +332,13 @@ func (tv TypeAndValue) HasOk() bool {
return tv.mode == commaok || tv.mode == mapindex
}
// Inferred reports the inferred type arguments and signature
// for a parameterized function call that uses type inference.
type Inferred struct {
Targs []Type
Sig *Signature
}
// An Initializer describes a package-level variable, or a list of variables in case
// of a multi-valued initialization expression, and the corresponding initialization
// expression.

View File

@ -308,48 +308,111 @@ func TestTypesInfo(t *testing.T) {
func TestInferredInfo(t *testing.T) {
var tests = []struct {
src string
fun string
sig string
src string
fun string
targs []string
sig string
}{
{`package p0; func f(type T)(T); func _() { f(42) }`, `f`, `func(int)`},
{`package p1; func f(type T)(T) T; func _() { f('@') }`, `f`, `func(rune) rune`},
{`package p2; func f(type T)(...T) T; func _() { f(0i) }`, `f`, `func(...complex128) complex128`},
{`package p3; func f(type A, B, C)(A, *B, []C); func _() { f(1.2, new(string), []byte{}) }`, `f`, `func(float64, *string, []byte)`},
{`package p4; func f(type A, B)(A, *B, ...[]B); func _() { f(1.2, new(byte)) }`, `f`, `func(float64, *byte, ...[]byte)`},
{`package p0; func f(type T)(T); func _() { f(42) }`,
`f`,
[]string{`int`},
`func(int)`,
},
{`package p1; func f(type T)(T) T; func _() { f('@') }`,
`f`,
[]string{`rune`},
`func(rune) rune`,
},
{`package p2; func f(type T)(...T) T; func _() { f(0i) }`,
`f`,
[]string{`complex128`},
`func(...complex128) complex128`,
},
{`package p3; func f(type A, B, C)(A, *B, []C); func _() { f(1.2, new(string), []byte{}) }`,
`f`,
[]string{`float64`, `string`, `byte`},
`func(float64, *string, []byte)`,
},
{`package p4; func f(type A, B)(A, *B, ...[]B); func _() { f(1.2, new(byte)) }`,
`f`,
[]string{`float64`, `byte`},
`func(float64, *byte, ...[]byte)`,
},
// we don't know how to translate these but we can type-check them
{`package q0; type T struct{}; func (T) m(type P)(P); func _(x T) { x.m(42) }`, `x.m`, `func(int)`},
{`package q1; type T struct{}; func (T) m(type P)(P) P; func _(x T) { x.m(42) }`, `x.m`, `func(int) int`},
{`package q2; type T struct{}; func (T) m(type P)(...P) P; func _(x T) { x.m(42) }`, `x.m`, `func(...int) int`},
{`package q3; type T struct{}; func (T) m(type A, B, C)(A, *B, []C); func _(x T) { x.m(1.2, new(string), []byte{}) }`, `x.m`, `func(float64, *string, []byte)`},
{`package q4; type T struct{}; func (T) m(type A, B)(A, *B, ...[]B); func _(x T) { x.m(1.2, new(byte)) }`, `x.m`, `func(float64, *byte, ...[]byte)`},
{`package q0; type T struct{}; func (T) m(type P)(P); func _(x T) { x.m(42) }`,
`x.m`,
[]string{`int`},
`func(int)`,
},
{`package q1; type T struct{}; func (T) m(type P)(P) P; func _(x T) { x.m(42) }`,
`x.m`,
[]string{`int`},
`func(int) int`,
},
{`package q2; type T struct{}; func (T) m(type P)(...P) P; func _(x T) { x.m(42) }`,
`x.m`,
[]string{`int`},
`func(...int) int`,
},
{`package q3; type T struct{}; func (T) m(type A, B, C)(A, *B, []C); func _(x T) { x.m(1.2, new(string), []byte{}) }`,
`x.m`,
[]string{`float64`, `string`, `byte`},
`func(float64, *string, []byte)`,
},
{`package q4; type T struct{}; func (T) m(type A, B)(A, *B, ...[]B); func _(x T) { x.m(1.2, new(byte)) }`,
`x.m`,
[]string{`float64`, `byte`},
`func(float64, *byte, ...[]byte)`,
},
{`package r0; type T(type P) struct{}; func (_ T(P)) m(type Q)(Q); func _(type P)(x T(P)) { x.m(42) }`, `x.m`, `func(int)`},
{`package r1; type T interface{ m(type P)(P) }; func _(x T) { x.m(4.2) }`, `x.m`, `func(float64)`},
{`package r0; type T(type P) struct{}; func (_ T(P)) m(type Q)(Q); func _(type P)(x T(P)) { x.m(42) }`,
`x.m`,
[]string{`int`},
`func(int)`,
},
{`package r1; type T interface{ m(type P)(P) }; func _(x T) { x.m(4.2) }`,
`x.m`,
[]string{`float64`},
`func(float64)`,
},
}
for _, test := range tests {
info := Info{Inferred: make(map[*ast.CallExpr]*Signature)}
info := Info{Inferred: make(map[*ast.CallExpr]Inferred)}
name, err := mayTypecheck(t, "InferredInfo", test.src, &info)
if err != nil {
t.Errorf("package %s: %v", name, err)
continue
}
// look for inferred signature
// look for inferred type arguments and signature
var targs []Type
var sig *Signature
for call, typ := range info.Inferred {
for call, inf := range info.Inferred {
if ExprString(call.Fun) == test.fun {
sig = typ
targs = inf.Targs
sig = inf.Sig
break
}
}
if sig == nil {
t.Errorf("package %s: no signature found for %s", name, test.fun)
if targs == nil {
t.Errorf("package %s: no inferred information found for %s", name, test.fun)
continue
}
// check that type arguments are correct
if len(targs) != len(test.targs) {
t.Errorf("package %s: got %d type arguments; want %d", name, len(targs), len(test.targs))
continue
}
for i, targ := range targs {
if got := targ.String(); got != test.targs[i] {
t.Errorf("package %s, %d. type argument: got %s; want %s", name, i, got, test.targs[i])
continue
}
}
// check that signature is correct
if got := sig.String(); got != test.sig {
t.Errorf("package %s: got %s; want %s", name, got, test.sig)

View File

@ -324,7 +324,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, args []*oper
// compute result signature
rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature)
assert(rsig.tparams == nil) // signature is not generic anymore
check.recordInferred(call, rsig)
check.recordInferred(call, targs, rsig)
// Optimization: Only if the parameter list was adjusted do we
// need to compute it from the adjusted list; otherwise we can

View File

@ -396,11 +396,11 @@ func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
}
}
func (check *Checker) recordInferred(call *ast.CallExpr, sig *Signature) {
func (check *Checker) recordInferred(call *ast.CallExpr, targs []Type, sig *Signature) {
assert(call != nil)
assert(sig != nil)
if m := check.Inferred; m != nil {
m[call] = sig
m[call] = Inferred{targs, sig}
}
}