1
0
mirror of https://github.com/golang/go synced 2024-09-23 23:10:13 -06: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:
Robert Griesemer 2022-02-20 12:58:21 -08:00
parent e534907f65
commit 163da6feb5
10 changed files with 110 additions and 16 deletions

View File

@ -899,7 +899,7 @@ func (check *Checker) incomparableCause(typ Type) string {
}
// see if we can extract a more specific error
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...)
})
return cause

View File

@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error {
// 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.
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)
}

View File

@ -102,11 +102,12 @@ func isGeneric(t Type) bool {
// Comparable reports whether values of type T are comparable.
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.
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] {
return true
}
@ -124,7 +125,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
return true
case *Struct:
for _, f := range t.fields {
if !comparable(f.typ, seen, nil) {
if !comparable(f.typ, dynamic, seen, nil) {
if reportf != nil {
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
case *Array:
if !comparable(t.elem, seen, nil) {
if !comparable(t.elem, dynamic, seen, nil) {
if reportf != nil {
reportf("%s cannot be compared", t)
}
@ -141,7 +142,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
}
return true
case *Interface:
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
}
return false
}

View 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},
})
}

View File

@ -39,7 +39,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
return s.comparable
}
return s.is(func(t *term) bool {
return t != nil && comparable(t.typ, seen, nil)
return t != nil && comparable(t.typ, false, seen, nil)
})
}

View File

@ -859,7 +859,7 @@ func (check *Checker) incomparableCause(typ Type) string {
}
// see if we can extract a more specific error
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...)
})
return cause

View File

@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error {
// 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.
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)
}

View File

@ -104,11 +104,12 @@ func isGeneric(t Type) bool {
// Comparable reports whether values of type T are comparable.
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.
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] {
return true
}
@ -126,7 +127,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
return true
case *Struct:
for _, f := range t.fields {
if !comparable(f.typ, seen, nil) {
if !comparable(f.typ, dynamic, seen, nil) {
if reportf != nil {
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
case *Array:
if !comparable(t.elem, seen, nil) {
if !comparable(t.elem, dynamic, seen, nil) {
if reportf != nil {
reportf("%s cannot be compared", t)
}
@ -143,7 +144,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
}
return true
case *Interface:
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
}
return false
}

View 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},
})
}

View File

@ -37,7 +37,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
return s.comparable
}
return s.is(func(t *term) bool {
return t != nil && comparable(t.typ, seen, nil)
return t != nil && comparable(t.typ, false, seen, nil)
})
}