]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: add "dynamic" flag to comparable predicate
authorRobert Griesemer <gri@golang.org>
Sun, 20 Feb 2022 20:58:21 +0000 (12:58 -0800)
committerRobert Griesemer <gri@golang.org>
Wed, 23 Feb 2022 20:51:26 +0000 (20:51 +0000)
A type implements a comparable interface only if the type
is statically known to be comparable. Specifically, a type
cannot contain (component) interfaces that are not statically
known to be comparable.

This CL adds a flag "dynamic" to the comparable predicate to
control whether interfaces are always (dynamically) comparable.
Set the flag to true when testing for (traditional) Go comparability;
set the flag to false when testing whether a type implements the
comparable interface.

Fixes #51257.

Change-Id: If22bc047ee59337deb2e7844b8f488d67e5c5530
Reviewed-on: https://go-review.googlesource.com/c/go/+/387055
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/instantiate.go
src/cmd/compile/internal/types2/predicates.go
src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2 [new file with mode: 0644]
src/cmd/compile/internal/types2/typeset.go
src/go/types/expr.go
src/go/types/instantiate.go
src/go/types/predicates.go
src/go/types/testdata/fixedbugs/issue51257.go2 [new file with mode: 0644]
src/go/types/typeset.go

index 02ece21e675554cdc2cf1c63071bc17b8c1dce77..ac5630dbbb16d2b1fcfc63568c047e0f09bd3b87 100644 (file)
@@ -899,7 +899,7 @@ func (check *Checker) incomparableCause(typ Type) string {
        }
        // see if we can extract a more specific error
        var cause string
-       comparable(typ, nil, func(format string, args ...interface{}) {
+       comparable(typ, true, nil, func(format string, args ...interface{}) {
                cause = check.sprintf(format, args...)
        })
        return cause
index f54938b6e198ae8349cadc8b71f64bb997153633..c2653a383443269b96a66651da8b8204ccfd34e7 100644 (file)
@@ -204,7 +204,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() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
+       if Ti.IsComparable() && !comparable(V, false, nil, nil) {
                pending = errorf("%s does not implement comparable", V)
        }
 
index 0e46333af7f76106f87146be28d7a340334910b3..ba259341f6ae125725b92ea9a7ca753a86de060e 100644 (file)
@@ -102,11 +102,12 @@ func isGeneric(t Type) bool {
 
 // Comparable reports whether values of type T are comparable.
 func Comparable(T Type) bool {
-       return comparable(T, nil, nil)
+       return comparable(T, true, nil, nil)
 }
 
+// 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 comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool {
+func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
        if seen[T] {
                return true
        }
@@ -124,7 +125,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
                return true
        case *Struct:
                for _, f := range t.fields {
-                       if !comparable(f.typ, seen, nil) {
+                       if !comparable(f.typ, dynamic, seen, nil) {
                                if reportf != nil {
                                        reportf("struct containing %s cannot be compared", f.typ)
                                }
@@ -133,7 +134,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
                }
                return true
        case *Array:
-               if !comparable(t.elem, seen, nil) {
+               if !comparable(t.elem, dynamic, seen, nil) {
                        if reportf != nil {
                                reportf("%s cannot be compared", t)
                        }
@@ -141,7 +142,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
                }
                return true
        case *Interface:
-               return !isTypeParam(T) || t.typeSet().IsComparable(seen)
+               return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
        }
        return false
 }
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue51257.go2
new file mode 100644 (file)
index 0000000..bc4208e
--- /dev/null
@@ -0,0 +1,46 @@
+// 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
+
+func f[_ comparable]() {}
+
+type S1 struct{ x int }
+type S2 struct{ x any }
+type S3 struct{ x [10]interface{ m() } }
+
+func _[P1 comparable, P2 S2]() {
+       _ = f[S1]
+       _ = f[S2 /* ERROR S2 does not implement comparable */ ]
+       _ = f[S3 /* ERROR S3 does not implement comparable */ ]
+
+       type L1 struct { x P1 }
+       type L2 struct { x P2 }
+       _ = f[L1]
+       _ = f[L2 /* ERROR L2 does not implement comparable */ ]
+}
+
+
+// example from issue
+
+type Set[T comparable] map[T]struct{}
+
+func NewSetFromSlice[T comparable](items []T) *Set[T] {
+       s := Set[T]{}
+
+       for _, item := range items {
+               s[item] = struct{}{}
+       }
+
+       return &s
+}
+
+type T struct{ x any }
+
+func main() {
+       NewSetFromSlice( /* ERROR T does not implement comparable */ []T{
+               {"foo"},
+               {5},
+       })
+}
index fff348bcf4361bcc8a32a6c4ab095514c2a45a82..2c3e826a3fff05167a7867fbe86a03c34748cb05 100644 (file)
@@ -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, nil)
+               return t != nil && comparable(t.typ, false, seen, nil)
        })
 }
 
