go/types, types2: better error messages for comparisons

Refactor Checker.comparison such that its logic is easier to reason
about and so that special cases can be handled more directly.

Use the appropriate operand (of 1st or 2nd operand) for error
reporting (position and type), rather than always using the
first operand.

Use an extra parameter to indicate a switch case
comparison; in this case the error is always reported at
the position of the first operand. (The error messages are
not yet adjusted for switches; see next CL.)

Introduce a new kindString function which is used to print simplified
types in error messages (related to comparisons only): instead of
printing the details of a struct type, we just print "struct" where
the details are not relevant. This matches the 1.17 compiler behavior.

Added a "reportf" parameter to the internal comparable function so we
can report an error cause in addition to the boolean result. Rather
than passing a *string for cause, we pass a function to record the
cause so that we can use the *Checker context for printing (needed
for proper type qualification). This mechanism reports the same
details now as the 1.17 compiler.

Adjusted various tests as needed added new test files.

Fixes #50918.

Change-Id: I1f0e7af22f09db4d31679c667c71a9038a8dc9d2
Reviewed-on: https://go-review.googlesource.com/c/go/+/381964
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2022-01-31 21:25:14 -08:00
parent f9763a648b
commit e052044d6b
17 changed files with 643 additions and 145 deletions

View File

@ -770,52 +770,82 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
return target, nil, 0 return target, nil, 0
} }
func (check *Checker) comparison(x, y *operand, op syntax.Operator) { // If switchCase is true, the operator op is ignored.
func (check *Checker) comparison(x, y *operand, op syntax.Operator, switchCase bool) {
if switchCase {
op = syntax.Eql
}
errOp := x // operand for which error is reported, if any
cause := "" // specific error cause, if any
// spec: "In any comparison, the first operand must be assignable // spec: "In any comparison, the first operand must be assignable
// to the type of the second operand, or vice versa." // to the type of the second operand, or vice versa."
err := "" ok, _ := x.assignableTo(check, y.typ, nil)
xok, _ := x.assignableTo(check, y.typ, nil) if !ok {
yok, _ := y.assignableTo(check, x.typ, nil) ok, _ = y.assignableTo(check, x.typ, nil)
if xok || yok { }
equality := false if !ok {
defined := false // Report the error on the 2nd operand since we only
switch op { // know after seeing the 2nd operand whether we have
case syntax.Eql, syntax.Neq: // a type mismatch.
// spec: "The equality operators == and != apply to operands that are comparable." errOp = y
equality = true // For now, if we're not running the compiler, use the
defined = Comparable(x.typ) && Comparable(y.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) // position of x to minimize changes to existing tests.
case syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq: if !check.conf.CompilerErrorMessages {
// spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." errOp = x
defined = allOrdered(x.typ) && allOrdered(y.typ)
default:
unreachable()
} }
if !defined { cause = check.sprintf("mismatched types %s and %s", x.typ, y.typ)
if equality && (isTypeParam(x.typ) || isTypeParam(y.typ)) { goto Error
typ := x.typ }
if isTypeParam(y.typ) {
typ = y.typ // check if comparison is defined for operands
} switch op {
err = check.sprintf("%s is not comparable", typ) case syntax.Eql, syntax.Neq:
} else { // spec: "The equality operators == and != apply to operands that are comparable."
typ := x.typ switch {
if x.isNil() { case x.isNil() || y.isNil():
typ = y.typ // Comparison against nil requires that the other operand type has nil.
} typ := x.typ
err = check.sprintf("operator %s not defined on %s", op, typ) if x.isNil() {
typ = y.typ
} }
if !hasNil(typ) {
// This case should only be possible for "nil == nil".
// Report the error on the 2nd operand since we only
// know after seeing the 2nd operand whether we have
// an invalid comparison.
errOp = y
goto Error
}
case !Comparable(x.typ):
errOp = x
cause = check.incomparableCause(x.typ)
goto Error
case !Comparable(y.typ):
errOp = y
cause = check.incomparableCause(y.typ)
goto Error
} }
} else {
err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) case syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq:
} // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered."
switch {
if err != "" { case !allOrdered(x.typ):
// TODO(gri) better error message for cases where one can only compare against nil errOp = x
check.errorf(x, invalidOp+"cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) goto Error
x.mode = invalid case !allOrdered(y.typ):
return errOp = y
goto Error
}
default:
unreachable()
} }
// comparison is ok
if x.mode == constant_ && y.mode == constant_ { if x.mode == constant_ && y.mode == constant_ {
x.val = constant.MakeBool(constant.Compare(x.val, op2tok[op], y.val)) x.val = constant.MakeBool(constant.Compare(x.val, op2tok[op], y.val))
// The operands are never materialized; no need to update // The operands are never materialized; no need to update
@ -833,6 +863,74 @@ func (check *Checker) comparison(x, y *operand, op syntax.Operator) {
// spec: "Comparison operators compare two operands and yield // spec: "Comparison operators compare two operands and yield
// an untyped boolean value." // an untyped boolean value."
x.typ = Typ[UntypedBool] x.typ = Typ[UntypedBool]
return
Error:
// We have an offending operand errOp and possibly an error cause.
if cause == "" {
if isTypeParam(x.typ) || isTypeParam(y.typ) {
// TODO(gri) should report the specific type causing the problem, if any
if !isTypeParam(x.typ) {
errOp = y
}
cause = check.sprintf("type parameter %s is not comparable with %s", errOp.typ, op)
} else {
cause = check.sprintf("operator %s not defined on %s", op, check.kindString(errOp.typ)) // catch-all
}
}
// For switches, report errors on the first (case) operand.
// TODO(gri) adjust error message in that case
if switchCase {
errOp = x
}
if check.conf.CompilerErrorMessages {
check.errorf(errOp, invalidOp+"%s %s %s (%s)", x.expr, op, y.expr, cause)
} else {
check.errorf(errOp, invalidOp+"cannot compare %s %s %s (%s)", x.expr, op, y.expr, cause)
}
x.mode = invalid
}
// incomparableCause returns a more specific cause why typ is not comparable.
// If there is no more specific cause, the result is "".
func (check *Checker) incomparableCause(typ Type) string {
switch under(typ).(type) {
case *Slice, *Signature, *Map:
return check.kindString(typ) + " can only be compared to nil"
}
// see if we can extract a more specific error
var cause string
comparable(typ, nil, func(format string, args ...interface{}) {
cause = check.sprintf(format, args...)
})
return cause
}
// kindString returns the type kind as a string.
func (check *Checker) kindString(typ Type) string {
switch under(typ).(type) {
case *Array:
return "array"
case *Slice:
return "slice"
case *Struct:
return "struct"
case *Pointer:
return "pointer"
case *Signature:
return "func"
case *Interface:
if isTypeParam(typ) {
return check.sprintf("type parameter %s", typ)
}
return "interface"
case *Map:
return "map"
case *Chan:
return "chan"
default:
return check.sprintf("%s", typ) // catch-all
}
} }
// If e != nil, it must be the shift expression; it may be nil for non-constant shifts. // If e != nil, it must be the shift expression; it may be nil for non-constant shifts.
@ -1034,7 +1132,7 @@ func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op
} }
if isComparison(op) { if isComparison(op) {
check.comparison(x, &y, op) check.comparison(x, &y, op, false)
return return
} }

View File

@ -102,10 +102,11 @@ 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) return comparable(T, nil, nil)
} }
func comparable(T Type, seen map[Type]bool) bool { // 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 {
if seen[T] { if seen[T] {
return true return true
} }
@ -123,13 +124,22 @@ func comparable(T Type, seen map[Type]bool) bool {
return true return true
case *Struct: case *Struct:
for _, f := range t.fields { for _, f := range t.fields {
if !comparable(f.typ, seen) { if !comparable(f.typ, seen, nil) {
if reportf != nil {
reportf("struct containing %s cannot be compared", f.typ)
}
return false return false
} }
} }
return true return true
case *Array: case *Array:
return comparable(t.elem, seen) if !comparable(t.elem, seen, nil) {
if reportf != nil {
reportf("%s cannot be compared", t)
}
return false
}
return true
case *Interface: case *Interface:
return !isTypeParam(T) || t.typeSet().IsComparable(seen) return !isTypeParam(T) || t.typeSet().IsComparable(seen)
} }

View File

@ -239,7 +239,7 @@ L:
} }
// Order matters: By comparing v against x, error positions are at the case values. // Order matters: By comparing v against x, error positions are at the case values.
res := v // keep original v unchanged res := v // keep original v unchanged
check.comparison(&res, x, syntax.Eql) check.comparison(&res, x, syntax.Eql, true)
if res.mode == invalid { if res.mode == invalid {
continue L continue L
} }

View File

@ -9,8 +9,8 @@ package expr2
func _bool() { func _bool() {
const t = true == true const t = true == true
const f = true == false const f = true == false
_ = t /* ERROR "cannot compare" */ < f _ = t /* ERROR cannot compare */ < f
_ = 0 /* ERROR "mismatched types untyped int and untyped bool" */ == t _ = 0 /* ERROR mismatched types untyped int and untyped bool */ == t
var b bool var b bool
var x, y float32 var x, y float32
b = x < y b = x < y
@ -20,7 +20,7 @@ func _bool() {
// corner cases // corner cases
var ( var (
v0 = nil /* ERROR "cannot compare" */ == nil v0 = nil == nil // ERROR operator == not defined on untyped nil
) )
func arrays() { func arrays() {
@ -40,7 +40,7 @@ func arrays() {
_ = c /* ERROR mismatched types */ == d _ = c /* ERROR mismatched types */ == d
var e [10]func() int var e [10]func() int
_ = e /* ERROR == not defined */ == e _ = e /* ERROR \[10\]func\(\) int cannot be compared */ == e
} }
func structs() { func structs() {
@ -79,8 +79,8 @@ func structs() {
func pointers() { func pointers() {
// nil // nil
_ = nil /* ERROR == not defined */ == nil _ = nil == nil // ERROR operator == not defined on untyped nil
_ = nil /* ERROR != not defined */ != nil _ = nil != nil // ERROR operator != not defined on untyped nil
_ = nil /* ERROR < not defined */ < nil _ = nil /* ERROR < not defined */ < nil
_ = nil /* ERROR <= not defined */ <= nil _ = nil /* ERROR <= not defined */ <= nil
_ = nil /* ERROR > not defined */ > nil _ = nil /* ERROR > not defined */ > nil
@ -211,16 +211,16 @@ func interfaces() {
// issue #28164 // issue #28164
// testcase from issue // testcase from issue
_ = interface /* ERROR cannot compare */ {}(nil) == []int(nil) _ = interface{}(nil) == [ /* ERROR slice can only be compared to nil */ ]int(nil)
// related cases // related cases
var e interface{} var e interface{}
var s []int var s []int
var x int var x int
_ = e /* ERROR cannot compare */ == s _ = e == s // ERROR slice can only be compared to nil
_ = s /* ERROR cannot compare */ == e _ = s /* ERROR slice can only be compared to nil */ == e
_ = e /* ERROR cannot compare */ < x _ = e /* ERROR operator < not defined on interface */ < x
_ = x /* ERROR cannot compare */ < e _ = x < e // ERROR operator < not defined on interface
} }
func slices() { func slices() {
@ -231,7 +231,7 @@ func slices() {
_ = s /* ERROR < not defined */ < nil _ = s /* ERROR < not defined */ < nil
// slices are not otherwise comparable // slices are not otherwise comparable
_ = s /* ERROR == not defined */ == s _ = s /* ERROR slice can only be compared to nil */ == s
_ = s /* ERROR < not defined */ < s _ = s /* ERROR < not defined */ < s
} }
@ -243,7 +243,7 @@ func maps() {
_ = m /* ERROR < not defined */ < nil _ = m /* ERROR < not defined */ < nil
// maps are not otherwise comparable // maps are not otherwise comparable
_ = m /* ERROR == not defined */ == m _ = m /* ERROR map can only be compared to nil */ == m
_ = m /* ERROR < not defined */ < m _ = m /* ERROR < not defined */ < m
} }
@ -255,6 +255,6 @@ func funcs() {
_ = f /* ERROR < not defined */ < nil _ = f /* ERROR < not defined */ < nil
// funcs are not otherwise comparable // funcs are not otherwise comparable
_ = f /* ERROR == not defined */ == f _ = f /* ERROR func can only be compared to nil */ == f
_ = f /* ERROR < not defined */ < f _ = f /* ERROR < not defined */ < f
} }

View File

@ -10,7 +10,7 @@ func _[P comparable](x, y P) {
_ = y == x _ = y == x
_ = y == y _ = y == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }
func _[P comparable](x P, y any) { func _[P comparable](x P, y any) {
@ -19,23 +19,23 @@ func _[P comparable](x P, y any) {
_ = y == x _ = y == x
_ = y == y _ = y == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }
func _[P any](x, y P) { func _[P any](x, y P) {
_ = x /* ERROR P is not comparable */ == x _ = x /* ERROR type parameter P is not comparable with == */ == x
_ = x /* ERROR P is not comparable */ == y _ = x /* ERROR type parameter P is not comparable with == */ == y
_ = y /* ERROR P is not comparable */ == x _ = y /* ERROR type parameter P is not comparable with == */ == x
_ = y /* ERROR P is not comparable */ == y _ = y /* ERROR type parameter P is not comparable with == */ == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }
func _[P any](x P, y any) { func _[P any](x P, y any) {
_ = x /* ERROR P is not comparable */ == x _ = x /* ERROR type parameter P is not comparable with == */ == x
_ = x /* ERROR P is not comparable */ == y _ = x /* ERROR type parameter P is not comparable with == */ == y
_ = y /* ERROR P is not comparable */ == x _ = y == x // ERROR type parameter P is not comparable with ==
_ = y == y _ = y == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }

View File

@ -0,0 +1,21 @@
// 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
type thing1 struct {
things []string
}
type thing2 struct {
things []thing1
}
func _() {
var a1, b1 thing1
_ = a1 /* ERROR struct containing \[\]string cannot be compared */ == b1
var a2, b2 thing2
_ = a2 /* ERROR struct containing \[\]thing1 cannot be compared */ == b2
}

View File

@ -0,0 +1,120 @@
// 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 comparisons
type (
B int // basic type representative
A [10]func()
L []byte
S struct{ f []byte }
P *S
F func()
I interface{}
M map[string]int
C chan int
)
var (
b B
a A
l L
s S
p P
f F
i I
m M
c C
)
func _() {
_ = nil == nil // ERROR operator == not defined on untyped nil
_ = b == b
_ = a /* ERROR \[10\]func\(\) 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
_ = f /* ERROR func can only be compared to nil */ == f
_ = i == i
_ = m /* ERROR map can only be compared to nil */ == m
_ = c == c
_ = b /* ERROR mismatched types */ == nil
_ = a /* ERROR mismatched types */ == nil
_ = l == nil
_ = s /* ERROR mismatched types */ == nil
_ = p == nil
_ = f == nil
_ = i == nil
_ = m == nil
_ = c == nil
_ = nil /* ERROR operator < not defined on untyped nil */ < nil
_ = b < b
_ = a /* ERROR operator < not defined on array */ < a
_ = l /* ERROR operator < not defined on slice */ < l
_ = s /* ERROR operator < not defined on struct */ < s
_ = p /* ERROR operator < not defined on pointer */ < p
_ = f /* ERROR operator < not defined on func */ < f
_ = i /* ERROR operator < not defined on interface */ < i
_ = m /* ERROR operator < not defined on map */ < m
_ = c /* ERROR operator < not defined on chan */ < c
}
func _[
B int,
A [10]func(),
L []byte,
S struct{ f []byte },
P *S,
F func(),
I interface{},
J comparable,
M map[string]int,
C chan int,
] (
b B,
a A,
l L,
s S,
p P,
f F,
i I,
j J,
m M,
c C,
) {
_ = b == b
_ = a /* ERROR type parameter A is not comparable with == */ == a
_ = l /* ERROR type parameter L is not comparable with == */ == l
_ = s /* ERROR type parameter S is not comparable with == */ == s
_ = p == p
_ = f /* ERROR type parameter F is not comparable with == */ == f
_ = i /* ERROR type parameter I is not comparable with == */ == i
_ = j == j
_ = m /* ERROR type parameter M is not comparable with == */ == m
_ = c == c
_ = b /* ERROR mismatched types */ == nil
_ = a /* ERROR mismatched types */ == nil
_ = l == nil
_ = s /* ERROR mismatched types */ == nil
_ = p == nil
_ = f == nil
_ = i /* ERROR mismatched types */ == nil
_ = j /* ERROR mismatched types */ == nil
_ = m == nil
_ = c == nil
_ = b < b
_ = a /* ERROR type parameter A is not comparable with < */ < a
_ = l /* ERROR type parameter L is not comparable with < */ < l
_ = s /* ERROR type parameter S is not comparable with < */ < s
_ = p /* ERROR type parameter P is not comparable with < */ < p
_ = f /* ERROR type parameter F is not comparable with < */ < f
_ = i /* ERROR type parameter I is not comparable with < */ < i
_ = j /* ERROR type parameter J is not comparable with < */ < j
_ = m /* ERROR type parameter M is not comparable with < */ < m
_ = c /* ERROR type parameter C is not comparable with < */ < c
}

View File

@ -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) return t != nil && comparable(t.typ, seen, nil)
}) })
} }

View File

@ -728,54 +728,84 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
return target, nil, 0 return target, nil, 0
} }
func (check *Checker) comparison(x, y *operand, op token.Token) { // If switchCase is true, the operator op is ignored.
func (check *Checker) comparison(x, y *operand, op token.Token, switchCase bool) {
if switchCase {
op = token.EQL
}
errOp := x // operand for which error is reported, if any
cause := "" // specific error cause, if any
// spec: "In any comparison, the first operand must be assignable // spec: "In any comparison, the first operand must be assignable
// to the type of the second operand, or vice versa." // to the type of the second operand, or vice versa."
err := "" code := _MismatchedTypes
var code errorCode ok, _ := x.assignableTo(check, y.typ, nil)
xok, _ := x.assignableTo(check, y.typ, nil) if !ok {
yok, _ := y.assignableTo(check, x.typ, nil) ok, _ = y.assignableTo(check, x.typ, nil)
if xok || yok { }
equality := false if !ok {
defined := false // Report the error on the 2nd operand since we only
switch op { // know after seeing the 2nd operand whether we have
case token.EQL, token.NEQ: // a type mismatch.
// spec: "The equality operators == and != apply to operands that are comparable." errOp = y
equality = true // For now, if we're not running the compiler, use the
defined = Comparable(x.typ) && Comparable(y.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) // position of x to minimize changes to existing tests.
case token.LSS, token.LEQ, token.GTR, token.GEQ: if !compilerErrorMessages {
// spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." errOp = x
defined = allOrdered(x.typ) && allOrdered(y.typ)
default:
unreachable()
} }
if !defined { cause = check.sprintf("mismatched types %s and %s", x.typ, y.typ)
if equality && (isTypeParam(x.typ) || isTypeParam(y.typ)) { goto Error
typ := x.typ }
if isTypeParam(y.typ) {
typ = y.typ // check if comparison is defined for operands
} code = _UndefinedOp
err = check.sprintf("%s is not comparable", typ) switch op {
} else { case token.EQL, token.NEQ:
typ := x.typ // spec: "The equality operators == and != apply to operands that are comparable."
if x.isNil() { switch {
typ = y.typ case x.isNil() || y.isNil():
} // Comparison against nil requires that the other operand type has nil.
err = check.sprintf("operator %s not defined on %s", op, typ) typ := x.typ
if x.isNil() {
typ = y.typ
} }
code = _UndefinedOp if !hasNil(typ) {
// This case should only be possible for "nil == nil".
// Report the error on the 2nd operand since we only
// know after seeing the 2nd operand whether we have
// an invalid comparison.
errOp = y
goto Error
}
case !Comparable(x.typ):
errOp = x
cause = check.incomparableCause(x.typ)
goto Error
case !Comparable(y.typ):
errOp = y
cause = check.incomparableCause(y.typ)
goto Error
} }
} else {
err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) case token.LSS, token.LEQ, token.GTR, token.GEQ:
code = _MismatchedTypes // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered."
} switch {
case !allOrdered(x.typ):
if err != "" { errOp = x
check.errorf(x, code, "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) goto Error
x.mode = invalid case !allOrdered(y.typ):
return errOp = y
goto Error
}
default:
unreachable()
} }
// comparison is ok
if x.mode == constant_ && y.mode == constant_ { if x.mode == constant_ && y.mode == constant_ {
x.val = constant.MakeBool(constant.Compare(x.val, op, y.val)) x.val = constant.MakeBool(constant.Compare(x.val, op, y.val))
// The operands are never materialized; no need to update // The operands are never materialized; no need to update
@ -793,6 +823,74 @@ func (check *Checker) comparison(x, y *operand, op token.Token) {
// spec: "Comparison operators compare two operands and yield // spec: "Comparison operators compare two operands and yield
// an untyped boolean value." // an untyped boolean value."
x.typ = Typ[UntypedBool] x.typ = Typ[UntypedBool]
return
Error:
// We have an offending operand errOp and possibly an error cause.
if cause == "" {
if isTypeParam(x.typ) || isTypeParam(y.typ) {
// TODO(gri) should report the specific type causing the problem, if any
if !isTypeParam(x.typ) {
errOp = y
}
cause = check.sprintf("type parameter %s is not comparable with %s", errOp.typ, op)
} else {
cause = check.sprintf("operator %s not defined on %s", op, check.kindString(errOp.typ)) // catch-all
}
}
// For switches, report errors on the first (case) operand.
// TODO(gri) adjust error message in that case
if switchCase {
errOp = x
}
if compilerErrorMessages {
check.invalidOp(errOp, code, "%s %s %s (%s)", x.expr, op, y.expr, cause)
} else {
check.invalidOp(errOp, code, "cannot compare %s %s %s (%s)", x.expr, op, y.expr, cause)
}
x.mode = invalid
}
// incomparableCause returns a more specific cause why typ is not comparable.
// If there is no more specific cause, the result is "".
func (check *Checker) incomparableCause(typ Type) string {
switch under(typ).(type) {
case *Slice, *Signature, *Map:
return check.kindString(typ) + " can only be compared to nil"
}
// see if we can extract a more specific error
var cause string
comparable(typ, nil, func(format string, args ...interface{}) {
cause = check.sprintf(format, args...)
})
return cause
}
// kindString returns the type kind as a string.
func (check *Checker) kindString(typ Type) string {
switch under(typ).(type) {
case *Array:
return "array"
case *Slice:
return "slice"
case *Struct:
return "struct"
case *Pointer:
return "pointer"
case *Signature:
return "func"
case *Interface:
if isTypeParam(typ) {
return check.sprintf("type parameter %s", typ)
}
return "interface"
case *Map:
return "map"
case *Chan:
return "chan"
default:
return check.sprintf("%s", typ) // catch-all
}
} }
// If e != nil, it must be the shift expression; it may be nil for non-constant shifts. // If e != nil, it must be the shift expression; it may be nil for non-constant shifts.
@ -1014,7 +1112,7 @@ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token
} }
if isComparison(op) { if isComparison(op) {
check.comparison(x, &y, op) check.comparison(x, &y, op, false)
return return
} }

View File

@ -104,10 +104,11 @@ 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) return comparable(T, nil, nil)
} }
func comparable(T Type, seen map[Type]bool) bool { // 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 {
if seen[T] { if seen[T] {
return true return true
} }
@ -125,13 +126,22 @@ func comparable(T Type, seen map[Type]bool) bool {
return true return true
case *Struct: case *Struct:
for _, f := range t.fields { for _, f := range t.fields {
if !comparable(f.typ, seen) { if !comparable(f.typ, seen, nil) {
if reportf != nil {
reportf("struct containing %s cannot be compared", f.typ)
}
return false return false
} }
} }
return true return true
case *Array: case *Array:
return comparable(t.elem, seen) if !comparable(t.elem, seen, nil) {
if reportf != nil {
reportf("%s cannot be compared", t)
}
return false
}
return true
case *Interface: case *Interface:
return !isTypeParam(T) || t.typeSet().IsComparable(seen) return !isTypeParam(T) || t.typeSet().IsComparable(seen)
} }

