mirror of https://github.com/golang/go.git
go/types, types2: fix implements and identical predicates
- Use the correct predicate in Checker.implements: for interfaces we cannot use the API Comparable because it always returns true for all non-type parameter interface types: Comparable simply answers if == and != is permitted, and it's always been permitted for interfaces. Instead we must use Interface.IsComparable which looks at the type set of an interface. - When comparing interfaces for identity, we must also consider the whether the type sets have the comparable bit set. With this change, `any` doesn't implement `comparable` anymore. This only matters for generic functions and types, and the API functions. It does mean that for now (until we allow type-constrained interfaces for general non-constraint use, at some point in the future) a type parameter that needs to be comparable cannot be instantiated with an interface anymore. For #50646. Change-Id: I7e7f711bdcf94461f330c90509211ec0c2cf3633 Reviewed-on: https://go-review.googlesource.com/c/go/+/381254 Trust: Robert Griesemer <gri@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
41f485b9a7
commit
360e1b8197
|
|
@ -221,7 +221,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() && !Comparable(V) {
|
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
|
||||||
pending = errorf("%s does not implement comparable", V)
|
pending = errorf("%s does not implement comparable", V)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -623,16 +623,15 @@ func TestIssue50646(t *testing.T) {
|
||||||
t.Errorf("comparable is not a comparable type")
|
t.Errorf("comparable is not a comparable type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gri) should comparable be an alias, like any? (see #50791)
|
if Implements(anyType, comparableType.Underlying().(*Interface)) {
|
||||||
if !Implements(anyType, comparableType.Underlying().(*Interface)) {
|
t.Errorf("any implements comparable")
|
||||||
t.Errorf("any does not implement comparable")
|
|
||||||
}
|
}
|
||||||
if !Implements(comparableType, anyType.(*Interface)) {
|
if !Implements(comparableType, anyType.(*Interface)) {
|
||||||
t.Errorf("comparable does not implement any")
|
t.Errorf("comparable does not implement any")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !AssignableTo(anyType, comparableType) {
|
if AssignableTo(anyType, comparableType) {
|
||||||
t.Errorf("any not assignable to comparable")
|
t.Errorf("any assignable to comparable")
|
||||||
}
|
}
|
||||||
if !AssignableTo(comparableType, anyType) {
|
if !AssignableTo(comparableType, anyType) {
|
||||||
t.Errorf("comparable not assignable to any")
|
t.Errorf("comparable not assignable to any")
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,9 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
|
||||||
if y, ok := y.(*Interface); ok {
|
if y, ok := y.(*Interface); ok {
|
||||||
xset := x.typeSet()
|
xset := x.typeSet()
|
||||||
yset := y.typeSet()
|
yset := y.typeSet()
|
||||||
|
if xset.comparable != yset.comparable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !xset.terms.equal(yset.terms) {
|
if !xset.terms.equal(yset.terms) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,18 @@ package p
|
||||||
import "io"
|
import "io"
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
// Interfaces are always comparable (though the comparison may panic at runtime).
|
|
||||||
func eql[T comparable](x, y T) bool {
|
func eql[T comparable](x, y T) bool {
|
||||||
return x == y
|
return x == y
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _[X comparable, Y interface{comparable; m()}]() {
|
||||||
var x interface{}
|
var x X
|
||||||
var y interface{ m() }
|
var y Y
|
||||||
eql(x, y /* ERROR does not match */ ) // interfaces of different types
|
eql(x, y /* ERROR does not match */ ) // interfaces of different types
|
||||||
eql(x, x)
|
eql(x, x)
|
||||||
eql(y, y)
|
eql(y, y)
|
||||||
eql(y, nil)
|
eql(y, nil /* ERROR cannot use nil as Y value in argument to eql */ )
|
||||||
eql[io.Reader](nil, nil)
|
eql[io /* ERROR does not implement comparable */ .Reader](nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a receiver of pointer to type parameter type (below: *T)
|
// If we have a receiver of pointer to type parameter type (below: *T)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
package p
|
package p
|
||||||
|
|
||||||
// Because we can use == and != with values of arbitrary
|
|
||||||
// interfaces, all interfaces implement comparable.
|
|
||||||
|
|
||||||
func f1[_ comparable]() {}
|
func f1[_ comparable]() {}
|
||||||
func f2[_ interface{ comparable }]() {}
|
func f2[_ interface{ comparable }]() {}
|
||||||
|
|
||||||
|
|
@ -14,15 +11,15 @@ type T interface{ m() }
|
||||||
|
|
||||||
func _[P comparable, Q ~int, R any]() {
|
func _[P comparable, Q ~int, R any]() {
|
||||||
_ = f1[int]
|
_ = f1[int]
|
||||||
_ = f1[T]
|
_ = f1[T /* ERROR T does not implement comparable */ ]
|
||||||
_ = f1[any]
|
_ = f1[any /* ERROR any does not implement comparable */ ]
|
||||||
_ = f1[P]
|
_ = f1[P]
|
||||||
_ = f1[Q]
|
_ = f1[Q]
|
||||||
_ = f1[R /* ERROR R does not implement comparable */]
|
_ = f1[R /* ERROR R does not implement comparable */]
|
||||||
|
|
||||||
_ = f2[int]
|
_ = f2[int]
|
||||||
_ = f2[T]
|
_ = f2[T /* ERROR T does not implement comparable */ ]
|
||||||
_ = f2[any]
|
_ = f2[any /* ERROR any does not implement comparable */ ]
|
||||||
_ = f2[P]
|
_ = f2[P]
|
||||||
_ = f2[Q]
|
_ = f2[Q]
|
||||||
_ = f2[R /* ERROR R does not implement comparable */]
|
_ = f2[R /* ERROR R does not implement comparable */]
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
|
||||||
if y, ok := y.(*Interface); ok {
|
if y, ok := y.(*Interface); ok {
|
||||||
xset := x.typeSet()
|
xset := x.typeSet()
|
||||||
yset := y.typeSet()
|
yset := y.typeSet()
|
||||||
|
if xset.comparable != yset.comparable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !xset.terms.equal(yset.terms) {
|
if !xset.terms.equal(yset.terms) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,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() && !Comparable(V) {
|
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
|
||||||
pending = errorf("%s does not implement comparable", V)
|
pending = errorf("%s does not implement comparable", V)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -650,16 +650,15 @@ func TestIssue50646(t *testing.T) {
|
||||||
t.Errorf("comparable is not a comparable type")
|
t.Errorf("comparable is not a comparable type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gri) should comparable be an alias, like any? (see #50791)
|
if Implements(anyType, comparableType.Underlying().(*Interface)) {
|
||||||
if !Implements(anyType, comparableType.Underlying().(*Interface)) {
|
t.Errorf("any implements comparable")
|
||||||
t.Errorf("any does not implement comparable")
|
|
||||||
}
|
}
|
||||||
if !Implements(comparableType, anyType.(*Interface)) {
|
if !Implements(comparableType, anyType.(*Interface)) {
|
||||||
t.Errorf("comparable does not implement any")
|
t.Errorf("comparable does not implement any")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !AssignableTo(anyType, comparableType) {
|
if AssignableTo(anyType, comparableType) {
|
||||||
t.Errorf("any not assignable to comparable")
|
t.Errorf("any assignable to comparable")
|
||||||
}
|
}
|
||||||
if !AssignableTo(comparableType, anyType) {
|
if !AssignableTo(comparableType, anyType) {
|
||||||
t.Errorf("comparable not assignable to any")
|
t.Errorf("comparable not assignable to any")
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,9 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
|
||||||
if y, ok := y.(*Interface); ok {
|
if y, ok := y.(*Interface); ok {
|
||||||
xset := x.typeSet()
|
xset := x.typeSet()
|
||||||
yset := y.typeSet()
|
yset := y.typeSet()
|
||||||
|
if xset.comparable != yset.comparable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !xset.terms.equal(yset.terms) {
|
if !xset.terms.equal(yset.terms) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,18 @@ package p
|
||||||
import "io"
|
import "io"
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
// Interfaces are always comparable (though the comparison may panic at runtime).
|
|
||||||
func eql[T comparable](x, y T) bool {
|
func eql[T comparable](x, y T) bool {
|
||||||
return x == y
|
return x == y
|
||||||
}
|
}
|
||||||
|
|
||||||
func _() {
|
func _[X comparable, Y interface{comparable; m()}]() {
|
||||||
var x interface{}
|
var x X
|
||||||
var y interface{ m() }
|
var y Y
|
||||||
eql(x, y /* ERROR does not match */ ) // interfaces of different types
|
eql(x, y /* ERROR does not match */ ) // interfaces of different types
|
||||||
eql(x, x)
|
eql(x, x)
|
||||||
eql(y, y)
|
eql(y, y)
|
||||||
eql(y, nil)
|
eql(y, nil /* ERROR cannot use nil as Y value in argument to eql */ )
|
||||||
eql[io.Reader](nil, nil)
|
eql[io /* ERROR does not implement comparable */ .Reader](nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a receiver of pointer to type parameter type (below: *T)
|
// If we have a receiver of pointer to type parameter type (below: *T)
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
package p
|
package p
|
||||||
|
|
||||||
// Because we can use == and != with values of arbitrary
|
|
||||||
// interfaces, all interfaces implement comparable.
|
|
||||||
|
|
||||||
func f1[_ comparable]() {}
|
func f1[_ comparable]() {}
|
||||||
func f2[_ interface{ comparable }]() {}
|
func f2[_ interface{ comparable }]() {}
|
||||||
|
|
||||||
|
|
@ -14,15 +11,15 @@ type T interface{ m() }
|
||||||
|
|
||||||
func _[P comparable, Q ~int, R any]() {
|
func _[P comparable, Q ~int, R any]() {
|
||||||
_ = f1[int]
|
_ = f1[int]
|
||||||
_ = f1[T]
|
_ = f1[T /* ERROR T does not implement comparable */ ]
|
||||||
_ = f1[any]
|
_ = f1[any /* ERROR any does not implement comparable */ ]
|
||||||
_ = f1[P]
|
_ = f1[P]
|
||||||
_ = f1[Q]
|
_ = f1[Q]
|
||||||
_ = f1[R /* ERROR R does not implement comparable */]
|
_ = f1[R /* ERROR R does not implement comparable */]
|
||||||
|
|
||||||
_ = f2[int]
|
_ = f2[int]
|
||||||
_ = f2[T]
|
_ = f2[T /* ERROR T does not implement comparable */ ]
|
||||||
_ = f2[any]
|
_ = f2[any /* ERROR any does not implement comparable */ ]
|
||||||
_ = f2[P]
|
_ = f2[P]
|
||||||
_ = f2[Q]
|
_ = f2[Q]
|
||||||
_ = f2[R /* ERROR R does not implement comparable */]
|
_ = f2[R /* ERROR R does not implement comparable */]
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
|
||||||
if y, ok := y.(*Interface); ok {
|
if y, ok := y.(*Interface); ok {
|
||||||
xset := x.typeSet()
|
xset := x.typeSet()
|
||||||
yset := y.typeSet()
|
yset := y.typeSet()
|
||||||
|
if xset.comparable != yset.comparable {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if !xset.terms.equal(yset.terms) {
|
if !xset.terms.equal(yset.terms) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ package main
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
IsZero[interface{}]("")
|
IsZero[int](0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsZero[T comparable](val T) bool {
|
func IsZero[T comparable](val T) bool {
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
<nil>:
|
0:0
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// run -gcflags=-G=3
|
|
||||||
|
|
||||||
// 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 main
|
|
||||||
|
|
||||||
func eql[P comparable](x, y P) {
|
|
||||||
if x != y {
|
|
||||||
panic("not equal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectPanic(f func()) {
|
|
||||||
defer func() {
|
|
||||||
if recover() == nil {
|
|
||||||
panic("function succeeded unexpectedly")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
eql[int](1, 1)
|
|
||||||
eql(1, 1)
|
|
||||||
|
|
||||||
// all interfaces implement comparable
|
|
||||||
var x, y any = 2, 2
|
|
||||||
eql[any](x, y)
|
|
||||||
eql(x, y)
|
|
||||||
|
|
||||||
// but we may get runtime panics
|
|
||||||
x, y = 1, 2 // x != y
|
|
||||||
expectPanic(func() { eql(x, y) })
|
|
||||||
|
|
||||||
x, y = main, main // functions are not comparable
|
|
||||||
expectPanic(func() { eql(x, y) })
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue