mirror of
https://github.com/golang/go
synced 2024-11-23 19:00:04 -07:00
go/types, types2: add "dynamic" flag to comparable predicate
A type implements a comparable interface only if the type is statically known to be comparable. Specifically, a type cannot contain (component) interfaces that are not statically known to be comparable. This CL adds a flag "dynamic" to the comparable predicate to control whether interfaces are always (dynamically) comparable. Set the flag to true when testing for (traditional) Go comparability; set the flag to false when testing whether a type implements the comparable interface. Fixes #51257. Change-Id: If22bc047ee59337deb2e7844b8f488d67e5c5530 Reviewed-on: https://go-review.googlesource.com/c/go/+/387055 Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
e534907f65
commit
163da6feb5
@ -899,7 +899,7 @@ func (check *Checker) incomparableCause(typ Type) string {
|
|||||||
}
|
}
|
||||||
// see if we can extract a more specific error
|
// see if we can extract a more specific error
|
||||||
var cause string
|
var cause string
|
||||||
comparable(typ, nil, func(format string, args ...interface{}) {
|
comparable(typ, true, nil, func(format string, args ...interface{}) {
|
||||||
cause = check.sprintf(format, args...)
|
cause = check.sprintf(format, args...)
|
||||||
})
|
})
|
||||||
return cause
|
return cause
|
||||||
|
@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error {
|
|||||||
// If T is comparable, V must be comparable.
|
// If T is comparable, V must be comparable.
|
||||||
// Remember as a pending error and report only if we don't have a more specific error.
|
// Remember as a pending error and report only if we don't have a more specific error.
|
||||||
var pending error
|
var pending error
|
||||||
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
|
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
|
||||||
pending = errorf("%s does not implement comparable", V)
|
pending = errorf("%s does not implement comparable", V)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,11 +102,12 @@ func isGeneric(t Type) bool {
|
|||||||
|
|
||||||
// Comparable reports whether values of type T are comparable.
|
// Comparable reports whether values of type T are comparable.
|
||||||
func Comparable(T Type) bool {
|
func Comparable(T Type) bool {
|
||||||
return comparable(T, nil, nil)
|
return comparable(T, true, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If dynamic is set, non-type parameter interfaces are always comparable.
|
||||||
// If reportf != nil, it may be used to report why T is not comparable.
|
// If reportf != nil, it may be used to report why T is not comparable.
|
||||||
func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool {
|
func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
|
||||||
if seen[T] {
|
if seen[T] {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -124,7 +125,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
|
|||||||
return true
|
return true
|
||||||
case *Struct:
|
case *Struct:
|
||||||
for _, f := range t.fields {
|
for _, f := range t.fields {
|
||||||
if !comparable(f.typ, seen, nil) {
|
if !comparable(f.typ, dynamic, seen, nil) {
|
||||||
if reportf != nil {
|
if reportf != nil {
|
||||||
reportf("struct containing %s cannot be compared", f.typ)
|
reportf("struct containing %s cannot be compared", f.typ)
|
||||||
}
|
}
|
||||||
@ -133,7 +134,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case *Array:
|
case *Array:
|
||||||
if !comparable(t.elem, seen, nil) {
|
if !comparable(t.elem, dynamic, seen, nil) {
|
||||||
if reportf != nil {
|
if reportf != nil {
|
||||||
reportf("%s cannot be compared", t)
|
reportf("%s cannot be compared", t)
|
||||||
}
|
}
|
||||||
@ -141,7 +142,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case *Interface:
|
case *Interface:
|
||||||
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
|
return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
46
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2
vendored
Normal file
46
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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 f[_ comparable]() {}
|
||||||
|
|
||||||
|
type S1 struct{ x int }
|
||||||
|
type S2 struct{ x any }
|
||||||
|
type S3 struct{ x [10]interface{ m() } }
|
||||||
|
|
||||||
|
func _[P1 comparable, P2 S2]() {
|
||||||
|
_ = f[S1]
|
||||||
|
_ = f[S2 /* ERROR S2 does not implement comparable */ ]
|
||||||
|
_ = f[S3 /* ERROR S3 does not implement comparable */ ]
|
||||||
|
|
||||||
|
type L1 struct { x P1 }
|
||||||
|
type L2 struct { x P2 }
|
||||||
|
_ = f[L1]
|
||||||
|
_ = f[L2 /* ERROR L2 does not implement comparable */ ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// example from issue
|
||||||
|
|
||||||
|
type Set[T comparable] map[T]struct{}
|
||||||
|
|
||||||
|
func NewSetFromSlice[T comparable](items []T) *Set[T] {
|
||||||
|
s := Set[T]{}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
s[item] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
type T struct{ x any }
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
NewSetFromSlice( /* ERROR T does not implement comparable */ []T{
|
||||||
|
{"foo"},
|
||||||
|
{5},
|
||||||
|
})
|
||||||
|
}
|
@ -39,7 +39,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
|
|||||||
return s.comparable
|
return s.comparable
|
||||||
}
|
}
|
||||||
return s.is(func(t *term) bool {
|
return s.is(func(t *term) bool {
|
||||||
return t != nil && comparable(t.typ, seen, nil)
|
return t != nil && comparable(t.typ, false, seen, nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -859,7 +859,7 @@ func (check *Checker) incomparableCause(typ Type) string {
|
|||||||
}
|
}
|
||||||
// see if we can extract a more specific error
|
// see if we can extract a more specific error
|
||||||
var cause string
|
var cause string
|
||||||
comparable(typ, nil, func(format string, args ...interface{}) {
|
comparable(typ, true, nil, func(format string, args ...interface{}) {
|
||||||
cause = check.sprintf(format, args...)
|
cause = check.sprintf(format, args...)
|
||||||
})
|
})
|
||||||
return cause
|
return cause
|
||||||
|
@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error {
|
|||||||
// If T is comparable, V must be comparable.
|
// If T is comparable, V must be comparable.
|
||||||
// Remember as a pending error and report only if we don't have a more specific error.
|
// Remember as a pending error and report only if we don't have a more specific error.
|
||||||
var pending error
|
var pending error
|
||||||
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
|
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
|
||||||
pending = errorf("%s does not implement comparable", V)
|
pending = errorf("%s does not implement comparable", V)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,11 +104,12 @@ func isGeneric(t Type) bool {
|
|||||||
|
|
||||||
// Comparable reports whether values of type T are comparable.
|
// Comparable reports whether values of type T are comparable.
|
||||||
func Comparable(T Type) bool {
|
func Comparable(T Type) bool {
|
||||||
return comparable(T, nil, nil)
|
return comparable(T, true, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If dynamic is set, non-type parameter interfaces are always comparable.
|
||||||
// If reportf != nil, it may be used to report why T is not comparable.
|
// If reportf != nil, it may be used to report why T is not comparable.
|
||||||
func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool {
|
func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
|
||||||
if seen[T] {
|
if seen[T] {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -126,7 +127,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
|
|||||||
return true
|
return true
|
||||||
case *Struct:
|
case *Struct:
|
||||||
for _, f := range t.fields {
|
for _, f := range t.fields {
|
||||||
if !comparable(f.typ, seen, nil) {
|
if !comparable(f.typ, dynamic, seen, nil) {
|
||||||
if reportf != nil {
|
if reportf != nil {
|
||||||
reportf("struct containing %s cannot be compared", f.typ)
|
reportf("struct containing %s cannot be compared", f.typ)
|
||||||
}
|
}
|
||||||
@ -135,7 +136,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case *Array:
|
case *Array:
|
||||||
if !comparable(t.elem, seen, nil) {
|
if !comparable(t.elem, dynamic, seen, nil) {
|
||||||
if reportf != nil {
|
if reportf != nil {
|
||||||
reportf("%s cannot be compared", t)
|
reportf("%s cannot be compared", t)
|
||||||
}
|
}
|
||||||
@ -143,7 +144,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
case *Interface:
|
case *Interface:
|
||||||
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
|
return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
46
src/go/types/testdata/fixedbugs/issue51257.go2
vendored
Normal file
46
src/go/types/testdata/fixedbugs/issue51257.go2
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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 f[_ comparable]() {}
|
||||||
|
|
||||||
|
type S1 struct{ x int }
|
||||||
|
type S2 struct{ x any }
|
||||||
|
type S3 struct{ x [10]interface{ m() } }
|
||||||
|
|
||||||
|
func _[P1 comparable, P2 S2]() {
|
||||||
|
_ = f[S1]
|
||||||
|
_ = f[S2 /* ERROR S2 does not implement comparable */ ]
|
||||||
|
_ = f[S3 /* ERROR S3 does not implement comparable */ ]
|
||||||
|
|
||||||
|
type L1 struct { x P1 }
|
||||||
|
type L2 struct { x P2 }
|
||||||
|
_ = f[L1]
|
||||||
|
_ = f[L2 /* ERROR L2 does not implement comparable */ ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// example from issue
|
||||||
|
|
||||||
|
type Set[T comparable] map[T]struct{}
|
||||||
|
|
||||||
|
func NewSetFromSlice[T comparable](items []T) *Set[T] {
|
||||||
|
s := Set[T]{}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
s[item] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
type T struct{ x any }
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
NewSetFromSlice /* ERROR T does not implement comparable */ ([]T{
|
||||||
|
{"foo"},
|
||||||
|
{5},
|
||||||
|
})
|
||||||
|
}
|
@ -37,7 +37,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
|
|||||||
return s.comparable
|
return s.comparable
|
||||||
}
|
}
|
||||||
return s.is(func(t *term) bool {
|
return s.is(func(t *term) bool {
|
||||||
return t != nil && comparable(t.typ, seen, nil)
|
return t != nil && comparable(t.typ, false, seen, nil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user