View File

@ -248,7 +248,7 @@ L:
} }
// Order matters: By comparing v against x, error positions are at the case values. // Order matters: By comparing v against x, error positions are at the case values.
res := v // keep original v unchanged res := v // keep original v unchanged
check.comparison(&res, x, token.EQL) check.comparison(&res, x, token.EQL, true)
if res.mode == invalid { if res.mode == invalid {
continue L continue L
} }

View File

@ -9,8 +9,8 @@ package expr2
func _bool() { func _bool() {
const t = true == true const t = true == true
const f = true == false const f = true == false
_ = t /* ERROR "cannot compare" */ < f _ = t /* ERROR cannot compare */ < f
_ = 0 /* ERROR "mismatched types untyped int and untyped bool" */ == t _ = 0 /* ERROR mismatched types untyped int and untyped bool */ == t
var b bool var b bool
var x, y float32 var x, y float32
b = x < y b = x < y
@ -20,7 +20,7 @@ func _bool() {
// corner cases // corner cases
var ( var (
v0 = nil /* ERROR "cannot compare" */ == nil v0 = nil == nil // ERROR operator == not defined on untyped nil
) )
func arrays() { func arrays() {
@ -40,7 +40,7 @@ func arrays() {
_ = c /* ERROR mismatched types */ == d _ = c /* ERROR mismatched types */ == d
var e [10]func() int var e [10]func() int
_ = e /* ERROR == not defined */ == e _ = e /* ERROR \[10\]func\(\) int cannot be compared */ == e
} }
func structs() { func structs() {
@ -79,8 +79,8 @@ func structs() {
func pointers() { func pointers() {
// nil // nil
_ = nil /* ERROR == not defined */ == nil _ = nil == nil // ERROR operator == not defined on untyped nil
_ = nil /* ERROR != not defined */ != nil _ = nil != nil // ERROR operator != not defined on untyped nil
_ = nil /* ERROR < not defined */ < nil _ = nil /* ERROR < not defined */ < nil
_ = nil /* ERROR <= not defined */ <= nil _ = nil /* ERROR <= not defined */ <= nil
_ = nil /* ERROR > not defined */ > nil _ = nil /* ERROR > not defined */ > nil
@ -211,16 +211,16 @@ func interfaces() {
// issue #28164 // issue #28164
// testcase from issue // testcase from issue
_ = interface /* ERROR cannot compare */ {}(nil) == []int(nil) _ = interface{}(nil) == [ /* ERROR slice can only be compared to nil */ ]int(nil)
// related cases // related cases
var e interface{} var e interface{}
var s []int var s []int
var x int var x int
_ = e /* ERROR cannot compare */ == s _ = e == s // ERROR slice can only be compared to nil
_ = s /* ERROR cannot compare */ == e _ = s /* ERROR slice can only be compared to nil */ == e
_ = e /* ERROR cannot compare */ < x _ = e /* ERROR operator < not defined on interface */ < x
_ = x /* ERROR cannot compare */ < e _ = x < e // ERROR operator < not defined on interface
} }
func slices() { func slices() {
@ -231,7 +231,7 @@ func slices() {
_ = s /* ERROR < not defined */ < nil _ = s /* ERROR < not defined */ < nil
// slices are not otherwise comparable // slices are not otherwise comparable
_ = s /* ERROR == not defined */ == s _ = s /* ERROR slice can only be compared to nil */ == s
_ = s /* ERROR < not defined */ < s _ = s /* ERROR < not defined */ < s
} }
@ -243,7 +243,7 @@ func maps() {
_ = m /* ERROR < not defined */ < nil _ = m /* ERROR < not defined */ < nil
// maps are not otherwise comparable // maps are not otherwise comparable
_ = m /* ERROR == not defined */ == m _ = m /* ERROR map can only be compared to nil */ == m
_ = m /* ERROR < not defined */ < m _ = m /* ERROR < not defined */ < m
} }
@ -255,6 +255,6 @@ func funcs() {
_ = f /* ERROR < not defined */ < nil _ = f /* ERROR < not defined */ < nil
// funcs are not otherwise comparable // funcs are not otherwise comparable
_ = f /* ERROR == not defined */ == f _ = f /* ERROR func can only be compared to nil */ == f
_ = f /* ERROR < not defined */ < f _ = f /* ERROR < not defined */ < f
} }

View File

@ -10,7 +10,7 @@ func _[P comparable](x, y P) {
_ = y == x _ = y == x
_ = y == y _ = y == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }
func _[P comparable](x P, y any) { func _[P comparable](x P, y any) {
@ -19,23 +19,23 @@ func _[P comparable](x P, y any) {
_ = y == x _ = y == x
_ = y == y _ = y == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }
func _[P any](x, y P) { func _[P any](x, y P) {
_ = x /* ERROR P is not comparable */ == x _ = x /* ERROR type parameter P is not comparable with == */ == x
_ = x /* ERROR P is not comparable */ == y _ = x /* ERROR type parameter P is not comparable with == */ == y
_ = y /* ERROR P is not comparable */ == x _ = y /* ERROR type parameter P is not comparable with == */ == x
_ = y /* ERROR P is not comparable */ == y _ = y /* ERROR type parameter P is not comparable with == */ == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }
func _[P any](x P, y any) { func _[P any](x P, y any) {
_ = x /* ERROR P is not comparable */ == x _ = x /* ERROR type parameter P is not comparable with == */ == x
_ = x /* ERROR P is not comparable */ == y _ = x /* ERROR type parameter P is not comparable with == */ == y
_ = y /* ERROR P is not comparable */ == x _ = y == x // ERROR type parameter P is not comparable with ==
_ = y == y _ = y == y
_ = x /* ERROR operator < not defined on P */ < y _ = x /* ERROR type parameter P is not comparable with < */ < y
} }

View File

@ -0,0 +1,21 @@
// 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
type thing1 struct {
things []string
}
type thing2 struct {
things []thing1
}
func _() {
var a1, b1 thing1
_ = a1 /* ERROR struct containing \[\]string cannot be compared */ == b1
var a2, b2 thing2
_ = a2 /* ERROR struct containing \[\]thing1 cannot be compared */ == b2
}

View File

@ -0,0 +1,120 @@
// 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 comparisons
type (
B int // basic type representative
A [10]func()
L []byte
S struct{ f []byte }
P *S
F func()
I interface{}
M map[string]int
C chan int
)
var (
b B
a A
l L
s S
p P
f F
i I
m M
c C
)
func _() {
_ = nil == nil // ERROR operator == not defined on untyped nil
_ = b == b
_ = a /* ERROR \[10\]func\(\) 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
_ = f /* ERROR func can only be compared to nil */ == f
_ = i == i
_ = m /* ERROR map can only be compared to nil */ == m
_ = c == c
_ = b /* ERROR mismatched types */ == nil
_ = a /* ERROR mismatched types */ == nil
_ = l == nil
_ = s /* ERROR mismatched types */ == nil
_ = p == nil
_ = f == nil
_ = i == nil
_ = m == nil
_ = c == nil
_ = nil /* ERROR operator < not defined on untyped nil */ < nil
_ = b < b
_ = a /* ERROR operator < not defined on array */ < a
_ = l /* ERROR operator < not defined on slice */ < l
_ = s /* ERROR operator < not defined on struct */ < s
_ = p /* ERROR operator < not defined on pointer */ < p
_ = f /* ERROR operator < not defined on func */ < f
_ = i /* ERROR operator < not defined on interface */ < i
_ = m /* ERROR operator < not defined on map */ < m
_ = c /* ERROR operator < not defined on chan */ < c
}
func _[
B int,
A [10]func(),
L []byte,
S struct{ f []byte },
P *S,
F func(),
I interface{},
J comparable,
M map[string]int,
C chan int,
] (
b B,
a A,
l L,
s S,
p P,
f F,
i I,
j J,
m M,
c C,
) {
_ = b == b
_ = a /* ERROR type parameter A is not comparable with == */ == a
_ = l /* ERROR type parameter L is not comparable with == */ == l
_ = s /* ERROR type parameter S is not comparable with == */ == s
_ = p == p
_ = f /* ERROR type parameter F is not comparable with == */ == f
_ = i /* ERROR type parameter I is not comparable with == */ == i
_ = j == j
_ = m /* ERROR type parameter M is not comparable with == */ == m
_ = c == c
_ = b /* ERROR mismatched types */ == nil
_ = a /* ERROR mismatched types */ == nil
_ = l == nil
_ = s /* ERROR mismatched types */ == nil
_ = p == nil
_ = f == nil
_ = i /* ERROR mismatched types */ == nil
_ = j /* ERROR mismatched types */ == nil
_ = m == nil
_ = c == nil
_ = b < b
_ = a /* ERROR type parameter A is not comparable with < */ < a
_ = l /* ERROR type parameter L is not comparable with < */ < l
_ = s /* ERROR type parameter S is not comparable with < */ < s
_ = p /* ERROR type parameter P is not comparable with < */ < p
_ = f /* ERROR type parameter F is not comparable with < */ < f
_ = i /* ERROR type parameter I is not comparable with < */ < i
_ = j /* ERROR type parameter J is not comparable with < */ < j
_ = m /* ERROR type parameter M is not comparable with < */ < m
_ = c /* ERROR type parameter C is not comparable with < */ < c
}

View File

@ -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) return t != nil && comparable(t.typ, seen, nil)
}) })
} }

View File

@ -12,6 +12,6 @@ func f()
func s(x interface{}) { func s(x interface{}) {
switch x { switch x {
case f: // ERROR "invalid case f \(type func\(\)\) in switch \(incomparable type\)|cannot compare" case f: // ERROR "invalid case f \(type func\(\)\) in switch \(incomparable type\)|can only be compared to nil"
} }
} }