mirror of
https://github.com/golang/go
synced 2024-11-07 11:36:11 -07:00
go/types, types2: avoid infinitely recursive instantiation
Type inference uses type parameter pointer identity to keep track of the correspondence between type parameters and type arguments. However, this technique can misidentify type parameters that are used in explicit type arguments or function arguments, as in the recursive instantiation below: func f[P *Q, Q any](p P, q Q) { f[P] } In this example, the fact that the P used in the instantation f[P] has the same pointer identity as the P we are trying to solve for via unification is coincidental: there is nothing special about recursive calls that should cause them to conflate the identity of type arguments with type parameters. To put it another way: any such self-recursive call is equivalent to a mutually recursive call, which does not run into any problems of type parameter identity. For example, the following code is equivalent to the code above. func f[P interface{*Q}, Q any](p P, q Q) { f2[P] } func f2[P interface{*Q}, Q any](p P, q Q) { f[P] } We can turn the first example into the second example by renaming type parameters in the original signature to give them a new identity. This CL does this for self-recursive instantiations. Fixes #51158 Fixes #48656 Updates #48619 Change-Id: I54fe37f2a79c9d98950cf6a3602335db2896dc24 Reviewed-on: https://go-review.googlesource.com/c/go/+/385494 Trust: Robert Findley <rfindley@google.com> Run-TryBot: Robert Findley <rfindley@google.com> Reviewed-by: Robert Griesemer <gri@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
16b1893600
commit
93b5309f0a
@ -42,7 +42,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if traceInference {
|
if traceInference {
|
||||||
check.dump("-- inferA %s ➞ %s", tparams, targs)
|
check.dump("-- inferA %s%s ➞ %s", tparams, params, targs)
|
||||||
defer func() {
|
defer func() {
|
||||||
check.dump("=> inferA %s ➞ %s", tparams, result)
|
check.dump("=> inferA %s ➞ %s", tparams, result)
|
||||||
}()
|
}()
|
||||||
@ -61,6 +61,74 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
|
|||||||
}
|
}
|
||||||
// len(targs) < n
|
// len(targs) < n
|
||||||
|
|
||||||
|
const enableTparamRenaming = true
|
||||||
|
if enableTparamRenaming {
|
||||||
|
// For the purpose of type inference we must differentiate type parameters
|
||||||
|
// occurring in explicit type or value function arguments from the type
|
||||||
|
// parameters we are solving for via unification, because they may be the
|
||||||
|
// same in self-recursive calls. For example:
|
||||||
|
//
|
||||||
|
// func f[P *Q, Q any](p P, q Q) {
|
||||||
|
// f(p)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In this example, the fact that the P used in the instantation f[P] has
|
||||||
|
// the same pointer identity as the P we are trying to solve for via
|
||||||
|
// unification is coincidental: there is nothing special about recursive
|
||||||
|
// calls that should cause them to conflate the identity of type arguments
|
||||||
|
// with type parameters. To put it another way: any such self-recursive
|
||||||
|
// call is equivalent to a mutually recursive call, which does not run into
|
||||||
|
// any problems of type parameter identity. For example, the following code
|
||||||
|
// is equivalent to the code above.
|
||||||
|
//
|
||||||
|
// func f[P interface{*Q}, Q any](p P, q Q) {
|
||||||
|
// f2(p)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func f2[P interface{*Q}, Q any](p P, q Q) {
|
||||||
|
// f(p)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// We can turn the first example into the second example by renaming type
|
||||||
|
// parameters in the original signature to give them a new identity. As an
|
||||||
|
// optimization, we do this only for self-recursive calls.
|
||||||
|
|
||||||
|
// We can detect if we are in a self-recursive call by comparing the
|
||||||
|
// identity of the first type parameter in the current function with the
|
||||||
|
// first type parameter in tparams. This works because type parameters are
|
||||||
|
// unique to their type parameter list.
|
||||||
|
selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0)
|
||||||
|
|
||||||
|
if selfRecursive {
|
||||||
|
// In self-recursive inference, rename the type parameters with new type
|
||||||
|
// parameters that are the same but for their pointer identity.
|
||||||
|
tparams2 := make([]*TypeParam, len(tparams))
|
||||||
|
for i, tparam := range tparams {
|
||||||
|
tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
|
||||||
|
tparams2[i] = NewTypeParam(tname, nil)
|
||||||
|
tparams2[i].index = tparam.index // == i
|
||||||
|
}
|
||||||
|
|
||||||
|
renameMap := makeRenameMap(tparams, tparams2)
|
||||||
|
for i, tparam := range tparams {
|
||||||
|
tparams2[i].bound = check.subst(pos, tparam.bound, renameMap, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
tparams = tparams2
|
||||||
|
params = check.subst(pos, params, renameMap, nil).(*Tuple)
|
||||||
|
|
||||||
|
// If we replaced any type parameters, their replacements may occur in
|
||||||
|
// the resulting inferred type arguments. Make sure we use the original
|
||||||
|
// type parameters in the result.
|
||||||
|
defer func() {
|
||||||
|
unrenameMap := makeRenameMap(tparams2, tparams)
|
||||||
|
for i, res := range result {
|
||||||
|
result[i] = check.subst(pos, res, unrenameMap, nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we have more than 2 arguments, we may have arguments with named and unnamed types.
|
// If we have more than 2 arguments, we may have arguments with named and unnamed types.
|
||||||
// If that is the case, permutate params and args such that the arguments with named
|
// If that is the case, permutate params and args such that the arguments with named
|
||||||
// types are first in the list. This doesn't affect type inference if all types are taken
|
// types are first in the list. This doesn't affect type inference if all types are taken
|
||||||
|
@ -21,6 +21,17 @@ func makeSubstMap(tpars []*TypeParam, targs []Type) substMap {
|
|||||||
return proj
|
return proj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeRenameMap is like makeSubstMap, but creates a map used to rename type
|
||||||
|
// parameters in from with the type parameters in to.
|
||||||
|
func makeRenameMap(from, to []*TypeParam) substMap {
|
||||||
|
assert(len(from) == len(to))
|
||||||
|
proj := make(substMap, len(from))
|
||||||
|
for i, tpar := range from {
|
||||||
|
proj[tpar] = to[i]
|
||||||
|
}
|
||||||
|
return proj
|
||||||
|
}
|
||||||
|
|
||||||
func (m substMap) empty() bool {
|
func (m substMap) empty() bool {
|
||||||
return len(m) == 0
|
return len(m) == 0
|
||||||
}
|
}
|
||||||
|
@ -2,24 +2,19 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This issue is still open:
|
|
||||||
// - the error messages could be better or are incorrect
|
|
||||||
// - unification fails due to stack overflow that is caught
|
|
||||||
|
|
||||||
package p
|
package p
|
||||||
|
|
||||||
func f[P any](a, _ P) {
|
func f[P any](a, _ P) {
|
||||||
var x int
|
var x int
|
||||||
// TODO(gri) these error messages, while correct, could be better
|
// TODO(gri) these error messages, while correct, could be better
|
||||||
f(a, x /* ERROR type int of x does not match P */)
|
f(a, x /* ERROR type int of x does not match inferred type P for P */)
|
||||||
f(x, a /* ERROR type P of a does not match inferred type int for P */)
|
f(x, a /* ERROR type P of a does not match inferred type int for P */)
|
||||||
}
|
}
|
||||||
|
|
||||||
func g[P any](a, b P) {
|
func g[P any](a, b P) {
|
||||||
g(a, b)
|
g(a, b)
|
||||||
// TODO(gri) these error messages are incorrect because the code is valid
|
g(&a, &b)
|
||||||
g(&a, & /* ERROR type \*P of &b does not match inferred type \*P for P */ b)
|
g([]P{}, []P{})
|
||||||
g([]P{}, [ /* ERROR type \[\]P of \[\]P{} does not match inferred type \[\]P for P */ ]P{})
|
|
||||||
|
|
||||||
// work-around: provide type argument explicitly
|
// work-around: provide type argument explicitly
|
||||||
g[*P](&a, &b)
|
g[*P](&a, &b)
|
||||||
|
@ -2,14 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This issue is still open:
|
|
||||||
// - the error messages are unclear
|
|
||||||
// - unification fails due to stack overflow that is caught
|
|
||||||
|
|
||||||
package p
|
package p
|
||||||
|
|
||||||
func f[P *Q, Q any](P, Q) {
|
func f[P *Q, Q any](P, Q) {
|
||||||
// TODO(gri) these error messages are unclear
|
_ = f[P]
|
||||||
_ = f[ /* ERROR P does not match \*Q */ P]
|
}
|
||||||
_ = f[ /* ERROR cannot infer P */ *P]
|
|
||||||
|
func f2[P /* ERROR instantiation cycle */ *Q, Q any](P, Q) {
|
||||||
|
_ = f2[*P]
|
||||||
}
|
}
|
||||||
|
18
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51158.go2
vendored
Normal file
18
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51158.go2
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
// Type checking the following code should not cause an infinite recursion.
|
||||||
|
func f[M map[K]int, K comparable](m M) {
|
||||||
|
f(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equivalent code using mutual recursion.
|
||||||
|
func f1[M map[K]int, K comparable](m M) {
|
||||||
|
f2(m)
|
||||||
|
}
|
||||||
|
func f2[M map[K]int, K comparable](m M) {
|
||||||
|
f1(m)
|
||||||
|
}
|
@ -41,7 +41,7 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if traceInference {
|
if traceInference {
|
||||||
check.dump("-- inferA %s ➞ %s", tparams, targs)
|
check.dump("-- inferA %s%s ➞ %s", tparams, params, targs)
|
||||||
defer func() {
|
defer func() {
|
||||||
check.dump("=> inferA %s ➞ %s", tparams, result)
|
check.dump("=> inferA %s ➞ %s", tparams, result)
|
||||||
}()
|
}()
|
||||||
@ -60,6 +60,74 @@ func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type,
|
|||||||
}
|
}
|
||||||
// len(targs) < n
|
// len(targs) < n
|
||||||
|
|
||||||
|
const enableTparamRenaming = true
|
||||||
|
if enableTparamRenaming {
|
||||||
|
// For the purpose of type inference we must differentiate type parameters
|
||||||
|
// occurring in explicit type or value function arguments from the type
|
||||||
|
// parameters we are solving for via unification, because they may be the
|
||||||
|
// same in self-recursive calls. For example:
|
||||||
|
//
|
||||||
|
// func f[P *Q, Q any](p P, q Q) {
|
||||||
|
// f(p)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In this example, the fact that the P used in the instantation f[P] has
|
||||||
|
// the same pointer identity as the P we are trying to solve for via
|
||||||
|
// unification is coincidental: there is nothing special about recursive
|
||||||
|
// calls that should cause them to conflate the identity of type arguments
|
||||||
|
// with type parameters. To put it another way: any such self-recursive
|
||||||
|
// call is equivalent to a mutually recursive call, which does not run into
|
||||||
|
// any problems of type parameter identity. For example, the following code
|
||||||
|
// is equivalent to the code above.
|
||||||
|
//
|
||||||
|
// func f[P interface{*Q}, Q any](p P, q Q) {
|
||||||
|
// f2(p)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func f2[P interface{*Q}, Q any](p P, q Q) {
|
||||||
|
// f(p)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// We can turn the first example into the second example by renaming type
|
||||||
|
// parameters in the original signature to give them a new identity. As an
|
||||||
|
// optimization, we do this only for self-recursive calls.
|
||||||
|
|
||||||
|
// We can detect if we are in a self-recursive call by comparing the
|
||||||
|
// identity of the first type parameter in the current function with the
|
||||||
|
// first type parameter in tparams. This works because type parameters are
|
||||||
|
// unique to their type parameter list.
|
||||||
|
selfRecursive := check.sig != nil && check.sig.tparams.Len() > 0 && tparams[0] == check.sig.tparams.At(0)
|
||||||
|
|
||||||
|
if selfRecursive {
|
||||||
|
// In self-recursive inference, rename the type parameters with new type
|
||||||
|
// parameters that are the same but for their pointer identity.
|
||||||
|
tparams2 := make([]*TypeParam, len(tparams))
|
||||||
|
for i, tparam := range tparams {
|
||||||
|
tname := NewTypeName(tparam.Obj().Pos(), tparam.Obj().Pkg(), tparam.Obj().Name(), nil)
|
||||||
|
tparams2[i] = NewTypeParam(tname, nil)
|
||||||
|
tparams2[i].index = tparam.index // == i
|
||||||
|
}
|
||||||
|
|
||||||
|
renameMap := makeRenameMap(tparams, tparams2)
|
||||||
|
for i, tparam := range tparams {
|
||||||
|
tparams2[i].bound = check.subst(posn.Pos(), tparam.bound, renameMap, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
tparams = tparams2
|
||||||
|
params = check.subst(posn.Pos(), params, renameMap, nil).(*Tuple)
|
||||||
|
|
||||||
|
// If we replaced any type parameters, their replacements may occur in
|
||||||
|
// the resulting inferred type arguments. Make sure we use the original
|
||||||
|
// type parameters in the result.
|
||||||
|
defer func() {
|
||||||
|
unrenameMap := makeRenameMap(tparams2, tparams)
|
||||||
|
for i, res := range result {
|
||||||
|
result[i] = check.subst(posn.Pos(), res, unrenameMap, nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we have more than 2 arguments, we may have arguments with named and unnamed types.
|
// If we have more than 2 arguments, we may have arguments with named and unnamed types.
|
||||||
// If that is the case, permutate params and args such that the arguments with named
|
// If that is the case, permutate params and args such that the arguments with named
|
||||||
// types are first in the list. This doesn't affect type inference if all types are taken
|
// types are first in the list. This doesn't affect type inference if all types are taken
|
||||||
|
@ -21,6 +21,17 @@ func makeSubstMap(tpars []*TypeParam, targs []Type) substMap {
|
|||||||
return proj
|
return proj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeRenameMap is like makeSubstMap, but creates a map used to rename type
|
||||||
|
// parameters in from with the type parameters in to.
|
||||||
|
func makeRenameMap(from, to []*TypeParam) substMap {
|
||||||
|
assert(len(from) == len(to))
|
||||||
|
proj := make(substMap, len(from))
|
||||||
|
for i, tpar := range from {
|
||||||
|
proj[tpar] = to[i]
|
||||||
|
}
|
||||||
|
return proj
|
||||||
|
}
|
||||||
|
|
||||||
func (m substMap) empty() bool {
|
func (m substMap) empty() bool {
|
||||||
return len(m) == 0
|
return len(m) == 0
|
||||||
}
|
}
|
||||||
|
11
src/go/types/testdata/fixedbugs/issue48619.go2
vendored
11
src/go/types/testdata/fixedbugs/issue48619.go2
vendored
@ -2,24 +2,19 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This issue is still open:
|
|
||||||
// - the error messages could be better or are incorrect
|
|
||||||
// - unification fails due to stack overflow that is caught
|
|
||||||
|
|
||||||
package p
|
package p
|
||||||
|
|
||||||
func f[P any](a, _ P) {
|
func f[P any](a, _ P) {
|
||||||
var x int
|
var x int
|
||||||
// TODO(gri) these error messages, while correct, could be better
|
// TODO(gri) these error messages, while correct, could be better
|
||||||
f(a, x /* ERROR type int of x does not match P */)
|
f(a, x /* ERROR type int of x does not match inferred type P for P */)
|
||||||
f(x, a /* ERROR type P of a does not match inferred type int for P */)
|
f(x, a /* ERROR type P of a does not match inferred type int for P */)
|
||||||
}
|
}
|
||||||
|
|
||||||
func g[P any](a, b P) {
|
func g[P any](a, b P) {
|
||||||
g(a, b)
|
g(a, b)
|
||||||
// TODO(gri) these error messages are incorrect because the code is valid
|
g(&a, &b)
|
||||||
g(&a, & /* ERROR type \*P of &b does not match inferred type \*P for P */ b)
|
g([]P{}, []P{})
|
||||||
g([]P{}, [ /* ERROR type \[\]P of \(\[\]P literal\) does not match inferred type \[\]P for P */ ]P{})
|
|
||||||
|
|
||||||
// work-around: provide type argument explicitly
|
// work-around: provide type argument explicitly
|
||||||
g[*P](&a, &b)
|
g[*P](&a, &b)
|
||||||
|
12
src/go/types/testdata/fixedbugs/issue48656.go2
vendored
12
src/go/types/testdata/fixedbugs/issue48656.go2
vendored
@ -2,14 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// This issue is still open:
|
|
||||||
// - the error messages are unclear
|
|
||||||
// - unification fails due to stack overflow that is caught
|
|
||||||
|
|
||||||
package p
|
package p
|
||||||
|
|
||||||
func f[P *Q, Q any](P, Q) {
|
func f[P *Q, Q any](P, Q) {
|
||||||
// TODO(gri) these error messages are unclear
|
_ = f[P]
|
||||||
_ = f /* ERROR P does not match \*Q */ [P]
|
}
|
||||||
_ = f /* ERROR cannot infer P */ [*P]
|
|
||||||
|
func f2[P /* ERROR instantiation cycle */ *Q, Q any](P, Q) {
|
||||||
|
_ = f2[*P]
|
||||||
}
|
}
|
||||||
|
18
src/go/types/testdata/fixedbugs/issue51158.go2
vendored
Normal file
18
src/go/types/testdata/fixedbugs/issue51158.go2
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
// Type checking the following code should not cause an infinite recursion.
|
||||||
|
func f[M map[K]int, K comparable](m M) {
|
||||||
|
f(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equivalent code using mutual recursion.
|
||||||
|
func f1[M map[K]int, K comparable](m M) {
|
||||||
|
f2(m)
|
||||||
|
}
|
||||||
|
func f2[M map[K]int, K comparable](m M) {
|
||||||
|
f1(m)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user