index 8747838c4bd9247cdce06e92c50746f79b3ee1d0..e8038dd178c6c0ddf070ce3153213197fea3d889 100644 (file)
@@ -859,7 +859,7 @@ func (check *Checker) incomparableCause(typ Type) string {
        }
        // see if we can extract a more specific error
        var cause string
-       comparable(typ, nil, func(format string, args ...interface{}) {
+       comparable(typ, true, nil, func(format string, args ...interface{}) {
                cause = check.sprintf(format, args...)
        })
        return cause
index 4aeaeb7f1169ca91d5424718fed63a810329e268..4b8e3d46612312a59e95ed1f15db06a9a8f9473e 100644 (file)
@@ -204,7 +204,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() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
+       if Ti.IsComparable() && !comparable(V, false, nil, nil) {
                pending = errorf("%s does not implement comparable", V)
        }
 
index 14e99bf42640bb87bfec099e5b7007f4cf767e22..0360f27ee60dc10d2e63c6717afd793eeccd01e8 100644 (file)
@@ -104,11 +104,12 @@ func isGeneric(t Type) bool {
 
 // Comparable reports whether values of type T are comparable.
 func Comparable(T Type) bool {
-       return comparable(T, nil, nil)
+       return comparable(T, true, nil, nil)
 }
 
+// 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 comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool {
+func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
        if seen[T] {
                return true
        }
@@ -126,7 +127,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
                return true
        case *Struct:
                for _, f := range t.fields {
-                       if !comparable(f.typ, seen, nil) {
+                       if !comparable(f.typ, dynamic, seen, nil) {
                                if reportf != nil {
                                        reportf("struct containing %s cannot be compared", f.typ)
                                }
@@ -135,7 +136,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
                }
                return true
        case *Array:
-               if !comparable(t.elem, seen, nil) {
+               if !comparable(t.elem, dynamic, seen, nil) {
                        if reportf != nil {
                                reportf("%s cannot be compared", t)
                        }
@@ -143,7 +144,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
                }
                return true
        case *Interface:
-               return !isTypeParam(T) || t.typeSet().IsComparable(seen)
+               return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
        }
        return false
 }
diff --git a/src/go/types/testdata/fixedbugs/issue51257.go2 b/src/go/types/testdata/fixedbugs/issue51257.go2
new file mode 100644 (file)
index 0000000..8a3eb32
--- /dev/null
@@ -0,0 +1,46 @@
+// 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
+
+func f[_ comparable]() {}
+
+type S1 struct{ x int }
+type S2 struct{ x any }
+type S3 struct{ x [10]interface{ m() } }
+
+func _[P1 comparable, P2 S2]() {
+       _ = f[S1]
+       _ = f[S2 /* ERROR S2 does not implement comparable */ ]
+       _ = f[S3 /* ERROR S3 does not implement comparable */ ]
+
+       type L1 struct { x P1 }
+       type L2 struct { x P2 }
+       _ = f[L1]
+       _ = f[L2 /* ERROR L2 does not implement comparable */ ]
+}
+
+
+// example from issue
+
+type Set[T comparable] map[T]struct{}
+
+func NewSetFromSlice[T comparable](items []T) *Set[T] {
+       s := Set[T]{}
+
+       for _, item := range items {
+               s[item] = struct{}{}
+       }
+
+       return &s
+}
+
+type T struct{ x any }
+
+func main() {
+       NewSetFromSlice /* ERROR T does not implement comparable */ ([]T{
+               {"foo"},
+               {5},
+       })
+}
index e1f73015b93a738a2e5e0e538d471619fb6baa87..3bc94746606539ba8edd64cc71db3725ee138b37 100644 (file)
@@ -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, nil)
+               return t != nil && comparable(t.typ, false, seen, nil)
        })
 }