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:
Robert Griesemer 2022-01-26 22:48:44 -08:00
parent 41f485b9a7
commit 360e1b8197
15 changed files with 42 additions and 79 deletions

View File

@ -221,7 +221,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() && !Comparable(V) {
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
pending = errorf("%s does not implement comparable", V)
}

View File

@ -623,16 +623,15 @@ func TestIssue50646(t *testing.T) {
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)) {
t.Errorf("any does not implement comparable")
if Implements(anyType, comparableType.Underlying().(*Interface)) {
t.Errorf("any implements comparable")
}
if !Implements(comparableType, anyType.(*Interface)) {
t.Errorf("comparable does not implement any")
}
if !AssignableTo(anyType, comparableType) {
t.Errorf("any not assignable to comparable")
if AssignableTo(anyType, comparableType) {
t.Errorf("any assignable to comparable")
}
if !AssignableTo(comparableType, anyType) {
t.Errorf("comparable not assignable to any")

View File

@ -306,6 +306,9 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
if y, ok := y.(*Interface); ok {
xset := x.typeSet()
yset := y.typeSet()
if xset.comparable != yset.comparable {
return false
}
if !xset.terms.equal(yset.terms) {
return false
}

View File

@ -9,19 +9,18 @@ package p
import "io"
import "context"
// Interfaces are always comparable (though the comparison may panic at runtime).
func eql[T comparable](x, y T) bool {
return x == y
}
func _() {
var x interface{}
var y interface{ m() }
func _[X comparable, Y interface{comparable; m()}]() {
var x X
var y Y
eql(x, y /* ERROR does not match */ ) // interfaces of different types
eql(x, x)
eql(y, y)
eql(y, nil)
eql[io.Reader](nil, nil)
eql(y, nil /* ERROR cannot use nil as Y value in argument to eql */ )
eql[io /* ERROR does not implement comparable */ .Reader](nil, nil)
}
// If we have a receiver of pointer to type parameter type (below: *T)

View File

@ -4,9 +4,6 @@
package p
// Because we can use == and != with values of arbitrary
// interfaces, all interfaces implement comparable.
func f1[_ comparable]() {}
func f2[_ interface{ comparable }]() {}
@ -14,15 +11,15 @@ type T interface{ m() }
func _[P comparable, Q ~int, R any]() {
_ = f1[int]
_ = f1[T]
_ = f1[any]
_ = f1[T /* ERROR T does not implement comparable */ ]
_ = f1[any /* ERROR any does not implement comparable */ ]
_ = f1[P]
_ = f1[Q]
_ = f1[R /* ERROR R does not implement comparable */]
_ = f2[int]
_ = f2[T]
_ = f2[any]
_ = f2[T /* ERROR T does not implement comparable */ ]
_ = f2[any /* ERROR any does not implement comparable */ ]
_ = f2[P]
_ = f2[Q]
_ = f2[R /* ERROR R does not implement comparable */]

View File

@ -387,6 +387,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
if y, ok := y.(*Interface); ok {
xset := x.typeSet()
yset := y.typeSet()
if xset.comparable != yset.comparable {
return false
}
if !xset.terms.equal(yset.terms) {
return false
}

View File

@ -225,7 +225,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() && !Comparable(V) {
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
pending = errorf("%s does not implement comparable", V)
}

View File

@ -650,16 +650,15 @@ func TestIssue50646(t *testing.T) {
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)) {
t.Errorf("any does not implement comparable")
if Implements(anyType, comparableType.Underlying().(*Interface)) {
t.Errorf("any implements comparable")
}
if !Implements(comparableType, anyType.(*Interface)) {
t.Errorf("comparable does not implement any")
}
if !AssignableTo(anyType, comparableType) {
t.Errorf("any not assignable to comparable")
if AssignableTo(anyType, comparableType) {
t.Errorf("any assignable to comparable")
}
if !AssignableTo(comparableType, anyType) {
t.Errorf("comparable not assignable to any")

View File

@ -308,6 +308,9 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool {
if y, ok := y.(*Interface); ok {
xset := x.typeSet()
yset := y.typeSet()
if xset.comparable != yset.comparable {
return false
}
if !xset.terms.equal(yset.terms) {
return false
}

View File

@ -9,19 +9,18 @@ package p
import "io"
import "context"
// Interfaces are always comparable (though the comparison may panic at runtime).
func eql[T comparable](x, y T) bool {
return x == y
}
func _() {
var x interface{}
var y interface{ m() }
func _[X comparable, Y interface{comparable; m()}]() {
var x X
var y Y
eql(x, y /* ERROR does not match */ ) // interfaces of different types
eql(x, x)
eql(y, y)
eql(y, nil)
eql[io.Reader](nil, nil)
eql(y, nil /* ERROR cannot use nil as Y value in argument to eql */ )
eql[io /* ERROR does not implement comparable */ .Reader](nil, nil)
}
// If we have a receiver of pointer to type parameter type (below: *T)

View File

@ -4,9 +4,6 @@
package p
// Because we can use == and != with values of arbitrary
// interfaces, all interfaces implement comparable.
func f1[_ comparable]() {}
func f2[_ interface{ comparable }]() {}
@ -14,15 +11,15 @@ type T interface{ m() }
func _[P comparable, Q ~int, R any]() {
_ = f1[int]
_ = f1[T]
_ = f1[any]
_ = f1[T /* ERROR T does not implement comparable */ ]
_ = f1[any /* ERROR any does not implement comparable */ ]
_ = f1[P]
_ = f1[Q]
_ = f1[R /* ERROR R does not implement comparable */]
_ = f2[int]
_ = f2[T]
_ = f2[any]
_ = f2[T /* ERROR T does not implement comparable */ ]
_ = f2[any /* ERROR any does not implement comparable */ ]
_ = f2[P]
_ = f2[Q]
_ = f2[R /* ERROR R does not implement comparable */]

View File

@ -387,6 +387,9 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool {
if y, ok := y.(*Interface); ok {
xset := x.typeSet()
yset := y.typeSet()
if xset.comparable != yset.comparable {
return false
}
if !xset.terms.equal(yset.terms) {
return false
}

View File

@ -9,7 +9,7 @@ package main
import "fmt"
func main() {
IsZero[interface{}]("")
IsZero[int](0)
}
func IsZero[T comparable](val T) bool {

View File

@ -1 +1 @@
<nil>:
0:0

View File

@ -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) })
}