mirror of
https://github.com/golang/go
synced 2024-11-17 10:14:46 -07:00
go/types, types2: correctly include comparable in type set intersection
The comparable bit was handled incorrectly. This CL establishes a clear invariant for a type set's terms and its comparable bit and correctly uses the bit when computing term intersections. Relevant changes: - Introduce a new function intersectTermLists that does the correct intersection computation. Minor: - Moved the comparable bit after terms in _TypeSet to make it clearer that they belong together. - Simplify and clarify _TypeSet.IsAll predicate. - Remove the IsTypeSet predicate which was only used for error reporting in union.go, and use the existing predicates instead. - Rename/introduce local variables in computeInterfaceTypeSet for consistency and to avoid confusion. - Update some tests whose output has changed because the comparable bit is now only set if we have have the set of all types. For instance, for interface{comparable; int} the type set doesn't set the comparable bit because the intersection of comparable and int is just int; etc. - Add many more comments to make the code clearer. Fixes #51472. Change-Id: I8a5661eb1693a41a17ce5f70d7e10774301f38ab Reviewed-on: https://go-review.googlesource.com/c/go/+/390025 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:
parent
dcb6547b76
commit
7dc6c5ec34
@ -47,7 +47,7 @@ type _ struct{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _ struct{
|
type _ struct{
|
||||||
I3 // ERROR interface is .* comparable
|
I3 // ERROR interface contains type constraints
|
||||||
}
|
}
|
||||||
|
|
||||||
// General composite types.
|
// General composite types.
|
||||||
@ -59,19 +59,19 @@ type (
|
|||||||
_ []I1 // ERROR interface is .* comparable
|
_ []I1 // ERROR interface is .* comparable
|
||||||
_ []I2 // ERROR interface contains type constraints
|
_ []I2 // ERROR interface contains type constraints
|
||||||
|
|
||||||
_ *I3 // ERROR interface is .* comparable
|
_ *I3 // ERROR interface contains type constraints
|
||||||
_ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints
|
_ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints
|
||||||
_ chan I3 // ERROR interface is .* comparable
|
_ chan I3 // ERROR interface contains type constraints
|
||||||
_ func(I1 /* ERROR interface is .* comparable */ )
|
_ func(I1 /* ERROR interface is .* comparable */ )
|
||||||
_ func() I2 // ERROR interface contains type constraints
|
_ func() I2 // ERROR interface contains type constraints
|
||||||
)
|
)
|
||||||
|
|
||||||
// Other cases.
|
// Other cases.
|
||||||
|
|
||||||
var _ = [...]I3 /* ERROR interface is .* comparable */ {}
|
var _ = [...]I3 /* ERROR interface contains type constraints */ {}
|
||||||
|
|
||||||
func _(x interface{}) {
|
func _(x interface{}) {
|
||||||
_ = x.(I3 /* ERROR interface is .* comparable */ )
|
_ = x.(I3 /* ERROR interface contains type constraints */ )
|
||||||
}
|
}
|
||||||
|
|
||||||
type T1[_ any] struct{}
|
type T1[_ any] struct{}
|
||||||
|
54
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51472.go2
vendored
Normal file
54
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51472.go2
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
func _[T comparable](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{interface{comparable}}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; interface{comparable}}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; ~int}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; ~[]byte}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gri) The error message here should be better. See issue #51525.
|
||||||
|
func _[T interface{comparable; ~int; ~string}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gri) The error message here should be better. See issue #51525.
|
||||||
|
func _[T interface{~int; ~string}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; interface{~int}; interface{int|float64}}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{interface{comparable; ~int}; interface{~float64; comparable; m()}}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
// test case from issue
|
||||||
|
|
||||||
|
func f[T interface{comparable; []byte|string}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _(s []byte) {
|
||||||
|
f( /* ERROR \[\]byte does not implement interface{comparable; \[\]byte\|string} */ s)
|
||||||
|
_ = f[[ /* ERROR does not implement */ ]byte]
|
||||||
|
}
|
@ -15,20 +15,25 @@ import (
|
|||||||
// API
|
// API
|
||||||
|
|
||||||
// A _TypeSet represents the type set of an interface.
|
// A _TypeSet represents the type set of an interface.
|
||||||
|
// Because of existing language restrictions, methods can be "factored out"
|
||||||
|
// from the terms. The actual type set is the intersection of the type set
|
||||||
|
// implied by the methods and the type set described by the terms and the
|
||||||
|
// comparable bit. To test whether a type is included in a type set
|
||||||
|
// ("implements" relation), the type must implement all methods _and_ be
|
||||||
|
// an element of the type set described by the terms and the comparable bit.
|
||||||
|
// If the term list describes the set of all types and comparable is true,
|
||||||
|
// only comparable types are meant; in all other cases comparable is false.
|
||||||
type _TypeSet struct {
|
type _TypeSet struct {
|
||||||
comparable bool // if set, the interface is or embeds comparable
|
methods []*Func // all methods of the interface; sorted by unique ID
|
||||||
// TODO(gri) consider using a set for the methods for faster lookup
|
terms termlist // type terms of the type set
|
||||||
methods []*Func // all methods of the interface; sorted by unique ID
|
comparable bool // invariant: !comparable || terms.isAll()
|
||||||
terms termlist // type terms of the type set
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty reports whether type set s is the empty set.
|
// IsEmpty reports whether type set s is the empty set.
|
||||||
func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() }
|
func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() }
|
||||||
|
|
||||||
// IsAll reports whether type set s is the set of all types (corresponding to the empty interface).
|
// IsAll reports whether type set s is the set of all types (corresponding to the empty interface).
|
||||||
func (s *_TypeSet) IsAll() bool {
|
func (s *_TypeSet) IsAll() bool { return s.IsMethodSet() && len(s.methods) == 0 }
|
||||||
return !s.comparable && len(s.methods) == 0 && s.terms.isAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMethodSet reports whether the interface t is fully described by its method set.
|
// IsMethodSet reports whether the interface t is fully described by its method set.
|
||||||
func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() }
|
func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() }
|
||||||
@ -43,13 +48,6 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gri) IsTypeSet is not a great name for this predicate. Find a better one.
|
|
||||||
|
|
||||||
// IsTypeSet reports whether the type set s is represented by a finite set of underlying types.
|
|
||||||
func (s *_TypeSet) IsTypeSet() bool {
|
|
||||||
return !s.comparable && len(s.methods) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumMethods returns the number of methods available.
|
// NumMethods returns the number of methods available.
|
||||||
func (s *_TypeSet) NumMethods() int { return len(s.methods) }
|
func (s *_TypeSet) NumMethods() int { return len(s.methods) }
|
||||||
|
|
||||||
@ -215,12 +213,12 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
|
|
||||||
var todo []*Func
|
var todo []*Func
|
||||||
var seen objset
|
var seen objset
|
||||||
var methods []*Func
|
var allMethods []*Func
|
||||||
mpos := make(map[*Func]syntax.Pos) // method specification or method embedding position, for good error messages
|
mpos := make(map[*Func]syntax.Pos) // method specification or method embedding position, for good error messages
|
||||||
addMethod := func(pos syntax.Pos, m *Func, explicit bool) {
|
addMethod := func(pos syntax.Pos, m *Func, explicit bool) {
|
||||||
switch other := seen.insert(m); {
|
switch other := seen.insert(m); {
|
||||||
case other == nil:
|
case other == nil:
|
||||||
methods = append(methods, m)
|
allMethods = append(allMethods, m)
|
||||||
mpos[m] = pos
|
mpos[m] = pos
|
||||||
case explicit:
|
case explicit:
|
||||||
if check == nil {
|
if check == nil {
|
||||||
@ -259,7 +257,8 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
}
|
}
|
||||||
|
|
||||||
// collect embedded elements
|
// collect embedded elements
|
||||||
var allTerms = allTermlist
|
allTerms := allTermlist
|
||||||
|
allComparable := false
|
||||||
for i, typ := range ityp.embeddeds {
|
for i, typ := range ityp.embeddeds {
|
||||||
// The embedding position is nil for imported interfaces
|
// The embedding position is nil for imported interfaces
|
||||||
// and also for interface copies after substitution (but
|
// and also for interface copies after substitution (but
|
||||||
@ -268,6 +267,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
if ityp.embedPos != nil {
|
if ityp.embedPos != nil {
|
||||||
pos = (*ityp.embedPos)[i]
|
pos = (*ityp.embedPos)[i]
|
||||||
}
|
}
|
||||||
|
var comparable bool
|
||||||
var terms termlist
|
var terms termlist
|
||||||
switch u := under(typ).(type) {
|
switch u := under(typ).(type) {
|
||||||
case *Interface:
|
case *Interface:
|
||||||
@ -279,9 +279,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
check.versionErrorf(pos, "go1.18", "embedding constraint interface %s", typ)
|
check.versionErrorf(pos, "go1.18", "embedding constraint interface %s", typ)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if tset.comparable {
|
comparable = tset.comparable
|
||||||
ityp.tset.comparable = true
|
|
||||||
}
|
|
||||||
for _, m := range tset.methods {
|
for _, m := range tset.methods {
|
||||||
addMethod(pos, m, false) // use embedding position pos rather than m.pos
|
addMethod(pos, m, false) // use embedding position pos rather than m.pos
|
||||||
}
|
}
|
||||||
@ -295,6 +293,8 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
if tset == &invalidTypeSet {
|
if tset == &invalidTypeSet {
|
||||||
continue // ignore invalid unions
|
continue // ignore invalid unions
|
||||||
}
|
}
|
||||||
|
assert(!tset.comparable)
|
||||||
|
assert(len(tset.methods) == 0)
|
||||||
terms = tset.terms
|
terms = tset.terms
|
||||||
default:
|
default:
|
||||||
if u == Typ[Invalid] {
|
if u == Typ[Invalid] {
|
||||||
@ -306,11 +306,11 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
}
|
}
|
||||||
terms = termlist{{false, typ}}
|
terms = termlist{{false, typ}}
|
||||||
}
|
}
|
||||||
// The type set of an interface is the intersection
|
|
||||||
// of the type sets of all its elements.
|
// The type set of an interface is the intersection of the type sets of all its elements.
|
||||||
// Intersection cannot produce longer termlists and
|
// Due to language restrictions, only embedded interfaces can add methods, they are handled
|
||||||
// thus cannot overflow.
|
// separately. Here we only need to intersect the term lists and comparable bits.
|
||||||
allTerms = allTerms.intersect(terms)
|
allTerms, allComparable = intersectTermLists(allTerms, allComparable, terms, comparable)
|
||||||
}
|
}
|
||||||
ityp.embedPos = nil // not needed anymore (errors have been reported)
|
ityp.embedPos = nil // not needed anymore (errors have been reported)
|
||||||
|
|
||||||
@ -323,15 +323,46 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if methods != nil {
|
ityp.tset.comparable = allComparable
|
||||||
sortMethods(methods)
|
if len(allMethods) != 0 {
|
||||||
ityp.tset.methods = methods
|
sortMethods(allMethods)
|
||||||
|
ityp.tset.methods = allMethods
|
||||||
}
|
}
|
||||||
ityp.tset.terms = allTerms
|
ityp.tset.terms = allTerms
|
||||||
|
|
||||||
return ityp.tset
|
return ityp.tset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gri) The intersectTermLists function belongs to the termlist implementation.
|
||||||
|
// The comparable type set may also be best represented as a term (using
|
||||||
|
// a special type).
|
||||||
|
|
||||||
|
// intersectTermLists computes the intersection of two term lists and respective comparable bits.
|
||||||
|
// xcomp, ycomp are valid only if xterms.isAll() and yterms.isAll() respectively.
|
||||||
|
func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool) (termlist, bool) {
|
||||||
|
terms := xterms.intersect(yterms)
|
||||||
|
// If one of xterms or yterms is marked as comparable,
|
||||||
|
// the result must only include comparable types.
|
||||||
|
comp := xcomp || ycomp
|
||||||
|
if comp && !terms.isAll() {
|
||||||
|
// only keep comparable terms
|
||||||
|
i := 0
|
||||||
|
for _, t := range terms {
|
||||||
|
assert(t.typ != nil)
|
||||||
|
if Comparable(t.typ) {
|
||||||
|
terms[i] = t
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terms = terms[:i]
|
||||||
|
if !terms.isAll() {
|
||||||
|
comp = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(!comp || terms.isAll()) // comparable invariant
|
||||||
|
return terms, comp
|
||||||
|
}
|
||||||
|
|
||||||
func sortMethods(list []*Func) {
|
func sortMethods(list []*Func) {
|
||||||
sort.Sort(byUniqueMethodName(list))
|
sort.Sort(byUniqueMethodName(list))
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,9 @@ func TestTypeSetString(t *testing.T) {
|
|||||||
"{int; string}": "∅",
|
"{int; string}": "∅",
|
||||||
|
|
||||||
"{comparable}": "{comparable}",
|
"{comparable}": "{comparable}",
|
||||||
"{comparable; int}": "{comparable; int}",
|
"{comparable; int}": "{int}",
|
||||||
"{~int; comparable}": "{comparable; ~int}",
|
"{~int; comparable}": "{~int}",
|
||||||
"{int|string; comparable}": "{comparable; int ∪ string}",
|
"{int|string; comparable}": "{int ∪ string}",
|
||||||
"{comparable; int; string}": "∅",
|
"{comparable; int; string}": "∅",
|
||||||
|
|
||||||
"{m()}": "{func (p.T).m()}",
|
"{m()}": "{func (p.T).m()}",
|
||||||
@ -37,8 +37,8 @@ func TestTypeSetString(t *testing.T) {
|
|||||||
"{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}",
|
"{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}",
|
||||||
"{comparable; error}": "{comparable; func (error).Error() string}",
|
"{comparable; error}": "{comparable; func (error).Error() string}",
|
||||||
|
|
||||||
"{m(); comparable; int|float32|string}": "{comparable; func (p.T).m(); int ∪ float32 ∪ string}",
|
"{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}",
|
||||||
"{m1(); int; m2(); comparable }": "{comparable; func (p.T).m1(); func (p.T).m2(); int}",
|
"{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}",
|
||||||
|
|
||||||
"{E}; type E interface{}": "𝓤",
|
"{E}; type E interface{}": "𝓤",
|
||||||
"{E}; type E interface{int;string}": "∅",
|
"{E}; type E interface{int;string}": "∅",
|
||||||
|
@ -100,25 +100,27 @@ func parseUnion(check *Checker, uexpr syntax.Expr) Type {
|
|||||||
|
|
||||||
if !Identical(u, t.typ) {
|
if !Identical(u, t.typ) {
|
||||||
check.errorf(tlist[i], "invalid use of ~ (underlying type of %s is %s)", t.typ, u)
|
check.errorf(tlist[i], "invalid use of ~ (underlying type of %s is %s)", t.typ, u)
|
||||||
continue // don't report another error for t
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stand-alone embedded interfaces are ok and are handled by the single-type case
|
// Stand-alone embedded interfaces are ok and are handled by the single-type case
|
||||||
// in the beginning. Embedded interfaces with tilde are excluded above. If we reach
|
// in the beginning. Embedded interfaces with tilde are excluded above. If we reach
|
||||||
// here, we must have at least two terms in the union.
|
// here, we must have at least two terms in the syntactic term list (but not necessarily
|
||||||
if f != nil && !f.typeSet().IsTypeSet() {
|
// in the term list of the union's type set).
|
||||||
|
if f != nil {
|
||||||
|
tset := f.typeSet()
|
||||||
switch {
|
switch {
|
||||||
case f.typeSet().NumMethods() != 0:
|
case tset.NumMethods() != 0:
|
||||||
check.errorf(tlist[i], "cannot use %s in union (%s contains methods)", t, t)
|
check.errorf(tlist[i], "cannot use %s in union (%s contains methods)", t, t)
|
||||||
|
continue
|
||||||
case t.typ == universeComparable.Type():
|
case t.typ == universeComparable.Type():
|
||||||
check.error(tlist[i], "cannot use comparable in union")
|
check.error(tlist[i], "cannot use comparable in union")
|
||||||
case f.typeSet().comparable:
|
continue
|
||||||
|
case tset.comparable:
|
||||||
check.errorf(tlist[i], "cannot use %s in union (%s embeds comparable)", t, t)
|
check.errorf(tlist[i], "cannot use %s in union (%s embeds comparable)", t, t)
|
||||||
default:
|
continue
|
||||||
panic("not a type set but no methods and not comparable")
|
|
||||||
}
|
}
|
||||||
continue // don't report another error for t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report overlapping (non-disjoint) terms such as
|
// Report overlapping (non-disjoint) terms such as
|
||||||
|
@ -111,7 +111,7 @@ func defPredeclaredTypes() {
|
|||||||
typ := NewNamed(obj, nil, nil)
|
typ := NewNamed(obj, nil, nil)
|
||||||
|
|
||||||
// interface{} // marked as comparable
|
// interface{} // marked as comparable
|
||||||
ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{true, nil, allTermlist}}
|
ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{nil, allTermlist, true}}
|
||||||
|
|
||||||
typ.SetUnderlying(ityp)
|
typ.SetUnderlying(ityp)
|
||||||
def(obj)
|
def(obj)
|
||||||
|
10
src/go/types/testdata/fixedbugs/issue41124.go2
vendored
10
src/go/types/testdata/fixedbugs/issue41124.go2
vendored
@ -47,7 +47,7 @@ type _ struct{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type _ struct{
|
type _ struct{
|
||||||
I3 // ERROR interface is .* comparable
|
I3 // ERROR interface contains type constraints
|
||||||
}
|
}
|
||||||
|
|
||||||
// General composite types.
|
// General composite types.
|
||||||
@ -59,19 +59,19 @@ type (
|
|||||||
_ []I1 // ERROR interface is .* comparable
|
_ []I1 // ERROR interface is .* comparable
|
||||||
_ []I2 // ERROR interface contains type constraints
|
_ []I2 // ERROR interface contains type constraints
|
||||||
|
|
||||||
_ *I3 // ERROR interface is .* comparable
|
_ *I3 // ERROR interface contains type constraints
|
||||||
_ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints
|
_ map[I1 /* ERROR interface is .* comparable */ ]I2 // ERROR interface contains type constraints
|
||||||
_ chan I3 // ERROR interface is .* comparable
|
_ chan I3 // ERROR interface contains type constraints
|
||||||
_ func(I1 /* ERROR interface is .* comparable */ )
|
_ func(I1 /* ERROR interface is .* comparable */ )
|
||||||
_ func() I2 // ERROR interface contains type constraints
|
_ func() I2 // ERROR interface contains type constraints
|
||||||
)
|
)
|
||||||
|
|
||||||
// Other cases.
|
// Other cases.
|
||||||
|
|
||||||
var _ = [...]I3 /* ERROR interface is .* comparable */ {}
|
var _ = [...]I3 /* ERROR interface contains type constraints */ {}
|
||||||
|
|
||||||
func _(x interface{}) {
|
func _(x interface{}) {
|
||||||
_ = x.(I3 /* ERROR interface is .* comparable */ )
|
_ = x.(I3 /* ERROR interface contains type constraints */ )
|
||||||
}
|
}
|
||||||
|
|
||||||
type T1[_ any] struct{}
|
type T1[_ any] struct{}
|
||||||
|
54
src/go/types/testdata/fixedbugs/issue51472.go2
vendored
Normal file
54
src/go/types/testdata/fixedbugs/issue51472.go2
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
func _[T comparable](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{interface{comparable}}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; interface{comparable}}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; ~int}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; ~[]byte}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gri) The error message here should be better. See issue #51525.
|
||||||
|
func _[T interface{comparable; ~int; ~string}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(gri) The error message here should be better. See issue #51525.
|
||||||
|
func _[T interface{~int; ~string}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{comparable; interface{~int}; interface{int|float64}}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _[T interface{interface{comparable; ~int}; interface{~float64; comparable; m()}}](x T) {
|
||||||
|
_ = x /* ERROR cannot compare */ == x
|
||||||
|
}
|
||||||
|
|
||||||
|
// test case from issue
|
||||||
|
|
||||||
|
func f[T interface{comparable; []byte|string}](x T) {
|
||||||
|
_ = x == x
|
||||||
|
}
|
||||||
|
|
||||||
|
func _(s []byte) {
|
||||||
|
f /* ERROR \[\]byte does not implement interface{comparable; \[\]byte\|string} */ (s)
|
||||||
|
_ = f[[ /* ERROR does not implement */ ]byte]
|
||||||
|
}
|
@ -15,18 +15,25 @@ import (
|
|||||||
// API
|
// API
|
||||||
|
|
||||||
// A _TypeSet represents the type set of an interface.
|
// A _TypeSet represents the type set of an interface.
|
||||||
|
// Because of existing language restrictions, methods can be "factored out"
|
||||||
|
// from the terms. The actual type set is the intersection of the type set
|
||||||
|
// implied by the methods and the type set described by the terms and the
|
||||||
|
// comparable bit. To test whether a type is included in a type set
|
||||||
|
// ("implements" relation), the type must implement all methods _and_ be
|
||||||
|
// an element of the type set described by the terms and the comparable bit.
|
||||||
|
// If the term list describes the set of all types and comparable is true,
|
||||||
|
// only comparable types are meant; in all other cases comparable is false.
|
||||||
type _TypeSet struct {
|
type _TypeSet struct {
|
||||||
comparable bool // if set, the interface is or embeds comparable
|
methods []*Func // all methods of the interface; sorted by unique ID
|
||||||
// TODO(gri) consider using a set for the methods for faster lookup
|
terms termlist // type terms of the type set
|
||||||
methods []*Func // all methods of the interface; sorted by unique ID
|
comparable bool // invariant: !comparable || terms.isAll()
|
||||||
terms termlist // type terms of the type set
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmpty reports whether type set s is the empty set.
|
// IsEmpty reports whether type set s is the empty set.
|
||||||
func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() }
|
func (s *_TypeSet) IsEmpty() bool { return s.terms.isEmpty() }
|
||||||
|
|
||||||
// IsAll reports whether type set s is the set of all types (corresponding to the empty interface).
|
// IsAll reports whether type set s is the set of all types (corresponding to the empty interface).
|
||||||
func (s *_TypeSet) IsAll() bool { return !s.comparable && len(s.methods) == 0 && s.terms.isAll() }
|
func (s *_TypeSet) IsAll() bool { return s.IsMethodSet() && len(s.methods) == 0 }
|
||||||
|
|
||||||
// IsMethodSet reports whether the interface t is fully described by its method set.
|
// IsMethodSet reports whether the interface t is fully described by its method set.
|
||||||
func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() }
|
func (s *_TypeSet) IsMethodSet() bool { return !s.comparable && s.terms.isAll() }
|
||||||
@ -41,13 +48,6 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gri) IsTypeSet is not a great name for this predicate. Find a better one.
|
|
||||||
|
|
||||||
// IsTypeSet reports whether the type set s is represented by a finite set of underlying types.
|
|
||||||
func (s *_TypeSet) IsTypeSet() bool {
|
|
||||||
return !s.comparable && len(s.methods) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumMethods returns the number of methods available.
|
// NumMethods returns the number of methods available.
|
||||||
func (s *_TypeSet) NumMethods() int { return len(s.methods) }
|
func (s *_TypeSet) NumMethods() int { return len(s.methods) }
|
||||||
|
|
||||||
@ -217,12 +217,12 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
|
|
||||||
var todo []*Func
|
var todo []*Func
|
||||||
var seen objset
|
var seen objset
|
||||||
var methods []*Func
|
var allMethods []*Func
|
||||||
mpos := make(map[*Func]token.Pos) // method specification or method embedding position, for good error messages
|
mpos := make(map[*Func]token.Pos) // method specification or method embedding position, for good error messages
|
||||||
addMethod := func(pos token.Pos, m *Func, explicit bool) {
|
addMethod := func(pos token.Pos, m *Func, explicit bool) {
|
||||||
switch other := seen.insert(m); {
|
switch other := seen.insert(m); {
|
||||||
case other == nil:
|
case other == nil:
|
||||||
methods = append(methods, m)
|
allMethods = append(allMethods, m)
|
||||||
mpos[m] = pos
|
mpos[m] = pos
|
||||||
case explicit:
|
case explicit:
|
||||||
if check == nil {
|
if check == nil {
|
||||||
@ -257,7 +257,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
}
|
}
|
||||||
|
|
||||||
// collect embedded elements
|
// collect embedded elements
|
||||||
var allTerms = allTermlist
|
allTerms := allTermlist
|
||||||
|
allComparable := false
|
||||||
for i, typ := range ityp.embeddeds {
|
for i, typ := range ityp.embeddeds {
|
||||||
// The embedding position is nil for imported interfaces
|
// The embedding position is nil for imported interfaces
|
||||||
// and also for interface copies after substitution (but
|
// and also for interface copies after substitution (but
|
||||||
@ -266,6 +267,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
if ityp.embedPos != nil {
|
if ityp.embedPos != nil {
|
||||||
pos = (*ityp.embedPos)[i]
|
pos = (*ityp.embedPos)[i]
|
||||||
}
|
}
|
||||||
|
var comparable bool
|
||||||
var terms termlist
|
var terms termlist
|
||||||
switch u := under(typ).(type) {
|
switch u := under(typ).(type) {
|
||||||
case *Interface:
|
case *Interface:
|
||||||
@ -277,9 +279,7 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
check.errorf(atPos(pos), _UnsupportedFeature, "embedding constraint interface %s requires go1.18 or later", typ)
|
check.errorf(atPos(pos), _UnsupportedFeature, "embedding constraint interface %s requires go1.18 or later", typ)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if tset.comparable {
|
comparable = tset.comparable
|
||||||
ityp.tset.comparable = true
|
|
||||||
}
|
|
||||||
for _, m := range tset.methods {
|
for _, m := range tset.methods {
|
||||||
addMethod(pos, m, false) // use embedding position pos rather than m.pos
|
addMethod(pos, m, false) // use embedding position pos rather than m.pos
|
||||||
}
|
}
|
||||||
@ -293,6 +293,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
if tset == &invalidTypeSet {
|
if tset == &invalidTypeSet {
|
||||||
continue // ignore invalid unions
|
continue // ignore invalid unions
|
||||||
}
|
}
|
||||||
|
assert(!tset.comparable)
|
||||||
|
assert(len(tset.methods) == 0)
|
||||||
terms = tset.terms
|
terms = tset.terms
|
||||||
default:
|
default:
|
||||||
if u == Typ[Invalid] {
|
if u == Typ[Invalid] {
|
||||||
@ -304,11 +306,11 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
}
|
}
|
||||||
terms = termlist{{false, typ}}
|
terms = termlist{{false, typ}}
|
||||||
}
|
}
|
||||||
// The type set of an interface is the intersection
|
|
||||||
// of the type sets of all its elements.
|
// The type set of an interface is the intersection of the type sets of all its elements.
|
||||||
// Intersection cannot produce longer termlists and
|
// Due to language restrictions, only embedded interfaces can add methods, they are handled
|
||||||
// thus cannot overflow.
|
// separately. Here we only need to intersect the term lists and comparable bits.
|
||||||
allTerms = allTerms.intersect(terms)
|
allTerms, allComparable = intersectTermLists(allTerms, allComparable, terms, comparable)
|
||||||
}
|
}
|
||||||
ityp.embedPos = nil // not needed anymore (errors have been reported)
|
ityp.embedPos = nil // not needed anymore (errors have been reported)
|
||||||
|
|
||||||
@ -321,15 +323,46 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if methods != nil {
|
ityp.tset.comparable = allComparable
|
||||||
sort.Sort(byUniqueMethodName(methods))
|
if len(allMethods) != 0 {
|
||||||
ityp.tset.methods = methods
|
sortMethods(allMethods)
|
||||||
|
ityp.tset.methods = allMethods
|
||||||
}
|
}
|
||||||
ityp.tset.terms = allTerms
|
ityp.tset.terms = allTerms
|
||||||
|
|
||||||
return ityp.tset
|
return ityp.tset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gri) The intersectTermLists function belongs to the termlist implementation.
|
||||||
|
// The comparable type set may also be best represented as a term (using
|
||||||
|
// a special type).
|
||||||
|
|
||||||
|
// intersectTermLists computes the intersection of two term lists and respective comparable bits.
|
||||||
|
// xcomp, ycomp are valid only if xterms.isAll() and yterms.isAll() respectively.
|
||||||
|
func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool) (termlist, bool) {
|
||||||
|
terms := xterms.intersect(yterms)
|
||||||
|
// If one of xterms or yterms is marked as comparable,
|
||||||
|
// the result must only include comparable types.
|
||||||
|
comp := xcomp || ycomp
|
||||||
|
if comp && !terms.isAll() {
|
||||||
|
// only keep comparable terms
|
||||||
|
i := 0
|
||||||
|
for _, t := range terms {
|
||||||
|
assert(t.typ != nil)
|
||||||
|
if Comparable(t.typ) {
|
||||||
|
terms[i] = t
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terms = terms[:i]
|
||||||
|
if !terms.isAll() {
|
||||||
|
comp = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(!comp || terms.isAll()) // comparable invariant
|
||||||
|
return terms, comp
|
||||||
|
}
|
||||||
|
|
||||||
func sortMethods(list []*Func) {
|
func sortMethods(list []*Func) {
|
||||||
sort.Sort(byUniqueMethodName(list))
|
sort.Sort(byUniqueMethodName(list))
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ func TestTypeSetString(t *testing.T) {
|
|||||||
"{int; string}": "∅",
|
"{int; string}": "∅",
|
||||||
|
|
||||||
"{comparable}": "{comparable}",
|
"{comparable}": "{comparable}",
|
||||||
"{comparable; int}": "{comparable; int}",
|
"{comparable; int}": "{int}",
|
||||||
"{~int; comparable}": "{comparable; ~int}",
|
"{~int; comparable}": "{~int}",
|
||||||
"{int|string; comparable}": "{comparable; int ∪ string}",
|
"{int|string; comparable}": "{int ∪ string}",
|
||||||
"{comparable; int; string}": "∅",
|
"{comparable; int; string}": "∅",
|
||||||
|
|
||||||
"{m()}": "{func (p.T).m()}",
|
"{m()}": "{func (p.T).m()}",
|
||||||
@ -38,8 +38,8 @@ func TestTypeSetString(t *testing.T) {
|
|||||||
"{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}",
|
"{m1(); comparable; m2() int }": "{comparable; func (p.T).m1(); func (p.T).m2() int}",
|
||||||
"{comparable; error}": "{comparable; func (error).Error() string}",
|
"{comparable; error}": "{comparable; func (error).Error() string}",
|
||||||
|
|
||||||
"{m(); comparable; int|float32|string}": "{comparable; func (p.T).m(); int ∪ float32 ∪ string}",
|
"{m(); comparable; int|float32|string}": "{func (p.T).m(); int ∪ float32 ∪ string}",
|
||||||
"{m1(); int; m2(); comparable }": "{comparable; func (p.T).m1(); func (p.T).m2(); int}",
|
"{m1(); int; m2(); comparable }": "{func (p.T).m1(); func (p.T).m2(); int}",
|
||||||
|
|
||||||
"{E}; type E interface{}": "𝓤",
|
"{E}; type E interface{}": "𝓤",
|
||||||
"{E}; type E interface{int;string}": "∅",
|
"{E}; type E interface{int;string}": "∅",
|
||||||
|
@ -103,25 +103,27 @@ func parseUnion(check *Checker, uexpr ast.Expr) Type {
|
|||||||
|
|
||||||
if !Identical(u, t.typ) {
|
if !Identical(u, t.typ) {
|
||||||
check.errorf(tlist[i], _InvalidUnion, "invalid use of ~ (underlying type of %s is %s)", t.typ, u)
|
check.errorf(tlist[i], _InvalidUnion, "invalid use of ~ (underlying type of %s is %s)", t.typ, u)
|
||||||
continue // don't report another error for t
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stand-alone embedded interfaces are ok and are handled by the single-type case
|
// Stand-alone embedded interfaces are ok and are handled by the single-type case
|
||||||
// in the beginning. Embedded interfaces with tilde are excluded above. If we reach
|
// in the beginning. Embedded interfaces with tilde are excluded above. If we reach
|
||||||
// here, we must have at least two terms in the union.
|
// here, we must have at least two terms in the syntactic term list (but not necessarily
|
||||||
if f != nil && !f.typeSet().IsTypeSet() {
|
// in the term list of the union's type set).
|
||||||
|
if f != nil {
|
||||||
|
tset := f.typeSet()
|
||||||
switch {
|
switch {
|
||||||
case f.typeSet().NumMethods() != 0:
|
case tset.NumMethods() != 0:
|
||||||
check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s contains methods)", t, t)
|
check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s contains methods)", t, t)
|
||||||
|
continue
|
||||||
case t.typ == universeComparable.Type():
|
case t.typ == universeComparable.Type():
|
||||||
check.error(tlist[i], _InvalidUnion, "cannot use comparable in union")
|
check.error(tlist[i], _InvalidUnion, "cannot use comparable in union")
|
||||||
case f.typeSet().comparable:
|
continue
|
||||||
|
case tset.comparable:
|
||||||
check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s embeds comparable)", t, t)
|
check.errorf(tlist[i], _InvalidUnion, "cannot use %s in union (%s embeds comparable)", t, t)
|
||||||
default:
|
continue
|
||||||
panic("not a type set but no methods and not comparable")
|
|
||||||
}
|
}
|
||||||
continue // don't report another error for t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report overlapping (non-disjoint) terms such as
|
// Report overlapping (non-disjoint) terms such as
|
||||||
|
@ -112,7 +112,7 @@ func defPredeclaredTypes() {
|
|||||||
typ := NewNamed(obj, nil, nil)
|
typ := NewNamed(obj, nil, nil)
|
||||||
|
|
||||||
// interface{} // marked as comparable
|
// interface{} // marked as comparable
|
||||||
ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{true, nil, allTermlist}}
|
ityp := &Interface{obj: obj, complete: true, tset: &_TypeSet{nil, allTermlist, true}}
|
||||||
|
|
||||||
typ.SetUnderlying(ityp)
|
typ.SetUnderlying(ityp)
|
||||||
def(obj)
|
def(obj)
|
||||||
|
Loading…
Reference in New Issue
Block a user