mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
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:
parent
f9763a648b
commit
e052044d6b
@ -770,52 +770,82 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
|
||||
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
|
||||
// to the type of the second operand, or vice versa."
|
||||
err := ""
|
||||
xok, _ := x.assignableTo(check, y.typ, nil)
|
||||
yok, _ := y.assignableTo(check, x.typ, nil)
|
||||
if xok || yok {
|
||||
equality := false
|
||||
defined := false
|
||||
switch op {
|
||||
case syntax.Eql, syntax.Neq:
|
||||
// spec: "The equality operators == and != apply to operands that are comparable."
|
||||
equality = true
|
||||
defined = Comparable(x.typ) && Comparable(y.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ)
|
||||
case syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq:
|
||||
// spec: The ordering operators <, <=, >, and >= apply to operands that are ordered."
|
||||
defined = allOrdered(x.typ) && allOrdered(y.typ)
|
||||
default:
|
||||
unreachable()
|
||||
ok, _ := x.assignableTo(check, y.typ, nil)
|
||||
if !ok {
|
||||
ok, _ = y.assignableTo(check, x.typ, nil)
|
||||
}
|
||||
if !ok {
|
||||
// Report the error on the 2nd operand since we only
|
||||
// know after seeing the 2nd operand whether we have
|
||||
// a type mismatch.
|
||||
errOp = y
|
||||
// For now, if we're not running the compiler, use the
|
||||
// position of x to minimize changes to existing tests.
|
||||
if !check.conf.CompilerErrorMessages {
|
||||
errOp = x
|
||||
}
|
||||
if !defined {
|
||||
if equality && (isTypeParam(x.typ) || isTypeParam(y.typ)) {
|
||||
typ := x.typ
|
||||
if isTypeParam(y.typ) {
|
||||
typ = y.typ
|
||||
}
|
||||
err = check.sprintf("%s is not comparable", typ)
|
||||
} else {
|
||||
typ := x.typ
|
||||
if x.isNil() {
|
||||
typ = y.typ
|
||||
}
|
||||
err = check.sprintf("operator %s not defined on %s", op, typ)
|
||||
cause = check.sprintf("mismatched types %s and %s", x.typ, y.typ)
|
||||
goto Error
|
||||
}
|
||||
|
||||
// check if comparison is defined for operands
|
||||
switch op {
|
||||
case syntax.Eql, syntax.Neq:
|
||||
// spec: "The equality operators == and != apply to operands that are comparable."
|
||||
switch {
|
||||
case x.isNil() || y.isNil():
|
||||
// Comparison against nil requires that the other operand type has nil.
|
||||
typ := x.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)
|
||||
}
|
||||
|
||||
if err != "" {
|
||||
// TODO(gri) better error message for cases where one can only compare against nil
|
||||
check.errorf(x, invalidOp+"cannot compare %s %s %s (%s)", x.expr, op, y.expr, err)
|
||||
x.mode = invalid
|
||||
return
|
||||
|
||||
case syntax.Lss, syntax.Leq, syntax.Gtr, syntax.Geq:
|
||||
// spec: The ordering operators <, <=, >, and >= apply to operands that are ordered."
|
||||
switch {
|
||||
case !allOrdered(x.typ):
|
||||
errOp = x
|
||||
goto Error
|
||||
case !allOrdered(y.typ):
|
||||
errOp = y
|
||||
goto Error
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// comparison is ok
|
||||
if x.mode == constant_ && y.mode == constant_ {
|
||||
x.val = constant.MakeBool(constant.Compare(x.val, op2tok[op], y.val))
|
||||
// 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
|
||||
// an untyped boolean value."
|
||||
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.
|
||||
@ -1034,7 +1132,7 @@ func (check *Checker) binary(x *operand, e syntax.Expr, lhs, rhs syntax.Expr, op
|
||||
}
|
||||
|
||||
if isComparison(op) {
|
||||
check.comparison(x, &y, op)
|
||||
check.comparison(x, &y, op, false)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -102,10 +102,11 @@ func isGeneric(t Type) bool {
|
||||
|
||||
// Comparable reports whether values of type T are comparable.
|
||||
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] {
|
||||
return true
|
||||
}
|
||||
@ -123,13 +124,22 @@ func comparable(T Type, seen map[Type]bool) bool {
|
||||
return true
|
||||
case *Struct:
|
||||
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 true
|
||||
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:
|
||||
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ L:
|
||||
}
|
||||
// Order matters: By comparing v against x, error positions are at the case values.
|
||||
res := v // keep original v unchanged
|
||||
check.comparison(&res, x, syntax.Eql)
|
||||
check.comparison(&res, x, syntax.Eql, true)
|
||||
if res.mode == invalid {
|
||||
continue L
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ package expr2
|
||||
func _bool() {
|
||||
const t = true == true
|
||||
const f = true == false
|
||||
_ = t /* ERROR "cannot compare" */ < f
|
||||
_ = 0 /* ERROR "mismatched types untyped int and untyped bool" */ == t
|
||||
_ = t /* ERROR cannot compare */ < f
|
||||
_ = 0 /* ERROR mismatched types untyped int and untyped bool */ == t
|
||||
var b bool
|
||||
var x, y float32
|
||||
b = x < y
|
||||
@ -20,7 +20,7 @@ func _bool() {
|
||||
|
||||
// corner cases
|
||||
var (
|
||||
v0 = nil /* ERROR "cannot compare" */ == nil
|
||||
v0 = nil == nil // ERROR operator == not defined on untyped nil
|
||||
)
|
||||
|
||||
func arrays() {
|
||||
@ -40,7 +40,7 @@ func arrays() {
|
||||
_ = c /* ERROR mismatched types */ == d
|
||||
|
||||
var e [10]func() int
|
||||
_ = e /* ERROR == not defined */ == e
|
||||
_ = e /* ERROR \[10\]func\(\) int cannot be compared */ == e
|
||||
}
|
||||
|
||||
func structs() {
|
||||
@ -79,8 +79,8 @@ func structs() {
|
||||
|
||||
func pointers() {
|
||||
// nil
|
||||
_ = nil /* ERROR == not defined */ == nil
|
||||
_ = nil /* ERROR != not defined */ != nil
|
||||
_ = nil == nil // ERROR operator == not defined on untyped 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
|
||||
@ -211,16 +211,16 @@ func interfaces() {
|
||||
|
||||
// issue #28164
|
||||
// testcase from issue
|
||||
_ = interface /* ERROR cannot compare */ {}(nil) == []int(nil)
|
||||
_ = interface{}(nil) == [ /* ERROR slice can only be compared to nil */ ]int(nil)
|
||||
|
||||
// related cases
|
||||
var e interface{}
|
||||
var s []int
|
||||
var x int
|
||||
_ = e /* ERROR cannot compare */ == s
|
||||
_ = s /* ERROR cannot compare */ == e
|
||||
_ = e /* ERROR cannot compare */ < x
|
||||
_ = x /* ERROR cannot compare */ < e
|
||||
_ = e == s // ERROR slice can only be compared to nil
|
||||
_ = s /* ERROR slice can only be compared to nil */ == e
|
||||
_ = e /* ERROR operator < not defined on interface */ < x
|
||||
_ = x < e // ERROR operator < not defined on interface
|
||||
}
|
||||
|
||||
func slices() {
|
||||
@ -231,7 +231,7 @@ func slices() {
|
||||
_ = s /* ERROR < not defined */ < nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ func maps() {
|
||||
_ = m /* ERROR < not defined */ < nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -255,6 +255,6 @@ func funcs() {
|
||||
_ = f /* ERROR < not defined */ < nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ func _[P comparable](x, y P) {
|
||||
_ = y == x
|
||||
_ = 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) {
|
||||
@ -19,23 +19,23 @@ func _[P comparable](x P, y any) {
|
||||
_ = y == x
|
||||
_ = 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) {
|
||||
_ = x /* ERROR P is not comparable */ == x
|
||||
_ = x /* ERROR P is not comparable */ == y
|
||||
_ = y /* ERROR P is not comparable */ == x
|
||||
_ = y /* ERROR P is not comparable */ == y
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == x
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == y
|
||||
_ = y /* ERROR type parameter P is not comparable with == */ == x
|
||||
_ = 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) {
|
||||
_ = x /* ERROR P is not comparable */ == x
|
||||
_ = x /* ERROR P is not comparable */ == y
|
||||
_ = y /* ERROR P is not comparable */ == x
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == x
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == y
|
||||
_ = y == x // ERROR type parameter P is not comparable with ==
|
||||
_ = y == y
|
||||
|
||||
_ = x /* ERROR operator < not defined on P */ < y
|
||||
_ = x /* ERROR type parameter P is not comparable with < */ < y
|
||||
}
|
||||
|
21
src/cmd/compile/internal/types2/testdata/fixedbugs/issue50918.go
vendored
Normal file
21
src/cmd/compile/internal/types2/testdata/fixedbugs/issue50918.go
vendored
Normal 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
|
||||
}
|
120
src/cmd/compile/internal/types2/testdata/spec/comparisons.go2
vendored
Normal file
120
src/cmd/compile/internal/types2/testdata/spec/comparisons.go2
vendored
Normal 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
|
||||
}
|
@ -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)
|
||||
return t != nil && comparable(t.typ, seen, nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -728,54 +728,84 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
|
||||
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
|
||||
// to the type of the second operand, or vice versa."
|
||||
err := ""
|
||||
var code errorCode
|
||||
xok, _ := x.assignableTo(check, y.typ, nil)
|
||||
yok, _ := y.assignableTo(check, x.typ, nil)
|
||||
if xok || yok {
|
||||
equality := false
|
||||
defined := false
|
||||
switch op {
|
||||
case token.EQL, token.NEQ:
|
||||
// spec: "The equality operators == and != apply to operands that are comparable."
|
||||
equality = true
|
||||
defined = Comparable(x.typ) && Comparable(y.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ)
|
||||
case token.LSS, token.LEQ, token.GTR, token.GEQ:
|
||||
// spec: The ordering operators <, <=, >, and >= apply to operands that are ordered."
|
||||
defined = allOrdered(x.typ) && allOrdered(y.typ)
|
||||
default:
|
||||
unreachable()
|
||||
code := _MismatchedTypes
|
||||
ok, _ := x.assignableTo(check, y.typ, nil)
|
||||
if !ok {
|
||||
ok, _ = y.assignableTo(check, x.typ, nil)
|
||||
}
|
||||
if !ok {
|
||||
// Report the error on the 2nd operand since we only
|
||||
// know after seeing the 2nd operand whether we have
|
||||
// a type mismatch.
|
||||
errOp = y
|
||||
// For now, if we're not running the compiler, use the
|
||||
// position of x to minimize changes to existing tests.
|
||||
if !compilerErrorMessages {
|
||||
errOp = x
|
||||
}
|
||||
if !defined {
|
||||
if equality && (isTypeParam(x.typ) || isTypeParam(y.typ)) {
|
||||
typ := x.typ
|
||||
if isTypeParam(y.typ) {
|
||||
typ = y.typ
|
||||
}
|
||||
err = check.sprintf("%s is not comparable", typ)
|
||||
} else {
|
||||
typ := x.typ
|
||||
if x.isNil() {
|
||||
typ = y.typ
|
||||
}
|
||||
err = check.sprintf("operator %s not defined on %s", op, typ)
|
||||
cause = check.sprintf("mismatched types %s and %s", x.typ, y.typ)
|
||||
goto Error
|
||||
}
|
||||
|
||||
// check if comparison is defined for operands
|
||||
code = _UndefinedOp
|
||||
switch op {
|
||||
case token.EQL, token.NEQ:
|
||||
// spec: "The equality operators == and != apply to operands that are comparable."
|
||||
switch {
|
||||
case x.isNil() || y.isNil():
|
||||
// Comparison against nil requires that the other operand type has nil.
|
||||
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)
|
||||
code = _MismatchedTypes
|
||||
}
|
||||
|
||||
if err != "" {
|
||||
check.errorf(x, code, "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err)
|
||||
x.mode = invalid
|
||||
return
|
||||
|
||||
case token.LSS, token.LEQ, token.GTR, token.GEQ:
|
||||
// spec: The ordering operators <, <=, >, and >= apply to operands that are ordered."
|
||||
switch {
|
||||
case !allOrdered(x.typ):
|
||||
errOp = x
|
||||
goto Error
|
||||
case !allOrdered(y.typ):
|
||||
errOp = y
|
||||
goto Error
|
||||
}
|
||||
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// comparison is ok
|
||||
if x.mode == constant_ && y.mode == constant_ {
|
||||
x.val = constant.MakeBool(constant.Compare(x.val, op, y.val))
|
||||
// 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
|
||||
// an untyped boolean value."
|
||||
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.
|
||||
@ -1014,7 +1112,7 @@ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token
|
||||
}
|
||||
|
||||
if isComparison(op) {
|
||||
check.comparison(x, &y, op)
|
||||
check.comparison(x, &y, op, false)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -104,10 +104,11 @@ func isGeneric(t Type) bool {
|
||||
|
||||
// Comparable reports whether values of type T are comparable.
|
||||
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] {
|
||||
return true
|
||||
}
|
||||
@ -125,13 +126,22 @@ func comparable(T Type, seen map[Type]bool) bool {
|
||||
return true
|
||||
case *Struct:
|
||||
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 true
|
||||
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:
|
||||
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
|
||||
}
|
||||
|
@ -248,7 +248,7 @@ L:
|
||||
}
|
||||
// Order matters: By comparing v against x, error positions are at the case values.
|
||||
res := v // keep original v unchanged
|
||||
check.comparison(&res, x, token.EQL)
|
||||
check.comparison(&res, x, token.EQL, true)
|
||||
if res.mode == invalid {
|
||||
continue L
|
||||
}
|
||||
|
28
src/go/types/testdata/check/expr2.src
vendored
28
src/go/types/testdata/check/expr2.src
vendored
@ -9,8 +9,8 @@ package expr2
|
||||
func _bool() {
|
||||
const t = true == true
|
||||
const f = true == false
|
||||
_ = t /* ERROR "cannot compare" */ < f
|
||||
_ = 0 /* ERROR "mismatched types untyped int and untyped bool" */ == t
|
||||
_ = t /* ERROR cannot compare */ < f
|
||||
_ = 0 /* ERROR mismatched types untyped int and untyped bool */ == t
|
||||
var b bool
|
||||
var x, y float32
|
||||
b = x < y
|
||||
@ -20,7 +20,7 @@ func _bool() {
|
||||
|
||||
// corner cases
|
||||
var (
|
||||
v0 = nil /* ERROR "cannot compare" */ == nil
|
||||
v0 = nil == nil // ERROR operator == not defined on untyped nil
|
||||
)
|
||||
|
||||
func arrays() {
|
||||
@ -40,7 +40,7 @@ func arrays() {
|
||||
_ = c /* ERROR mismatched types */ == d
|
||||
|
||||
var e [10]func() int
|
||||
_ = e /* ERROR == not defined */ == e
|
||||
_ = e /* ERROR \[10\]func\(\) int cannot be compared */ == e
|
||||
}
|
||||
|
||||
func structs() {
|
||||
@ -79,8 +79,8 @@ func structs() {
|
||||
|
||||
func pointers() {
|
||||
// nil
|
||||
_ = nil /* ERROR == not defined */ == nil
|
||||
_ = nil /* ERROR != not defined */ != nil
|
||||
_ = nil == nil // ERROR operator == not defined on untyped 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
|
||||
@ -211,16 +211,16 @@ func interfaces() {
|
||||
|
||||
// issue #28164
|
||||
// testcase from issue
|
||||
_ = interface /* ERROR cannot compare */ {}(nil) == []int(nil)
|
||||
_ = interface{}(nil) == [ /* ERROR slice can only be compared to nil */ ]int(nil)
|
||||
|
||||
// related cases
|
||||
var e interface{}
|
||||
var s []int
|
||||
var x int
|
||||
_ = e /* ERROR cannot compare */ == s
|
||||
_ = s /* ERROR cannot compare */ == e
|
||||
_ = e /* ERROR cannot compare */ < x
|
||||
_ = x /* ERROR cannot compare */ < e
|
||||
_ = e == s // ERROR slice can only be compared to nil
|
||||
_ = s /* ERROR slice can only be compared to nil */ == e
|
||||
_ = e /* ERROR operator < not defined on interface */ < x
|
||||
_ = x < e // ERROR operator < not defined on interface
|
||||
}
|
||||
|
||||
func slices() {
|
||||
@ -231,7 +231,7 @@ func slices() {
|
||||
_ = s /* ERROR < not defined */ < nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -243,7 +243,7 @@ func maps() {
|
||||
_ = m /* ERROR < not defined */ < nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -255,6 +255,6 @@ func funcs() {
|
||||
_ = f /* ERROR < not defined */ < nil
|
||||
|
||||
// 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
|
||||
}
|
||||
|
22
src/go/types/testdata/fixedbugs/issue48712.go2
vendored
22
src/go/types/testdata/fixedbugs/issue48712.go2
vendored
@ -10,7 +10,7 @@ func _[P comparable](x, y P) {
|
||||
_ = y == x
|
||||
_ = 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) {
|
||||
@ -19,23 +19,23 @@ func _[P comparable](x P, y any) {
|
||||
_ = y == x
|
||||
_ = 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) {
|
||||
_ = x /* ERROR P is not comparable */ == x
|
||||
_ = x /* ERROR P is not comparable */ == y
|
||||
_ = y /* ERROR P is not comparable */ == x
|
||||
_ = y /* ERROR P is not comparable */ == y
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == x
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == y
|
||||
_ = y /* ERROR type parameter P is not comparable with == */ == x
|
||||
_ = 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) {
|
||||
_ = x /* ERROR P is not comparable */ == x
|
||||
_ = x /* ERROR P is not comparable */ == y
|
||||
_ = y /* ERROR P is not comparable */ == x
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == x
|
||||
_ = x /* ERROR type parameter P is not comparable with == */ == y
|
||||
_ = y == x // ERROR type parameter P is not comparable with ==
|
||||
_ = y == y
|
||||
|
||||
_ = x /* ERROR operator < not defined on P */ < y
|
||||
_ = x /* ERROR type parameter P is not comparable with < */ < y
|
||||
}
|
||||
|
21
src/go/types/testdata/fixedbugs/issue50918.go
vendored
Normal file
21
src/go/types/testdata/fixedbugs/issue50918.go
vendored
Normal 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
|
||||
}
|
120
src/go/types/testdata/spec/comparisons.go2
vendored
Normal file
120
src/go/types/testdata/spec/comparisons.go2
vendored
Normal 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
|
||||
}
|
@ -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)
|
||||
return t != nil && comparable(t.typ, seen, nil)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,6 @@ func f()
|
||||
|
||||
func s(x interface{}) {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user