From 795f77660230b38116be0934522306bd4e7c0eb8 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 12 Jun 2020 16:12:46 -0700 Subject: [PATCH] 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 --- src/go/types/examples/functions.go2 | 41 +++++++++++++++++++++++++++ src/go/types/infer.go | 2 +- src/go/types/lookup.go | 4 +-- src/go/types/type.go | 25 ++++++++++++---- src/go/types/unify.go | 44 +++++++++++++++++++++-------- 5 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/go/types/examples/functions.go2 b/src/go/types/examples/functions.go2 index 2b538c2ff2..396ddca452 100644 --- a/src/go/types/examples/functions.go2 +++ b/src/go/types/examples/functions.go2 @@ -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) +} diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 80afecdd24..c30b63c9fd 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -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) { diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index 8af5ebcfa0..f4d40ed535 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -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 diff --git a/src/go/types/type.go b/src/go/types/type.go index 4304f240c0..c6272a4c4c 100644 --- a/src/go/types/type.go +++ b/src/go/types/type.go @@ -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 { diff --git a/src/go/types/unify.go b/src/go/types/unify.go index 5f4c6f6924..1598c3dc65 100644 --- a/src/go/types/unify.go +++ b/src/go/types/unify.go @@ -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: