1
0
mirror of https://github.com/golang/go synced 2024-07-03 00:40:45 +00:00

go/types, types2: correctly consider ~ (tilde) in constraint type inference

When doing constraint type inference, we must consider whether the
constraint's core type is precise (no tilde) or imprecise (tilde,
or not a single specific type). In the latter case, we cannot infer
an unknown type argument from the (imprecise) core type because there
are infinitely many possible types. For instance, given

        [E ~byte]

if we don't know E, we cannot infer that E must be byte (it could be
myByte, etc.). On the other hand, if we do know the type argument,
say for S in this example:

        [S ~[]E, E any]

we must consider the underlying type of S when matching against ~[]E
because we have a tilde.

Because constraint type inference may infer type arguments that were
not eligible initially (because they were unknown and the core type
is imprecise), we must iterate the process until nothing changes any-
more. For instance, given

        [S ~[]E, M ~map[string]S, E any]

where we initially only know the type argument for M, we must ignore
S (and E) at first. After one iteration of constraint type inference,
S is known at which point we can infer E as well.

The change is large-ish but the actual functional changes are small:

- There's a new method "unknowns" to determine the number of as of yet
  unknown type arguments.

- The adjCoreType function has been adjusted to also return tilde
  and single-type information. This is now conveniently returned
  as (*term, bool), and the function has been renamed to coreTerm.

- The original constraint type inference loop has been adjusted to
  consider tilde information.

- This adjusted original constraint type inference loop has been
  nested in another loop for iteration, together with some minimal
  logic to control termination.

The remaining changes are modifications to tests:

- There's a substantial new test for this issue.

- Several existing test cases were adjusted to accomodate the
  fact that they inferred incorrect types: tildes have been
  removed throughout. Most of these tests are for pathological
  cases.

- A couple of tests were adjusted where there was a difference
  between the go/types and types2 version.

Fixes #51229.

Change-Id: If0bf5fb70ec22913b5a2da89adbf8a27fbc921d9
Reviewed-on: https://go-review.googlesource.com/c/go/+/387977
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Robert Griesemer 2022-02-24 22:11:40 -08:00
parent aaa3d39f27
commit 0807986fe6
21 changed files with 650 additions and 142 deletions

View File

