go/types: fix type inference bugs for untyped arguments

Change-Id: Ic98be8b102521090108d288514ee2cf016a7bb46
This commit is contained in:
Robert Griesemer 2019-08-02 23:26:07 -07:00
parent d03ba9f74c
commit a65d9b58a9
4 changed files with 70 additions and 48 deletions

View File

@ -10,54 +10,74 @@ package types
import "go/token"
// infer returns the list of actual type arguments for the given list of type parameters tparams
// by inferring them from the actual arguments args for the parameters pars. If infer fails to
// by inferring them from the actual arguments args for the parameters params. If infer fails to
// determine all type arguments, an error is reported and the result is nil.
func (check *Checker) infer(pos token.Pos, tparams []*TypeName, params *Tuple, args []*operand) []Type {
assert(params.Len() == len(args))
// targs is the list of inferred type parameter types.
targs := make([]Type, len(tparams))
// determine indices of type-parametrized parameters
// Terminology: TPP = type-parameterized function parameter
// 1st pass: Unify parameter and argument types for TPPs with typed arguments
// and collect the indices of TPPs with untyped arguments.
var indices []int
for i := 0; i < params.Len(); i++ {
par := params.At(i).typ
if isParameterized(par) {
indices = append(indices, i)
for i, arg := range args {
par := params.At(i)
if isParameterized(par.typ) {
if arg.mode == invalid {
// TODO(gri) we might still be able to infer all targs by
// simply ignoring (continue) invalid args
return nil // error was reported earlier
}
if isTyped(arg.typ) {
if !check.identical0(par.typ, arg.typ, true, nil, targs) {
check.errorf(arg.pos(), "type %s for %s does not match %s = %s",
arg.typ, arg.expr, par.typ, check.subst(par.typ, tparams, targs),
)
return nil
}
} else {
indices = append(indices, i)
}
}
}
// 1st pass: unify parameter and argument types for typed arguments
// Some of the TPPs with untyped arguments may have been given a type
// indirectly via a TPP with a typed argument; we can ignore those now.
j := 0
for _, i := range indices {
arg := args[i]
if arg.mode == invalid {
// TODO(gri) we might still be able to infer all targs by
// simply ignoring (continue) invalid args
return nil // error was reported earlier
}
if isUntyped(arg.typ) {
continue // handled in 2nd pass
}
par := params.At(i)
if !check.identical0(par.typ, arg.typ, true, nil, targs) {
check.errorf(arg.pos(), "type %s for %s does not match %s = %s", arg.typ, arg.expr, par.typ, check.subst(par.typ, tparams, targs))
// Since untyped types are all basic (i.e., unstructured) types, an
// untyped argument will never match a structured parameter type; the
// only parameter type it can possibly match against is a *TypeParam.
// Thus, only keep the indices of TPPs that are unstructured and which
// don't have a type inferred yet.
if tpar, _ := par.typ.(*TypeParam); tpar != nil && targs[tpar.index] == nil {
indices[j] = i
j++
}
}
indices = indices[:j]
// 2nd pass: Unify parameter and default argument types for remaining TPPs.
for _, i := range indices {
par := params.At(i)
arg := args[i]
targ := Default(arg.typ)
// The default type for an untyped nil is untyped nil. We must not
// infer an untyped nil type as type parameter type. Ignore untyped
// nil by making sure all default argument types are typed.
if isTyped(targ) && !check.identical0(par.typ, targ, true, nil, targs) {
check.errorf(arg.pos(), "default type %s for %s does not match %s = %s",
Default(arg.typ), arg.expr, par.typ, check.subst(par.typ, tparams, targs),
)
return nil
}
}
// 2nd pass: unify parameter and default argument types for remaining parametrized parameter types with untyped arguments
for _, i := range indices {
arg := args[i]
if isTyped(arg.typ) {
continue // handled in 1st pass
}
par := params.At(i)
if !check.identical0(par.typ, Default(arg.typ), true, nil, targs) {
check.errorf(arg.pos(), "default type %s for %s does not match %s = %s", Default(arg.typ), arg.expr, par.typ, check.subst(par.typ, tparams, targs))
return nil
}
}
// check if all type parameters have been determined
// Check if all type parameters have been determined.
// TODO(gri) consider moving this outside this function and then we won't need to pass in pos
for i, t := range targs {
if t == nil {

View File

@ -52,7 +52,8 @@ func reducer(x float64, y int) float64 {
}
var reduced1 = Reduce(int, float64)(input, 0, reducer)
var reduced2 = Reduce(input, 1.0, reducer) // using type inference
var reduced2 = Reduce(input, 1i /* ERROR overflows */, reducer) // using type inference
var reduced2 = Reduce(input, 1, reducer) // using type inference
func filter(x int) bool {
return x&1 != 0

View File

@ -1,18 +1,13 @@
package p
func Reduce(type T1, T2)(s []T1, initializer T2, f func(T2, T1) T2) T2 {
r := initializer
for _, v := range s {
r = f(r, v)
}
return r
}
func f(type T)([]*T) int
func reducer(float64, int) float64 {
return 0
}
var _ = f(nil) // ERROR cannot infer
var _ = Reduce([]int{}, 1.0, reducer)
func g(type T)(T) int
// TODO(gri) investigate - seems like type inference should accept 1 here
// var _ = Reduce([]int{}, 1, reducer)
var _ = g(nil) // ERROR cannot infer
func h(type T)(T, T) int
var _ = h(nil /* ERROR cannot convert */, uintptr(1))

View File

@ -82,12 +82,16 @@ var _ = f4(int, float32, complex128)(1, 2)
func f5(type A, B, C)(A, []*B, struct{f []C}) int
var _ = f5(int, float32, complex128)(0, nil, struct{f []complex128}{})
var _ = f5(0, nil /* ERROR untyped nil */, struct{f []complex128}{}) // <<<< TODO(gri) need better error message
var _ = f5(0, nil, struct{f []complex128}{}) // ERROR cannot infer
var _ = f5(0, []*float32{new(float32)()}, struct{f []complex128}{})
func f6(type A)(A, []A) int
var _ = f6(0, nil /* ERROR does not match */ ) // <<<< TODO(gri) should this be accepted?
var _ = f6(0, nil)
func f6nil(type A)(A) int
var _ = f6nil(nil) // ERROR cannot infer
// type inference with variadic functions
@ -99,6 +103,8 @@ var _ int = f7(1, 2)
var _ int = f7([]int{}...)
var _ int = f7 /* ERROR cannot use */ ([]float64{}...)
var _ float64 = f7([]float64{}...)
var _ = f7(float64)(1, 2.3)
var _ = f7(float64(1), 2.3)
var _ = f7(1, 2.3 /* ERROR does not match */ )
var _ = f7(1.2, 3 /* ERROR does not match */ )