go/types: better error messages for empty type sets

This is a clean port of CL 358175 to go/types.

Change-Id: If1b4e51d1579fd168e651d79d031335ff09ca128
Reviewed-on: https://go-review.googlesource.com/c/go/+/360474
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Robert Findley 2021-11-01 14:46:35 -04:00
parent 8c8baad927
commit c406380fa9
4 changed files with 48 additions and 16 deletions

View file

@ -134,8 +134,16 @@ func (check *Checker) verify(pos token.Pos, tparams []*TypeParam, targs []Type)
// TODO(gri) This should be a method of interfaces or type sets.
func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *TypeParam, smap substMap) error {
iface := tpar.iface()
// Every type argument satisfies interface{}.
if iface.Empty() {
return nil // no type bound
return nil
}
// A type argument that is a type parameter with an empty type set satisfies any constraint.
// (The empty set is a subset of any set.)
if targ := asTypeParam(targ); targ != nil && targ.iface().typeSet().IsEmpty() {
return nil
}
// TODO(rfindley): it would be great if users could pass in a qualifier here,
@ -149,6 +157,11 @@ func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *TypeParam, smap
return errors.New(sprintf(nil, qf, false, format, args...))
}
// No type argument with non-empty type set satisfies the empty type set.
if iface.typeSet().IsEmpty() {
return errorf("%s does not satisfy %s (constraint type set is empty)", targ, tpar.bound)
}
// The type parameter bound is parameterized with the same type parameters
// as the instantiated type; before we can use it for bounds checking we
// need to instantiate it with the type arguments with which we instantiate
@ -190,28 +203,27 @@ func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *TypeParam, smap
}
}
// targ's underlying type must also be one of the interface types listed, if any
// targ must also be in the set of types of iface, if any.
// Constraints with empty type sets were already excluded above.
if !iface.typeSet().hasTerms() {
return nil // nothing to do
}
// If targ is itself a type parameter, each of its possible types, but at least one, must be in the
// list of iface types (i.e., the targ type list must be a non-empty subset of the iface types).
// If targ is itself a type parameter, each of its possible types must be in the set
// of iface types (i.e., the targ type set must be a subset of the iface type set).
// Type arguments with empty type sets were already excluded above.
if targ := asTypeParam(targ); targ != nil {
targBound := targ.iface()
if !targBound.typeSet().hasTerms() {
return errorf("%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ)
}
if !targBound.typeSet().subsetOf(iface.typeSet()) {
// TODO(gri) need better error message
// TODO(gri) report which type is missing
return errorf("%s does not satisfy %s", targ, tpar.bound)
}
return nil
}
// Otherwise, targ's type or underlying type must also be one of the interface types listed, if any.
// Otherwise, targ's type must be included in the iface type set.
if !iface.typeSet().includes(targ) {
// TODO(gri) better error message
// TODO(gri) report which type is missing
return errorf("%s does not satisfy %s", targ, tpar.bound)
}

View file

@ -226,10 +226,10 @@ type I012 interface {
}
func f012[T I012]() {}
var _ = f012[int /* ERROR does not satisfy I012 */ ]
var _ = f012[bool /* ERROR does not satisfy I012 */ ]
var _ = f012[string /* ERROR does not satisfy I012 */ ]
var _ = f012[float64 /* ERROR does not satisfy I012 */ ]
var _ = f012[int /* ERROR does not satisfy I012.*type set is empty */ ]
var _ = f012[bool /* ERROR does not satisfy I012.*type set is empty */ ]
var _ = f012[string /* ERROR does not satisfy I012.*type set is empty */ ]
var _ = f012[float64 /* ERROR does not satisfy I012.*type set is empty */ ]
type I12 interface {
E1
@ -256,3 +256,23 @@ var _ = f0_[float64 /* ERROR does not satisfy I0_ */ ]
// Using a function instance as a type is an error.
var _ f0 // ERROR not a type
var _ f0 /* ERROR not a type */ [int]
// Empty type sets can only be satisfied by empty type sets.
type none interface {
// force an empty type set
int
string
}
func ff[T none]() {}
func gg[T any]() {}
func hh[T ~int]() {}
func _[T none]() {
_ = ff[int /* ERROR int does not satisfy none \(constraint type set is empty\) */ ]
_ = ff[T] // pathological but ok because T's type set is empty, too
_ = gg[int]
_ = gg[T]
_ = hh[int]
_ = hh[T]
}

View file

@ -19,7 +19,7 @@ func _[P comparable,
_ = f[R /* ERROR R has no constraints */ ]
_ = g[int]
_ = g[P /* ERROR P has no type constraints */ ]
_ = g[P /* ERROR P does not satisfy interface{interface{comparable; ~int\|~string} */ ]
_ = g[Q]
_ = g[func /* ERROR does not satisfy comparable */()]
_ = g[R /* ERROR R has no constraints */ ]

View file

@ -99,7 +99,7 @@ func (s *_TypeSet) String() string {
// ----------------------------------------------------------------------------
// Implementation
func (s *_TypeSet) hasTerms() bool { return !s.terms.isAll() }
func (s *_TypeSet) hasTerms() bool { return !s.terms.isEmpty() && !s.terms.isAll() }
func (s *_TypeSet) structuralType() Type { return s.terms.structuralType() }
func (s *_TypeSet) includes(t Type) bool { return s.terms.includes(t) }
func (s1 *_TypeSet) subsetOf(s2 *_TypeSet) bool { return s1.terms.subsetOf(s2.terms) }