@ -474,52 +474,54 @@ func TestInstanceInfo(t *testing.T) {
// `func(float64)`,
// },
{`package s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`,
{`package s1; func f[T any, P interface{*T}](x T) {}; func _(x string) { f(x) }`,
`f`,
[]string{`string`, `*string`},
`func(x string)`,
},
{`package s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`,
{`package s2; func f[T any, P interface{*T}](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `*int`},
`func(x []int)`,
},
{`package s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
{`package s3; type C[T any] interface{chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `chan<- int`},
`func(x []int)`,
},
{`package s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
{`package s4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func(x []int)`,
},
{`package t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`,
{`package t1; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = f[string] }`,
`f`,
[]string{`string`, `*string`},
`func() string`,
},
{`package t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
{`package t2; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
`f`,
[]string{`string`, `*string`},
`func() string`,
},
{`package t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
{`package t3; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
},
{`package t4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
{`package t4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = (f[int]) }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
},
{`package i0; import lib "generic_lib"; func _() { lib.F(42) }`,
{`package i0; import "lib"; func _() { lib.F(42) }`,
`F`,
[]string{`int`},
`func(int)`,
},
{`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`,
`T`,
[]string{`int`},
@ -540,7 +542,7 @@ func TestInstanceInfo(t *testing.T) {
[]string{`[]int`, `int`},
`struct{x []int; y int}`,
},
{`package type4; import lib "generic_lib"; var _ lib.T[int]`,
{`package type4; import "lib"; var _ lib.T[int]`,
`T`,
[]string{`int`},
`[]int`,
@ -548,7 +550,7 @@ func TestInstanceInfo(t *testing.T) {
}
for _, test := range tests {
const lib = `package generic_lib
const lib = `package lib
func F[P any](P) {}

View File

@ -488,21 +488,88 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type)
}
}
// If a constraint has a core type, unify the corresponding type parameter with it.
for _, tpar := range tparams {
if ctype := adjCoreType(tpar); ctype != nil {
if !u.unify(tpar, ctype) {
// TODO(gri) improve error message by providing the type arguments
// which we know already
check.errorf(pos, "%s does not match %s", tpar, ctype)
return nil, 0
// Repeatedly apply constraint type inference as long as
// there are still unknown type arguments and progress is
// being made.
//
// This is an O(n^2) algorithm where n is the number of
// type parameters: if there is progress (and iteration
// continues), at least one type argument is inferred
// per iteration and we have a doubly nested loop.
// In practice this is not a problem because the number
// of type parameters tends to be very small (< 5 or so).
// (It should be possible for unification to efficiently
// signal newly inferred type arguments; then the loops
// here could handle the respective type parameters only,
// but that will come at a cost of extra complexity which
// may not be worth it.)
for n := u.x.unknowns(); n > 0; {
nn := n
for i, tpar := range tparams {
// If there is a core term (i.e., a core type with tilde information)
// unify the type parameter with the core type.
if core, single := coreTerm(tpar); core != nil {
// A type parameter can be unified with its core type in two cases.
tx := u.x.at(i)
switch {
case tx != nil:
// The corresponding type argument tx is known.
// In this case, if the core type has a tilde, the type argument's underlying
// type must match the core type, otherwise the type argument and the core type
// must match.
// If tx is an external type parameter, don't consider its underlying type
// (which is an interface). Core type unification will attempt to unify against
// core.typ.
// Note also that even with inexact unification we cannot leave away the under
// call here because it's possible that both tx and core.typ are named types,
// with under(tx) being a (named) basic type matching core.typ. Such cases do
// not match with inexact unification.
if core.tilde && !isTypeParam(tx) {
tx = under(tx)
}
if !u.unify(tx, core.typ) {
// TODO(gri) improve error message by providing the type arguments
// which we know already
// Don't use term.String() as it always qualifies types, even if they
// are in the current package.
tilde := ""
if core.tilde {
tilde = "~"
}
check.errorf(pos, "%s does not match %s%s", tpar, tilde, core.typ)
return nil, 0
}
case single && !core.tilde:
// The corresponding type argument tx is unknown and there's a single
// specific type and no tilde.
// In this case the type argument must be that single type; set it.
u.x.set(i, core.typ)
default:
// Unification is not possible and no progress was made.
continue
}
// The number of known type arguments may have changed.
nn = u.x.unknowns()
if nn == 0 {
break // all type arguments are known
}
}
}
assert(nn <= n)
if nn == n {
break // no progress
}
n = nn
}
// u.x.types() now contains the incoming type arguments plus any additional type
// arguments which were inferred from core types. The newly inferred non-
// nil entries may still contain references to other type parameters.
// arguments which were inferred from core terms. The newly inferred non-nil
// entries may still contain references to other type parameters.
// For instance, for [A any, B interface{ []C }, C interface{ *A }], if A == int
// was given, unification produced the type list [int, []C, *A]. We eliminate the
// remaining type parameters by substituting the type parameters in this type list
@ -591,26 +658,40 @@ func (check *Checker) inferB(pos syntax.Pos, tparams []*TypeParam, targs []Type)
return
}
// adjCoreType returns the core type of tpar unless the
// type parameter embeds a single, possibly named type,
// in which case it returns that single type instead.
// (The core type is always the underlying type of that
// single type.)
func adjCoreType(tpar *TypeParam) Type {
var single *term
if tpar.is(func(t *term) bool {
if single == nil && t != nil {
single = t
return true
// If the type parameter has a single specific type S, coreTerm returns (S, true).
// Otherwise, if tpar has a core type T, it returns a term corresponding to that
// core type and false. In that case, if any term of tpar has a tilde, the core
// term has a tilde. In all other cases coreTerm returns (nil, false).
func coreTerm(tpar *TypeParam) (*term, bool) {
n := 0
var single *term // valid if n == 1
var tilde bool
tpar.is(func(t *term) bool {
if t == nil {
assert(n == 0)
return false // no terms
}
return false // zero or more than one terms
}) {
n++
single = t
if t.tilde {
tilde = true
}
return true
})
if n == 1 {
if debug {
assert(under(single.typ) == coreType(tpar))
assert(debug && under(single.typ) == coreType(tpar))
}
return single.typ
return single, true
}
return coreType(tpar)
if typ := coreType(tpar); typ != nil {
// A core type is always an underlying type.
// If any term of tpar has a tilde, we don't
// have a precise core type and we must return
// a tilde as well.
return &term{tilde, typ}, false
}
return nil, false
}
type cycleFinder struct {

View File

@ -8,21 +8,21 @@ import "strconv"
type any interface{}
func f0[A any, B interface{~*C}, C interface{~*D}, D interface{~*A}](A, B, C, D) {}
func f0[A any, B interface{*C}, C interface{*D}, D interface{*A}](A, B, C, D) {}
func _() {
f := f0[string]
f("a", nil, nil, nil)
f0("a", nil, nil, nil)
}
func f1[A any, B interface{~*A}](A, B) {}
func f1[A any, B interface{*A}](A, B) {}
func _() {
f := f1[int]
f(int(0), new(int))
f1(int(0), new(int))
}
func f2[A any, B interface{~[]A}](A, B) {}
func f2[A any, B interface{[]A}](A, B) {}
func _() {
f := f2[byte]
f(byte(0), []byte{})
@ -38,7 +38,7 @@ func _() {
// f3(x, &x, &x)
// }
func f4[A any, B interface{~[]C}, C interface{~*A}](A, B, C) {}
func f4[A any, B interface{[]C}, C interface{*A}](A, B, C) {}
func _() {
f := f4[int]
var x int
@ -46,7 +46,7 @@ func _() {
f4(x, []*int{}, &x)
}
func f5[A interface{~struct{b B; c C}}, B any, C interface{~*B}](x B) A { panic(0) }
func f5[A interface{struct{b B; c C}}, B any, C interface{*B}](x B) A { panic(0) }
func _() {
x := f5(1.2)
var _ float64 = x.b
@ -79,14 +79,14 @@ var _ = Double(MySlice{1})
type Setter[B any] interface {
Set(string)
~*B
*B
}
func FromStrings[T interface{}, PT Setter[T]](s []string) []T {
result := make([]T, len(s))
for i, v := range s {
// The type of &result[i] is *T which is in the type list
// of Setter2, so we can convert it to PT.
// of Setter, so we can convert it to PT.
p := PT(&result[i])
// PT has a Set method.
p.Set(v)

View File

@ -14,7 +14,7 @@ func _() {
}
// recursive inference
type Tr[A any, B ~*C, C ~*D, D ~*A] int
type Tr[A any, B *C, C *D, D *A] int
func _() {
var x Tr[string]
var y Tr[string, ***string, **string, *string]
@ -25,11 +25,11 @@ func _() {
}
// other patterns of inference
type To0[A any, B ~[]A] int
type To1[A any, B ~struct{a A}] int
type To2[A any, B ~[][]A] int
type To3[A any, B ~[3]*A] int
type To4[A any, B any, C ~struct{a A; b B}] int
type To0[A any, B []A] int
type To1[A any, B struct{a A}] int
type To2[A any, B [][]A] int
type To3[A any, B [3]*A] int
type To4[A any, B any, C struct{a A; b B}] int
func _() {
var _ To0[int]
var _ To1[int]

View File

@ -78,7 +78,7 @@ func _() {
related1(si, "foo" /* ERROR cannot use "foo" */ )
}
func related2[Elem any, Slice interface{~[]Elem}](e Elem, s Slice) {}
func related2[Elem any, Slice interface{[]Elem}](e Elem, s Slice) {}
func _() {
// related2 can be called with explicit instantiation.
@ -109,16 +109,8 @@ func _() {
related3[int, []int]()
related3[byte, List[byte]]()
// Alternatively, the 2nd type argument can be inferred
// from the first one through constraint type inference.
related3[int]()
// The inferred type is the core type of the Slice
// type parameter.
var _ []int = related3[int]()
// It is not the defined parameterized type List.
type anotherList []float32
var _ anotherList = related3[float32]() // valid
var _ anotherList = related3 /* ERROR cannot use .* \(value of type List\[float32\]\) as anotherList */ [float32, List[float32]]()
// The 2nd type argument cannot be inferred from the first
// one because there's two possible choices: []Elem and
// List[Elem].
related3[int]( /* ERROR cannot infer Slice */ )
}

View File

@ -35,7 +35,7 @@ func _() int {
return deref(p)
}
func addrOfCopy[V any, P ~*V](v V) P {
func addrOfCopy[V any, P *V](v V) P {
return &v
}

View File

@ -4,7 +4,7 @@
package p
func f[F interface{~*Q}, G interface{~*R}, Q, R any](q Q, r R) {}
func f[F interface{*Q}, G interface{*R}, Q, R any](q Q, r R) {}
func _() {
f[*float64, *int](1, 2)

View File

@ -0,0 +1,164 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
// Constraint type inference should be independent of the
// ordering of the type parameter declarations. Try all
// permutations in the test case below.
// Permutations produced by https://go.dev/play/p/PHcZNGJTEBZ.
func f00[S1 ~[]E1, S2 ~[]E2, E1 ~byte, E2 ~byte](S1, S2) {}
func f01[S2 ~[]E2, S1 ~[]E1, E1 ~byte, E2 ~byte](S1, S2) {}
func f02[E1 ~byte, S1 ~[]E1, S2 ~[]E2, E2 ~byte](S1, S2) {}
func f03[S1 ~[]E1, E1 ~byte, S2 ~[]E2, E2 ~byte](S1, S2) {}
func f04[S2 ~[]E2, E1 ~byte, S1 ~[]E1, E2 ~byte](S1, S2) {}
func f05[E1 ~byte, S2 ~[]E2, S1 ~[]E1, E2 ~byte](S1, S2) {}
func f06[E2 ~byte, S2 ~[]E2, S1 ~[]E1, E1 ~byte](S1, S2) {}
func f07[S2 ~[]E2, E2 ~byte, S1 ~[]E1, E1 ~byte](S1, S2) {}
func f08[S1 ~[]E1, E2 ~byte, S2 ~[]E2, E1 ~byte](S1, S2) {}
func f09[E2 ~byte, S1 ~[]E1, S2 ~[]E2, E1 ~byte](S1, S2) {}
func f10[S2 ~[]E2, S1 ~[]E1, E2 ~byte, E1 ~byte](S1, S2) {}
func f11[S1 ~[]E1, S2 ~[]E2, E2 ~byte, E1 ~byte](S1, S2) {}
func f12[S1 ~[]E1, E1 ~byte, E2 ~byte, S2 ~[]E2](S1, S2) {}
func f13[E1 ~byte, S1 ~[]E1, E2 ~byte, S2 ~[]E2](S1, S2) {}
func f14[E2 ~byte, S1 ~[]E1, E1 ~byte, S2 ~[]E2](S1, S2) {}
func f15[S1 ~[]E1, E2 ~byte, E1 ~byte, S2 ~[]E2](S1, S2) {}
func f16[E1 ~byte, E2 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {}
func f17[E2 ~byte, E1 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {}
func f18[E2 ~byte, E1 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {}
func f19[E1 ~byte, E2 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {}
func f20[S2 ~[]E2, E2 ~byte, E1 ~byte, S1 ~[]E1](S1, S2) {}
func f21[E2 ~byte, S2 ~[]E2, E1 ~byte, S1 ~[]E1](S1, S2) {}
func f22[E1 ~byte, S2 ~[]E2, E2 ~byte, S1 ~[]E1](S1, S2) {}
func f23[S2 ~[]E2, E1 ~byte, E2 ~byte, S1 ~[]E1](S1, S2) {}
type myByte byte
func _(a []byte, b []myByte) {
f00(a, b)
f01(a, b)
f02(a, b)
f03(a, b)
f04(a, b)
f05(a, b)
f06(a, b)
f07(a, b)
f08(a, b)
f09(a, b)
f10(a, b)
f11(a, b)
f12(a, b)
f13(a, b)
f14(a, b)
f15(a, b)
f16(a, b)
f17(a, b)
f18(a, b)
f19(a, b)
f20(a, b)
f21(a, b)
f22(a, b)
f23(a, b)
}
// Constraint type inference may have to iterate.
// Again, the order of the type parameters shouldn't matter.
func g0[S ~[]E, M ~map[string]S, E any](m M) {}
func g1[M ~map[string]S, S ~[]E, E any](m M) {}
func g2[E any, S ~[]E, M ~map[string]S](m M) {}
func g3[S ~[]E, E any, M ~map[string]S](m M) {}
func g4[M ~map[string]S, E any, S ~[]E](m M) {}
func g5[E any, M ~map[string]S, S ~[]E](m M) {}
func _(m map[string][]byte) {
g0(m)
g1(m)
g2(m)
g3(m)
g4(m)
g5(m)
}
// Worst-case scenario.
// There are 10 unknown type parameters. In each iteration of
// constraint type inference we infer one more, from right to left.
// Each iteration looks repeatedly at all 11 type parameters,
// requiring a total of 10*11 = 110 iterations with the current
// implementation. Pathological case.
func h[K any, J ~*K, I ~*J, H ~*I, G ~*H, F ~*G, E ~*F, D ~*E, C ~*D, B ~*C, A ~*B](x A) {}
func _(x **********int) {
h(x)
}
// Examples with channel constraints and tilde.
func ch1[P chan<- int]() (_ P) { return } // core(P) == chan<- int (single type, no tilde)
func ch2[P ~chan int]() { return } // core(P) == ~chan<- int (tilde)
func ch3[P chan E, E any](E) { return } // core(P) == chan<- E (single type, no tilde)
func ch4[P chan E | ~chan<- E, E any](E) { return } // core(P) == ~chan<- E (tilde)
func ch5[P chan int | chan<- int]() { return } // core(P) == chan<- int (not a single type)
func _() {
// P can be inferred as there's a single specific type and no tilde.
var _ chan int = ch1 /* ERROR cannot use ch1.*value of type chan<- int */ ()
var _ chan<- int = ch1()
// P cannot be inferred as there's a tilde.
ch2( /* ERROR cannot infer P */ )
type myChan chan int
ch2[myChan]()
// P can be inferred as there's a single specific type and no tilde.
var e int
ch3(e)
// P cannot be inferred as there's more than one specific type and a tilde.
ch4( /* ERROR cannot infer P */ e)
_ = ch4[chan int]
// P cannot be inferred as there's more than one specific type.
ch5( /* ERROR cannot infer P */ )
ch5[chan<- int]()
}
// test case from issue
func equal[M1 ~map[K1]V1, M2 ~map[K2]V2, K1, K2 ~uint32, V1, V2 ~string](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 {
return false
}
}
return true
}
func equalFixed[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || v1 != V1(v2) {
return false
}
}
return true
}
type (
someNumericID uint32
someStringID string
)
func _() {
foo := map[uint32]string{10: "bar"}
bar := map[someNumericID]someStringID{10: "bar"}
equal(foo, bar)
}

View File

@ -247,6 +247,17 @@ func (d *tparamsList) set(i int, typ Type) {
}
}
// unknowns returns the number of type parameters for which no type has been set yet.
func (d *tparamsList) unknowns() int {
n := 0
for _, ti := range d.indices {
if ti <= 0 {
n++
}
}
return n
}
// types returns the list of inferred types (via unification) for the type parameters
// described by d, and an index. If all types were inferred, the returned index is < 0.
// Otherwise, it is the index of the first type parameter which couldn't be inferred;
@ -348,8 +359,12 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
// (see issue #50755 for a test case).
if enableCoreTypeUnification && !u.exact {
if isTypeParam(x) && !hasName(y) {
// Caution: This may not be correct in light of ~ constraints.
// See issue #51376.
// TODO(gri) investigate!
//
// When considering the type parameter for unification
// we look at the adjusted core type (adjCoreType).
// we look at the adjusted core type (coreTerm).
// If the adjusted core type is a named type N; the
// corresponding core type is under(N). Since !u.exact
// and y doesn't have a name, unification will end up

View File

@ -466,52 +466,54 @@ func TestInstanceInfo(t *testing.T) {
`func(float64, *byte, ...[]byte)`,
},
{`package s1; func f[T any, P interface{~*T}](x T) {}; func _(x string) { f(x) }`,
{`package s1; func f[T any, P interface{*T}](x T) {}; func _(x string) { f(x) }`,
`f`,
[]string{`string`, `*string`},
`func(x string)`,
},
{`package s2; func f[T any, P interface{~*T}](x []T) {}; func _(x []int) { f(x) }`,
{`package s2; func f[T any, P interface{*T}](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `*int`},
`func(x []int)`,
},
{`package s3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
{`package s3; type C[T any] interface{chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `chan<- int`},
`func(x []int)`,
},
{`package s4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
{`package s4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func(x []int)`,
},
{`package t1; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = f[string] }`,
{`package t1; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = f[string] }`,
`f`,
[]string{`string`, `*string`},
`func() string`,
},
{`package t2; func f[T any, P interface{~*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
{`package t2; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
`f`,
[]string{`string`, `*string`},
`func() string`,
},
{`package t3; type C[T any] interface{~chan<- T}; func f[T any, P C[T]]() []T { return nil }; func _() { _ = f[int] }`,
`f`,
[]string{`int`, `chan<- int`},
`func() []int`,
},
{`package t4; type C[T any] interface{~chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
{`package t3; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
},
{`package t4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = (f[int]) }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
},
{`package i0; import "lib"; func _() { lib.F(42) }`,
`F`,
[]string{`int`},
`func(int)`,
},
{`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`,
`T`,
[]string{`int`},

View File

@ -487,21 +487,88 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type
}
}
// If a constraint has a core type, unify the corresponding type parameter with it.
for _, tpar := range tparams {
if ctype := adjCoreType(tpar); ctype != nil {
if !u.unify(tpar, ctype) {
// TODO(gri) improve error message by providing the type arguments
// which we know already
check.errorf(posn, _InvalidTypeArg, "%s does not match %s", tpar, ctype)
return nil, 0
// Repeatedly apply constraint type inference as long as
// there are still unknown type arguments and progress is
// being made.
//
// This is an O(n^2) algorithm where n is the number of
// type parameters: if there is progress (and iteration
// continues), at least one type argument is inferred
// per iteration and we have a doubly nested loop.
// In practice this is not a problem because the number
// of type parameters tends to be very small (< 5 or so).
// (It should be possible for unification to efficiently
// signal newly inferred type arguments; then the loops
// here could handle the respective type parameters only,
// but that will come at a cost of extra complexity which
// may not be worth it.)
for n := u.x.unknowns(); n > 0; {
nn := n
for i, tpar := range tparams {
// If there is a core term (i.e., a core type with tilde information)
// unify the type parameter with the core type.
if core, single := coreTerm(tpar); core != nil {
// A type parameter can be unified with its core type in two cases.
tx := u.x.at(i)
switch {
case tx != nil:
// The corresponding type argument tx is known.
// In this case, if the core type has a tilde, the type argument's underlying
// type must match the core type, otherwise the type argument and the core type
// must match.
// If tx is an external type parameter, don't consider its underlying type
// (which is an interface). Core type unification will attempt to unify against
// core.typ.
// Note also that even with inexact unification we cannot leave away the under
// call here because it's possible that both tx and core.typ are named types,
// with under(tx) being a (named) basic type matching core.typ. Such cases do
// not match with inexact unification.
if core.tilde && !isTypeParam(tx) {
tx = under(tx)
}
if !u.unify(tx, core.typ) {
// TODO(gri) improve error message by providing the type arguments
// which we know already
// Don't use term.String() as it always qualifies types, even if they
// are in the current package.
tilde := ""
if core.tilde {
tilde = "~"
}
check.errorf(posn, _InvalidTypeArg, "%s does not match %s%s", tpar, tilde, core.typ)
return nil, 0
}
case single && !core.tilde:
// The corresponding type argument tx is unknown and there's a single
// specific type and no tilde.
// In this case the type argument must be that single type; set it.
u.x.set(i, core.typ)
default:
// Unification is not possible and no progress was made.
continue
}
// The number of known type arguments may have changed.
nn = u.x.unknowns()
if nn == 0 {
break // all type arguments are known
}
}
}
assert(nn <= n)
if nn == n {
break // no progress
}
n = nn
}
// u.x.types() now contains the incoming type arguments plus any additional type
// arguments which were inferred from core types. The newly inferred non-
// nil entries may still contain references to other type parameters.
// arguments which were inferred from core terms. The newly inferred non-nil
// entries may still contain references to other type parameters.
// For instance, for [A any, B interface{ []C }, C interface{ *A }], if A == int
// was given, unification produced the type list [int, []C, *A]. We eliminate the
// remaining type parameters by substituting the type parameters in this type list
@ -590,26 +657,40 @@ func (check *Checker) inferB(posn positioner, tparams []*TypeParam, targs []Type
return
}
// adjCoreType returns the core type of tpar unless the
// type parameter embeds a single, possibly named type,
// in which case it returns that single type instead.
// (The core type is always the underlying type of that
// single type.)
func adjCoreType(tpar *TypeParam) Type {
var single *term
if tpar.is(func(t *term) bool {
if single == nil && t != nil {
single = t
return true
// If the type parameter has a single specific type S, coreTerm returns (S, true).
// Otherwise, if tpar has a core type T, it returns a term corresponding to that
// core type and false. In that case, if any term of tpar has a tilde, the core
// term has a tilde. In all other cases coreTerm returns (nil, false).
func coreTerm(tpar *TypeParam) (*term, bool) {
n := 0
var single *term // valid if n == 1
var tilde bool
tpar.is(func(t *term) bool {
if t == nil {
assert(n == 0)
return false // no terms
}
return false // zero or more than one terms
}) {
n++
single = t
if t.tilde {
tilde = true
}
return true
})
if n == 1 {
if debug {
assert(under(single.typ) == coreType(tpar))
assert(debug && under(single.typ) == coreType(tpar))
}
return single.typ
return single, true
}
return coreType(tpar)
if typ := coreType(tpar); typ != nil {
// A core type is always an underlying type.
// If any term of tpar has a tilde, we don't
// have a precise core type and we must return
// a tilde as well.
return &term{tilde, typ}, false
}
return nil, false
}
type cycleFinder struct {

View File

@ -8,21 +8,21 @@ import "strconv"
type any interface{}
func f0[A any, B interface{~*C}, C interface{~*D}, D interface{~*A}](A, B, C, D) {}
func f0[A any, B interface{*C}, C interface{*D}, D interface{*A}](A, B, C, D) {}
func _() {
f := f0[string]
f("a", nil, nil, nil)
f0("a", nil, nil, nil)
}
func f1[A any, B interface{~*A}](A, B) {}
func f1[A any, B interface{*A}](A, B) {}
func _() {
f := f1[int]
f(int(0), new(int))
f1(int(0), new(int))
}
func f2[A any, B interface{~[]A}](A, B) {}
func f2[A any, B interface{[]A}](A, B) {}
func _() {
f := f2[byte]
f(byte(0), []byte{})
@ -38,7 +38,7 @@ func _() {
// f3(x, &x, &x)
// }
func f4[A any, B interface{~[]C}, C interface{~*A}](A, B, C) {}
func f4[A any, B interface{[]C}, C interface{*A}](A, B, C) {}
func _() {
f := f4[int]
var x int
@ -46,7 +46,7 @@ func _() {
f4(x, []*int{}, &x)
}
func f5[A interface{~struct{b B; c C}}, B any, C interface{~*B}](x B) A { panic(0) }
func f5[A interface{struct{b B; c C}}, B any, C interface{*B}](x B) A { panic(0) }
func _() {
x := f5(1.2)
var _ float64 = x.b
@ -79,7 +79,7 @@ var _ = Double(MySlice{1})
type Setter[B any] interface {
Set(string)
~*B
*B
}
func FromStrings[T interface{}, PT Setter[T]](s []string) []T {

View File

@ -14,7 +14,7 @@ func _() {
}
// recursive inference
type Tr[A any, B ~*C, C ~*D, D ~*A] int
type Tr[A any, B *C, C *D, D *A] int
func _() {
var x Tr[string]
var y Tr[string, ***string, **string, *string]
@ -25,11 +25,11 @@ func _() {
}
// other patterns of inference
type To0[A any, B ~[]A] int
type To1[A any, B ~struct{a A}] int
type To2[A any, B ~[][]A] int
type To3[A any, B ~[3]*A] int
type To4[A any, B any, C ~struct{a A; b B}] int
type To0[A any, B []A] int
type To1[A any, B struct{a A}] int
type To2[A any, B [][]A] int
type To3[A any, B [3]*A] int
type To4[A any, B any, C struct{a A; b B}] int
func _() {
var _ To0[int]
var _ To1[int]

View File

@ -78,7 +78,7 @@ func _() {
related1(si, "foo" /* ERROR cannot use "foo" */ )
}
func related2[Elem any, Slice interface{~[]Elem}](e Elem, s Slice) {}
func related2[Elem any, Slice interface{[]Elem}](e Elem, s Slice) {}
func _() {
// related2 can be called with explicit instantiation.
@ -109,16 +109,8 @@ func _() {
related3[int, []int]()
related3[byte, List[byte]]()
// Alternatively, the 2nd type argument can be inferred
// from the first one through constraint type inference.
related3[int]()
// The inferred type is the core type of the Slice
// type parameter.
var _ []int = related3[int]()
// It is not the defined parameterized type List.
type anotherList []float32
var _ anotherList = related3[float32]() // valid
var _ anotherList = related3 /* ERROR cannot use .* \(value of type List\[float32\]\) as anotherList */ [float32, List[float32]]()
// The 2nd type argument cannot be inferred from the first
// one because there's two possible choices: []Elem and
// List[Elem].
related3 /* ERROR cannot infer Slice */ [int]()
}

View File

@ -35,7 +35,7 @@ func _() int {
return deref(p)
}
func addrOfCopy[V any, P ~*V](v V) P {
func addrOfCopy[V any, P *V](v V) P {
return &v
}

View File

@ -4,7 +4,7 @@
package p
func f[F interface{~*Q}, G interface{~*R}, Q, R any](q Q, r R) {}
func f[F interface{*Q}, G interface{*R}, Q, R any](q Q, r R) {}
func _() {
f[*float64, *int](1, 2)

View File

@ -0,0 +1,164 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
// Constraint type inference should be independent of the
// ordering of the type parameter declarations. Try all
// permutations in the test case below.
// Permutations produced by https://go.dev/play/p/PHcZNGJTEBZ.
func f00[S1 ~[]E1, S2 ~[]E2, E1 ~byte, E2 ~byte](S1, S2) {}
func f01[S2 ~[]E2, S1 ~[]E1, E1 ~byte, E2 ~byte](S1, S2) {}
func f02[E1 ~byte, S1 ~[]E1, S2 ~[]E2, E2 ~byte](S1, S2) {}
func f03[S1 ~[]E1, E1 ~byte, S2 ~[]E2, E2 ~byte](S1, S2) {}
func f04[S2 ~[]E2, E1 ~byte, S1 ~[]E1, E2 ~byte](S1, S2) {}
func f05[E1 ~byte, S2 ~[]E2, S1 ~[]E1, E2 ~byte](S1, S2) {}
func f06[E2 ~byte, S2 ~[]E2, S1 ~[]E1, E1 ~byte](S1, S2) {}
func f07[S2 ~[]E2, E2 ~byte, S1 ~[]E1, E1 ~byte](S1, S2) {}
func f08[S1 ~[]E1, E2 ~byte, S2 ~[]E2, E1 ~byte](S1, S2) {}
func f09[E2 ~byte, S1 ~[]E1, S2 ~[]E2, E1 ~byte](S1, S2) {}
func f10[S2 ~[]E2, S1 ~[]E1, E2 ~byte, E1 ~byte](S1, S2) {}
func f11[S1 ~[]E1, S2 ~[]E2, E2 ~byte, E1 ~byte](S1, S2) {}
func f12[S1 ~[]E1, E1 ~byte, E2 ~byte, S2 ~[]E2](S1, S2) {}
func f13[E1 ~byte, S1 ~[]E1, E2 ~byte, S2 ~[]E2](S1, S2) {}
func f14[E2 ~byte, S1 ~[]E1, E1 ~byte, S2 ~[]E2](S1, S2) {}
func f15[S1 ~[]E1, E2 ~byte, E1 ~byte, S2 ~[]E2](S1, S2) {}
func f16[E1 ~byte, E2 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {}
func f17[E2 ~byte, E1 ~byte, S1 ~[]E1, S2 ~[]E2](S1, S2) {}
func f18[E2 ~byte, E1 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {}
func f19[E1 ~byte, E2 ~byte, S2 ~[]E2, S1 ~[]E1](S1, S2) {}
func f20[S2 ~[]E2, E2 ~byte, E1 ~byte, S1 ~[]E1](S1, S2) {}
func f21[E2 ~byte, S2 ~[]E2, E1 ~byte, S1 ~[]E1](S1, S2) {}
func f22[E1 ~byte, S2 ~[]E2, E2 ~byte, S1 ~[]E1](S1, S2) {}
func f23[S2 ~[]E2, E1 ~byte, E2 ~byte, S1 ~[]E1](S1, S2) {}
type myByte byte
func _(a []byte, b []myByte) {
f00(a, b)
f01(a, b)
f02(a, b)
f03(a, b)
f04(a, b)
f05(a, b)
f06(a, b)
f07(a, b)
f08(a, b)
f09(a, b)
f10(a, b)
f11(a, b)
f12(a, b)
f13(a, b)
f14(a, b)
f15(a, b)
f16(a, b)
f17(a, b)
f18(a, b)
f19(a, b)
f20(a, b)
f21(a, b)
f22(a, b)
f23(a, b)
}
// Constraint type inference may have to iterate.
// Again, the order of the type parameters shouldn't matter.
func g0[S ~[]E, M ~map[string]S, E any](m M) {}
func g1[M ~map[string]S, S ~[]E, E any](m M) {}
func g2[E any, S ~[]E, M ~map[string]S](m M) {}
func g3[S ~[]E, E any, M ~map[string]S](m M) {}
func g4[M ~map[string]S, E any, S ~[]E](m M) {}
func g5[E any, M ~map[string]S, S ~[]E](m M) {}
func _(m map[string][]byte) {
g0(m)
g1(m)
g2(m)
g3(m)
g4(m)
g5(m)
}
// Worst-case scenario.
// There are 10 unknown type parameters. In each iteration of
// constraint type inference we infer one more, from right to left.
// Each iteration looks repeatedly at all 11 type parameters,
// requiring a total of 10*11 = 110 iterations with the current
// implementation. Pathological case.
func h[K any, J ~*K, I ~*J, H ~*I, G ~*H, F ~*G, E ~*F, D ~*E, C ~*D, B ~*C, A ~*B](x A) {}
func _(x **********int) {
h(x)
}
// Examples with channel constraints and tilde.
func ch1[P chan<- int]() (_ P) { return } // core(P) == chan<- int (single type, no tilde)
func ch2[P ~chan int]() { return } // core(P) == ~chan<- int (tilde)
func ch3[P chan E, E any](E) { return } // core(P) == chan<- E (single type, no tilde)
func ch4[P chan E | ~chan<- E, E any](E) { return } // core(P) == ~chan<- E (tilde)
func ch5[P chan int | chan<- int]() { return } // core(P) == chan<- int (not a single type)
func _() {
// P can be inferred as there's a single specific type and no tilde.
var _ chan int = ch1 /* ERROR cannot use ch1.*value of type chan<- int */ ()
var _ chan<- int = ch1()
// P cannot be inferred as there's a tilde.
ch2 /* ERROR cannot infer P */ ()
type myChan chan int
ch2[myChan]()
// P can be inferred as there's a single specific type and no tilde.
var e int
ch3(e)
// P cannot be inferred as there's more than one specific type and a tilde.
ch4 /* ERROR cannot infer P */ (e)
_ = ch4[chan int]
// P cannot be inferred as there's more than one specific type.
ch5 /* ERROR cannot infer P */ ()
ch5[chan<- int]()
}
// test case from issue
func equal[M1 ~map[K1]V1, M2 ~map[K2]V2, K1, K2 ~uint32, V1, V2 ~string](m1 M1, m2 M2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || V2(v1) != v2 {
return false
}
}
return true
}
func equalFixed[K1, K2 ~uint32, V1, V2 ~string](m1 map[K1]V1, m2 map[K2]V2) bool {
if len(m1) != len(m2) {
return false
}
for k, v1 := range m1 {
if v2, ok := m2[K2(k)]; !ok || v1 != V1(v2) {
return false
}
}
return true
}
type (
someNumericID uint32
someStringID string
)
func _() {
foo := map[uint32]string{10: "bar"}
bar := map[someNumericID]someStringID{10: "bar"}
equal(foo, bar)
}

View File

@ -247,6 +247,17 @@ func (d *tparamsList) set(i int, typ Type) {
}
}
// unknowns returns the number of type parameters for which no type has been set yet.
func (d *tparamsList) unknowns() int {
n := 0
for _, ti := range d.indices {
if ti <= 0 {
n++
}
}
return n
}
// types returns the list of inferred types (via unification) for the type parameters
// described by d, and an index. If all types were inferred, the returned index is < 0.
// Otherwise, it is the index of the first type parameter which couldn't be inferred;
@ -348,8 +359,12 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) {
// (see issue #50755 for a test case).
if enableCoreTypeUnification && !u.exact {
if isTypeParam(x) && !hasName(y) {
// Caution: This may not be correct in light of ~ constraints.
// See issue #51376.
// TODO(gri) investigate!
//
// When considering the type parameter for unification
// we look at the adjusted core type (adjCoreType).
// we look at the adjusted core type (coreTerm).
// If the adjusted core type is a named type N; the
// corresponding core type is under(N). Since !u.exact
// and y doesn't have a name, unification will end up

View File

@ -13,14 +13,14 @@ func identity[T int](x T) T {
return x
}
func min[T int|string](x, y T) T {
func min[T int | string](x, y T) T {
if x < y {
return x
}
return y
}
func max[T ~float64](x, y T) T {
func max[T ~int | ~float64](x, y T) T {
if x > y {
return x
}
@ -48,7 +48,7 @@ func main() {
// Some random type parameter lists with elided interfaces.
type (
_ [T struct{}] struct{}
_ [M map[K]V, K comparable, V any] struct{}
_ [_ interface{}|int] struct{}
_[T struct{}] struct{}
_[M map[K]V, K comparable, V any] struct{}
_[_ interface{} | int] struct{}
)

View File

@ -15,7 +15,7 @@ import (
type Setter[B any] interface {
Set(string)
~*B
*B
}
// Takes two type parameters where PT = *T

View File

@ -89,7 +89,7 @@ func f1x() {
}
*/
func f2[A any, B interface{ ~[]A }](_ A, _ B) {}
func f2[A any, B interface{ []A }](_ A, _ B) {}
func f2x() {
f := f2[byte]
f(byte(0), []byte{})
@ -109,7 +109,7 @@ func f3x() {
}
*/
func f4[A any, B interface{ ~[]C }, C interface{ ~*A }](_ A, _ B, c C) {}
func f4[A any, B interface{ []C }, C interface{ *A }](_ A, _ B, c C) {}
func f4x() {
f := f4[int]
var x int
@ -118,11 +118,11 @@ func f4x() {
}
func f5[A interface {
~struct {
struct {
b B
c C
}
}, B any, C interface{ ~*B }](x B) A {
}, B any, C interface{ *B }](x B) A {
panic(0)
}
func f5x() {