Skip to content

Commit 163da6f

Browse files
committed
go/types, types2: add "dynamic" flag to comparable predicate
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 <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent e534907 commit 163da6f

File tree

10 files changed

+110
-16
lines changed

10 files changed

+110
-16
lines changed

src/cmd/compile/internal/types2/expr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ func (check *Checker) incomparableCause(typ Type) string {
899899
}
900900
// see if we can extract a more specific error
901901
var cause string
902-
comparable(typ, nil, func(format string, args ...interface{}) {
902+
comparable(typ, true, nil, func(format string, args ...interface{}) {
903903
cause = check.sprintf(format, args...)
904904
})
905905
return cause

src/cmd/compile/internal/types2/instantiate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error {
204204
// If T is comparable, V must be comparable.
205205
// Remember as a pending error and report only if we don't have a more specific error.
206206
var pending error
207-
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
207+
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
208208
pending = errorf("%s does not implement comparable", V)
209209
}
210210

src/cmd/compile/internal/types2/predicates.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,12 @@ func isGeneric(t Type) bool {
102102

103103
// Comparable reports whether values of type T are comparable.
104104
func Comparable(T Type) bool {
105-
return comparable(T, nil, nil)
105+
return comparable(T, true, nil, nil)
106106
}
107107

108+
// If dynamic is set, non-type parameter interfaces are always comparable.
108109
// If reportf != nil, it may be used to report why T is not comparable.
109-
func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool {
110+
func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
110111
if seen[T] {
111112
return true
112113
}
@@ -124,7 +125,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
124125
return true
125126
case *Struct:
126127
for _, f := range t.fields {
127-
if !comparable(f.typ, seen, nil) {
128+
if !comparable(f.typ, dynamic, seen, nil) {
128129
if reportf != nil {
129130
reportf("struct containing %s cannot be compared", f.typ)
130131
}
@@ -133,15 +134,15 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
133134
}
134135
return true
135136
case *Array:
136-
if !comparable(t.elem, seen, nil) {
137+
if !comparable(t.elem, dynamic, seen, nil) {
137138
if reportf != nil {
138139
reportf("%s cannot be compared", t)
139140
}
140141
return false
141142
}
142143
return true
143144
case *Interface:
144-
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
145+
return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
145146
}
146147
return false
147148
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
func f[_ comparable]() {}
8+
9+
type S1 struct{ x int }
10+
type S2 struct{ x any }
11+
type S3 struct{ x [10]interface{ m() } }
12+
13+
func _[P1 comparable, P2 S2]() {
14+
_ = f[S1]
15+
_ = f[S2 /* ERROR S2 does not implement comparable */ ]
16+
_ = f[S3 /* ERROR S3 does not implement comparable */ ]
17+
18+
type L1 struct { x P1 }
19+
type L2 struct { x P2 }
20+
_ = f[L1]
21+
_ = f[L2 /* ERROR L2 does not implement comparable */ ]
22+
}
23+
24+
25+
// example from issue
26+
27+
type Set[T comparable] map[T]struct{}
28+
29+
func NewSetFromSlice[T comparable](items []T) *Set[T] {
30+
s := Set[T]{}
31+
32+
for _, item := range items {
33+
s[item] = struct{}{}
34+
}
35+
36+
return &s
37+
}
38+
39+
type T struct{ x any }
40+
41+
func main() {
42+
NewSetFromSlice( /* ERROR T does not implement comparable */ []T{
43+
{"foo"},
44+
{5},
45+
})
46+
}

src/cmd/compile/internal/types2/typeset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
3939
return s.comparable
4040
}
4141
return s.is(func(t *term) bool {
42-
return t != nil && comparable(t.typ, seen, nil)
42+
return t != nil && comparable(t.typ, false, seen, nil)
4343
})
4444
}
4545

src/go/types/expr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ func (check *Checker) incomparableCause(typ Type) string {
859859
}
860860
// see if we can extract a more specific error
861861
var cause string
862-
comparable(typ, nil, func(format string, args ...interface{}) {
862+
comparable(typ, true, nil, func(format string, args ...interface{}) {
863863
cause = check.sprintf(format, args...)
864864
})
865865
return cause

src/go/types/instantiate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func (check *Checker) implements(V, T Type) error {
204204
// If T is comparable, V must be comparable.
205205
// Remember as a pending error and report only if we don't have a more specific error.
206206
var pending error
207-
if Ti.IsComparable() && ((Vi != nil && !Vi.IsComparable()) || (Vi == nil && !Comparable(V))) {
207+
if Ti.IsComparable() && !comparable(V, false, nil, nil) {
208208
pending = errorf("%s does not implement comparable", V)
209209
}
210210

src/go/types/predicates.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ func isGeneric(t Type) bool {
104104

105105
// Comparable reports whether values of type T are comparable.
106106
func Comparable(T Type) bool {
107-
return comparable(T, nil, nil)
107+
return comparable(T, true, nil, nil)
108108
}
109109

110+
// If dynamic is set, non-type parameter interfaces are always comparable.
110111
// If reportf != nil, it may be used to report why T is not comparable.
111-
func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})) bool {
112+
func comparable(T Type, dynamic bool, seen map[Type]bool, reportf func(string, ...interface{})) bool {
112113
if seen[T] {
113114
return true
114115
}
@@ -126,7 +127,7 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
126127
return true
127128
case *Struct:
128129
for _, f := range t.fields {
129-
if !comparable(f.typ, seen, nil) {
130+
if !comparable(f.typ, dynamic, seen, nil) {
130131
if reportf != nil {
131132
reportf("struct containing %s cannot be compared", f.typ)
132133
}
@@ -135,15 +136,15 @@ func comparable(T Type, seen map[Type]bool, reportf func(string, ...interface{})
135136
}
136137
return true
137138
case *Array:
138-
if !comparable(t.elem, seen, nil) {
139+
if !comparable(t.elem, dynamic, seen, nil) {
139140
if reportf != nil {
140141
reportf("%s cannot be compared", t)
141142
}
142143
return false
143144
}
144145
return true
145146
case *Interface:
146-
return !isTypeParam(T) || t.typeSet().IsComparable(seen)
147+
return dynamic && !isTypeParam(T) || t.typeSet().IsComparable(seen)
147148
}
148149
return false
149150
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
func f[_ comparable]() {}
8+
9+
type S1 struct{ x int }
10+
type S2 struct{ x any }
11+
type S3 struct{ x [10]interface{ m() } }
12+
13+
func _[P1 comparable, P2 S2]() {
14+
_ = f[S1]
15+
_ = f[S2 /* ERROR S2 does not implement comparable */ ]
16+
_ = f[S3 /* ERROR S3 does not implement comparable */ ]
17+
18+
type L1 struct { x P1 }
19+
type L2 struct { x P2 }
20+
_ = f[L1]
21+
_ = f[L2 /* ERROR L2 does not implement comparable */ ]
22+
}
23+
24+
25+
// example from issue
26+
27+
type Set[T comparable] map[T]struct{}
28+
29+
func NewSetFromSlice[T comparable](items []T) *Set[T] {
30+
s := Set[T]{}
31+
32+
for _, item := range items {
33+
s[item] = struct{}{}
34+
}
35+
36+
return &s
37+
}
38+
39+
type T struct{ x any }
40+
41+
func main() {
42+
NewSetFromSlice /* ERROR T does not implement comparable */ ([]T{
43+
{"foo"},
44+
{5},
45+
})
46+
}

src/go/types/typeset.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (s *_TypeSet) IsComparable(seen map[Type]bool) bool {
3737
return s.comparable
3838
}
3939
return s.is(func(t *term) bool {
40-
return t != nil && comparable(t.typ, seen, nil)
40+
return t != nil && comparable(t.typ, false, seen, nil)
4141
})
4242
}
4343

0 commit comments

Comments
 (0)