go/types: stronger type inference across defined types

If a defined type is passed as an argument for a paramater
of a composite generic but unnamed type (and vice versa),
use the defined type's underlying type for type inference
rather than fail.

Passes all.bash.

Change-Id: I90b33b549c1d0e7569c1520b0f8e45a67152df43
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go2-dev/+/770149
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2020-06-12 16:12:46 -07:00
parent e7b04c5e2d
commit 795f776602
5 changed files with 96 additions and 20 deletions

View File

@ -161,3 +161,44 @@ func _() {
ffsend(recv /* ERROR cannot use */ )
ffsend(send)
}
// When inferring elements of unnamed composite parameter types,
// if the arguments are defined types, use their underlying types.
// Even though the matching types are not exactly structurally the
// same (one is a type literal, the other a named type), because
// assignment is permitted, parameter passing is permitted as well,
// so type inference should be able to handle these cases well.
func g1(type T)([]T)
func g2(type T)([]T, T)
func g3(type T)(*T, ...T)
func _() {
type intSlize []int
g1([]int{})
g1(intSlize{})
g2(nil, 0)
type myString string
var s1 string
g3(nil, "1", myString("2"), "3")
g3(&s1, "1", myString /* ERROR does not match */ ("2"), "3")
type myStruct struct{x int}
var s2 myStruct
g3(nil, struct{x int}{}, myStruct{})
g3(&s2, struct{x int}{}, myStruct{})
g3(nil, myStruct{}, struct{x int}{})
g3(&s2, myStruct{}, struct{x int}{})
}
// Here's a realistic example.
func append(type T)(s []T, t ...T) []T
func _() {
var f func()
type Funcs []func()
var funcs Funcs
_ = append(funcs, f)
}

View File

@ -15,7 +15,7 @@ import "go/token"
func (check *Checker) infer(pos token.Pos, tparams []*TypeName, params *Tuple, args []*operand) []Type {
assert(params.Len() == len(args))
u := check.unifier()
u := check.newUnifier(false)
u.x.init(tparams)
errorf := func(kind string, tpar, targ Type, arg *operand) {

View File

@ -334,7 +334,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
// to see if they can be made to match.
// TODO(gri) is this always correct? what about type bounds?
// (Alternative is to rename/subst type parameters and compare.)
u := check.unifier()
u := check.newUnifier(true)
u.x.init(mtyp.tparams)
if !u.unify(ftyp, mtyp) {
return m, f
@ -398,7 +398,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method,
// to see if they can be made to match.
// TODO(gri) is this always correct? what about type bounds?
// (Alternative is to rename/subst type parameters and compare.)
u := check.unifier()
u := check.newUnifier(true)
u.x.init(mtyp.tparams)
if !u.unify(ftyp, mtyp) {
return m, f

View File

@ -241,11 +241,26 @@ func NewTuple(x ...*Var) *Tuple {
return nil
}
// We cannot rely on the embedded Basic() method because (*Tuple)(nil)
// is a valid *Tuple value but (*Tuple)(nil).Basic() would panic without
// this implementation.
// TODO(gri) It seems that at the moment we only need this converter.
func (*Tuple) Basic() *Basic { return nil }
// We cannot rely on the embedded X() *X methods because (*Tuple)(nil)
// is a valid *Tuple value but (*Tuple)(nil).X() would panic without
// these implementations. At the moment we only need X = Basic, Named,
// but add all because missing one leads to very confusing bugs.
// TODO(gri) Don't represent empty tuples with a (*Tuple)(nil) pointer;
// it's too subtle and causes problems.
func (*Tuple) Basic() *Basic { return nil }
func (*Tuple) Array() *Array { return nil }
func (*Tuple) Slice() *Slice { return nil }
func (*Tuple) Struct() *Struct { return nil }
func (*Tuple) Pointer() *Pointer { return nil }
// func (*Tuple) Tuple() *Tuple // implemented below
func (*Tuple) Signature() *Signature { return nil }
func (*Tuple) Sum() *Sum { return nil }
func (*Tuple) Interface() *Interface { return nil }
func (*Tuple) Map() *Map { return nil }
func (*Tuple) Chan() *Chan { return nil }
func (*Tuple) Named() *Named { return nil }
func (*Tuple) TypeParam() *TypeParam { return nil }
// Len returns the number variables of tuple t.
func (t *Tuple) Len() int {

View File

@ -16,13 +16,18 @@ import (
// A uninifier is created by calling Checker.unifier.
type unifier struct {
check *Checker
exact bool
x, y typeDesc // x and y must initialized via typeDesc.init
types []Type // inferred types, shared by x and y
}
// unifier returns a new unifier.
func (check *Checker) unifier() *unifier {
u := &unifier{check: check}
// newUnifier returns a new unifier.
// If exact is set, unification requires unified types to match
// exactly. If exact is not set, a named type's underlying type
// is considered if unification would fail otherwise, and the
// direction of channels is ignored.
func (check *Checker) newUnifier(exact bool) *unifier {
u := &unifier{check: check, exact: exact}
u.x.uplink = u
u.y.uplink = u
return u
@ -68,12 +73,12 @@ func (d *typeDesc) set(i int, typ Type) {
d.indices[i] = len(u.types)
}
// If typ is a type parameter in tparams, index returns the
// corresponding tparams index. Otherwise, the result is < 0.
func (u *unifier) index(tparams []*TypeName, typ Type) int {
// If typ is a type parameter in d.tparams, index returns the
// corresponding d.tparams index. Otherwise, the result is < 0.
func (d *typeDesc) index(typ Type) int {
if t, ok := typ.(*TypeParam); ok {
// typ is a type parameter; check that it belongs to the (enclosing) type
if i := t.index; i < len(tparams) && tparams[i].typ == t {
if i := t.index; i < len(d.tparams) && d.tparams[i].typ == t {
return i
}
}
@ -89,9 +94,24 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
x = expand(x)
y = expand(y)
if !u.exact {
// If exact unification is known to fail because we attempt to
// match a type name against an unnamed type literal, consider
// the underlying type of the named type.
// (Subtle: We use isNamed to include any type with a name (incl.
// basic types and type parameters. We use Named() because we only
// want *Named types.)
switch {
case !isNamed(x) && y != nil && y.Named() != nil:
return u.nify(x, y.Under(), p)
case x != nil && x.Named() != nil && !isNamed(y):
return u.nify(x.Under(), y, p)
}
}
//u.check.dump("### u.nify(%s, %s)", x, y)
i := u.index(u.x.tparams, x)
j := u.index(u.y.tparams, y)
i := u.x.index(x)
j := u.y.index(y)
switch {
case i >= 0 && j >= 0:
//u.check.dump("### i = %d, j = %d", i, j)
@ -313,9 +333,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
case *Chan:
// Two channel types are identical if they have identical value types.
// For type unification, channel direction is ignored.
if y, ok := y.(*Chan); ok {
return u.nify(x.elem, y.elem, p)
return (!u.exact || x.dir == y.dir) && u.nify(x.elem, y.elem, p)
}
case *Named:
@ -341,7 +360,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
case *TypeParam:
// Two type parameters (which are not part of the type parameters of the
// enclosing type) are identical if they originate in the same declaration.
// enclosing type as those are handled in the beginning of this function)
// are identical if they originate in the same declaration.
return x == y
// case *instance: