go/types, types2: use errorCause instead of reportf in comparableType

If the error cause is not further specified (empty string),
avoid allocating a new errorCause. This makes using errorCauses
as boolean signals efficient.

While at it, fix an error message for incomparable arrays:
report the array type rather than its underlying type.

Change-Id: I844b18a76695330ca726932ee760aa89635f6a38
Reviewed-on: https://go-review.googlesource.com/c/go/+/654575
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2025-03-04 08:47:25 -08:00 committed by Gopher Robot
parent 584e631023
commit 5af3658eaa
11 changed files with 81 additions and 77 deletions

View File

@ -621,11 +621,7 @@ func (check *Checker) incomparableCause(typ Type) string {
return compositeKind(typ) + " can only be compared to nil"
}
// see if we can extract a more specific error
var cause string
comparableType(typ, true, nil, func(format string, args ...interface{}) {
cause = check.sprintf(format, args...)
})
return cause
return comparableType(typ, true, nil).format(check)
}
// If e != nil, it must be the shift expression; it may be nil for non-constant shifts.

View File

@ -296,12 +296,12 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool
}
// If T is comparable, V must be comparable.
// If V is strictly comparable, we're done.
if comparableType(V, false /* strict comparability */, nil, nil) {
if comparableType(V, false /* strict comparability */, nil) == nil {
return true
}
// For constraint satisfaction, use dynamic (spec) comparability
// so that ordinary, non-type parameter interfaces implement comparable.
if constraint && comparableType(V, true /* spec comparability */, nil, nil) {
if constraint && comparableType(V, true /* spec comparability */, nil) == nil {
// V is comparable if we are at Go 1.20 or higher.
if check == nil || check.allowVersion(go1_20) {
return true

View File

@ -148,14 +148,15 @@ func isGeneric(t Type) bool {
// Comparable reports whether values of type T are comparable.
func Comparable(T Type) bool {
return comparableType(T, true, nil, nil)
return comparableType(T, true, nil) == nil
}
// If T is comparable, comparableType returns nil.
// Otherwise it returns an error cause explaining why T is not comparable.
// 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 comparableType(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
func comparableType(T Type, dynamic bool, seen map[Type]bool) *errorCause {
if seen[T] {
return true
return nil
}
if seen == nil {
seen = make(map[Type]bool)
@ -164,43 +165,43 @@ func comparableType(T Type, dynamic bool, seen map[Type]bool, reportf func(strin
switch t := under(T).(type) {
case *Basic:
// assume invalid types to be comparable
// to avoid follow-up errors
return t.kind != UntypedNil
// assume invalid types to be comparable to avoid follow-up errors
if t.kind == UntypedNil {
return newErrorCause("")
}
case *Pointer, *Chan:
return true
// always comparable
case *Struct:
for _, f := range t.fields {
if !comparableType(f.typ, dynamic, seen, nil) {
if reportf != nil {
reportf("struct containing %s cannot be compared", f.typ)
}
return false
if comparableType(f.typ, dynamic, seen) != nil {
return newErrorCause("struct containing %s cannot be compared", f.typ)
}
}
return true
case *Array:
if !comparableType(t.elem, dynamic, seen, nil) {
if reportf != nil {
reportf("%s cannot be compared", t)
}
return false
if comparableType(t.elem, dynamic, seen) != nil {
return newErrorCause("%s cannot be compared", T)
}
return true
case *Interface:
if dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen) {
return true
return nil
}
if reportf != nil {
if t.typeSet().IsEmpty() {
reportf("empty type set")
} else {
reportf("incomparable types in type set")
}
var cause string
if t.typeSet().IsEmpty() {
cause = "empty type set"
} else {
cause = "incomparable types in type set"
}
// fallthrough
return newErrorCause(cause)
default:
return newErrorCause("")
}
return false
return nil
}
// hasNil reports whether type t includes the nil value.

View File

@ -44,7 +44,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
return s.comparable
}
return s.is(func(t *term) bool {
return t != nil && comparableType(t.typ, false, seen, nil)
return t != nil && comparableType(t.typ, false, seen) == nil
})
}
@ -331,7 +331,7 @@ func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool
i := 0
for _, t := range terms {
assert(t.typ != nil)
if comparableType(t.typ, false /* strictly comparable */, nil, nil) {
if comparableType(t.typ, false /* strictly comparable */, nil) == nil {
terms[i] = t
i++
}

View File

@ -46,7 +46,12 @@ type errorCause struct {
args []any
}
var emptyErrorCause errorCause
func newErrorCause(format string, args ...any) *errorCause {
if format == "" {
return &emptyErrorCause
}
return &errorCause{format, args}
}

View File

@ -610,11 +610,7 @@ func (check *Checker) incomparableCause(typ Type) string {
return compositeKind(typ) + " can only be compared to nil"
}
// see if we can extract a more specific error
var cause string
comparableType(typ, true, nil, func(format string, args ...interface{}) {
cause = check.sprintf(format, args...)
})
return cause
return comparableType(typ, true, nil).format(check)
}
// If e != nil, it must be the shift expression; it may be nil for non-constant shifts.

View File

@ -299,12 +299,12 @@ func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool
}
// If T is comparable, V must be comparable.
// If V is strictly comparable, we're done.
if comparableType(V, false /* strict comparability */, nil, nil) {
if comparableType(V, false /* strict comparability */, nil) == nil {
return true
}
// For constraint satisfaction, use dynamic (spec) comparability
// so that ordinary, non-type parameter interfaces implement comparable.
if constraint && comparableType(V, true /* spec comparability */, nil, nil) {
if constraint && comparableType(V, true /* spec comparability */, nil) == nil {
// V is comparable if we are at Go 1.20 or higher.
if check == nil || check.allowVersion(go1_20) {
return true

View File

@ -151,14 +151,15 @@ func isGeneric(t Type) bool {
// Comparable reports whether values of type T are comparable.
func Comparable(T Type) bool {
return comparableType(T, true, nil, nil)
return comparableType(T, true, nil) == nil
}
// If T is comparable, comparableType returns nil.
// Otherwise it returns an error cause explaining why T is not comparable.
// 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 comparableType(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
func comparableType(T Type, dynamic bool, seen map[Type]bool) *errorCause {
if seen[T] {
return true
return nil
}
if seen == nil {
seen = make(map[Type]bool)
@ -167,43 +168,43 @@ func comparableType(T Type, dynamic bool, seen map[Type]bool, reportf func(strin
switch t := under(T).(type) {
case *Basic:
// assume invalid types to be comparable
// to avoid follow-up errors
return t.kind != UntypedNil
// assume invalid types to be comparable to avoid follow-up errors
if t.kind == UntypedNil {
return newErrorCause("")
}
case *Pointer, *Chan:
return true
// always comparable
case *Struct:
for _, f := range t.fields {
if !comparableType(f.typ, dynamic, seen, nil) {
if reportf != nil {
reportf("struct containing %s cannot be compared", f.typ)
}
return false
if comparableType(f.typ, dynamic, seen) != nil {
return newErrorCause("struct containing %s cannot be compared", f.typ)
}
}
return true
case *Array:
if !comparableType(t.elem, dynamic, seen, nil) {
if reportf != nil {
reportf("%s cannot be compared", t)
}
return false
if comparableType(t.elem, dynamic, seen) != nil {
return newErrorCause("%s cannot be compared", T)
}
return true
case *Interface:
if dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen) {
return true
return nil
}
if reportf != nil {
if t.typeSet().IsEmpty() {
reportf("empty type set")
} else {
reportf("incomparable types in type set")
}
var cause string
if t.typeSet().IsEmpty() {
cause = "empty type set"
} else {
cause = "incomparable types in type set"
}
// fallthrough
return newErrorCause(cause)
default:
return newErrorCause("")
}
return false
return nil
}
// hasNil reports whether type t includes the nil value.

View File

@ -47,7 +47,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
return s.comparable
}
return s.is(func(t *term) bool {
return t != nil && comparableType(t.typ, false, seen, nil)
return t != nil && comparableType(t.typ, false, seen) == nil
})
}
@ -334,7 +334,7 @@ func intersectTermLists(xterms termlist, xcomp bool, yterms termlist, ycomp bool
i := 0
for _, t := range terms {
assert(t.typ != nil)
if comparableType(t.typ, false /* strictly comparable */, nil, nil) {
if comparableType(t.typ, false /* strictly comparable */, nil) == nil {
terms[i] = t
i++
}

View File

@ -49,7 +49,12 @@ type errorCause struct {
args []any
}
var emptyErrorCause errorCause
func newErrorCause(format string, args ...any) *errorCause {
if format == "" {
return &emptyErrorCause
}
return &errorCause{format, args}
}

View File

@ -31,7 +31,7 @@ var (
func _() {
_ = nil == nil // ERROR "operator == not defined on untyped nil"
_ = b == b
_ = a /* ERROR "[10]func() cannot be compared" */ == a
_ = a /* ERROR "A cannot be compared" */ == a
_ = l /* ERROR "slice can only be compared to nil" */ == l
_ = s /* ERROR "struct containing []byte cannot be compared" */ == s
_ = p == p