cmd/compile/internal/types2: handle recursive type parameter constraints

Check type constraints after the respective type parameter list
has been associated with a parameterized type so that recursive
type parameter constraints "see" a parameterized type.

Fixes #45550.
Fixes #47796.

Change-Id: Iac74610ca017a78013820624230c857395506aff
Reviewed-on: https://go-review.googlesource.com/c/go/+/348090
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2021-09-07 13:58:49 -07:00
parent 9581d891ab
commit 73a062c3e7
4 changed files with 58 additions and 6 deletions

View file

@ -567,7 +567,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
if tdecl.TParamList != nil {
check.openScope(tdecl, "type parameters")
defer check.closeScope()
named.tparams = check.collectTypeParams(tdecl.TParamList)
check.collectTypeParams(&named.tparams, tdecl.TParamList)
}
// determine underlying type of named
@ -598,7 +598,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
}
}
func (check *Checker) collectTypeParams(list []*syntax.Field) *TParamList {
func (check *Checker) collectTypeParams(dst **TParamList, list []*syntax.Field) {
tparams := make([]*TypeParam, len(list))
// Declare type parameters up-front.
@ -608,6 +608,11 @@ func (check *Checker) collectTypeParams(list []*syntax.Field) *TParamList {
tparams[i] = check.declareTypeParam(f.Name)
}
// Set the type parameters before collecting the type constraints because
// the parameterized type may be used by the constraints (issue #47887).
// Example: type T[P T[P]] interface{}
*dst = bindTParams(tparams)
var bound Type
for i, f := range list {
// Optimization: Re-use the previous type bound if it hasn't changed.
@ -618,13 +623,17 @@ func (check *Checker) collectTypeParams(list []*syntax.Field) *TParamList {
}
tparams[i].bound = bound
}
return bindTParams(tparams)
}
func (check *Checker) declareTypeParam(name *syntax.Name) *TypeParam {
// Use Typ[Invalid] for the type constraint to ensure that a type
// is present even if the actual constraint has not been assigned
// yet.
// TODO(gri) Need to systematically review all uses of type parameter
// constraints to make sure we don't rely on them if they
// are not properly set yet.
tname := NewTypeName(name.Pos(), check.pkg, name.Value, nil)
tpar := check.NewTypeParam(tname, nil) // assigns type to tname as a side-effect
tpar := check.NewTypeParam(tname, Typ[Invalid]) // assigns type to tname as a side-effect
check.declare(check.scope, name, tname, check.scope.pos) // TODO(gri) check scope position
return tpar
}

View file

@ -157,7 +157,7 @@ func (check *Checker) funcType(sig *Signature, recvPar *syntax.Field, tparams []
}
if tparams != nil {
sig.tparams = check.collectTypeParams(tparams)
check.collectTypeParams(&sig.tparams, tparams)
// Always type-check method type parameters but complain if they are not enabled.
// (A separate check is needed when type-checking interface method signatures because
// they don't have a receiver specification.)

View file

@ -0,0 +1,10 @@
// Copyright 2021 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 Builder[T interface{ struct{ Builder[T] } }] struct{}
type myBuilder struct {
Builder[myBuilder /* ERROR myBuilder does not satisfy */]
}

View file

@ -0,0 +1,33 @@
// Copyright 2021 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
// parameterized types with self-recursive constraints
type (
T1[P T1[P]] interface{}
T2[P, Q T2[P, Q]] interface{}
T3[P T2[P, Q], Q interface{ ~string }] interface{}
T4a[P T4a[P]] interface{ ~int }
T4b[P T4b[int]] interface{ ~int }
T4c[P T4c[string /* ERROR string does not satisfy T4c\[string\] */]] interface{ ~int }
// mutually recursive constraints
T5[P T6[P]] interface{ int }
T6[P T5[P]] interface{ int }
)
// verify that constraints are checked as expected
var (
_ T1[int]
_ T2[int, string]
_ T3[int, string]
)
// test case from issue
type Eq[a Eq[a]] interface {
Equal(that a) bool
}