Skip to content

Commit

Permalink
go/types, types2: correctly consider ~ (tilde) in constraint type inf…
Browse files Browse the repository at this point in the history
…erence

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>
  • Loading branch information
griesemer committed Mar 1, 2022
1 parent aaa3d39 commit 0807986
Show file tree
Hide file tree
Showing 21 changed files with 648 additions and 140 deletions.
24 changes: 13 additions & 11 deletions src/cmd/compile/internal/types2/api_test.go
Expand Up @@ -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`},
Expand All @@ -540,15 +542,15 @@ 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`,
},
}

for _, test := range tests {
const lib = `package generic_lib
const lib = `package lib
func F[P any](P) {}
Expand Down
135 changes: 108 additions & 27 deletions src/cmd/compile/internal/types2/infer.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
return false // zero or more than one terms
}) {
// 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
}
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
}
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 coreType(tpar)
return nil, false
}

type cycleFinder struct {
Expand Down
14 changes: 7 additions & 7 deletions src/cmd/compile/internal/types2/testdata/check/funcinference.go2
Expand Up @@ -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{})
Expand All @@ -38,15 +38,15 @@ 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
f(x, []*int{}, &x)
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
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions src/cmd/compile/internal/types2/testdata/check/typeinference.go2
Expand Up @@ -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]
Expand All @@ -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]
Expand Down
18 changes: 5 additions & 13 deletions src/cmd/compile/internal/types2/testdata/examples/inference.go2
Expand Up @@ -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.
Expand Down Expand Up @@ -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 */ )
}
Expand Up @@ -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
}

Expand Down
Expand Up @@ -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)
Expand Down

0 comments on commit 0807986

Please sign in to comment.