You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: document/explain which interfaces implement comparable #50646
var (
anyType=types.Universe.Lookup("any").Type()
comparableType=types.Universe.Lookup("comparable").Type()
)
fmt.Println(types.AssignableTo(comparableType, anyType))
fmt.Println(types.AssignableTo(anyType, comparableType))
What did you expect to see?
true
false
That is, I (naively?) expected the same check that prevents me using non-comparable types as map keys to tell me that I cannot assign a value of an unconstrained type to a variable of a comparable type.
A little more context: I am updating github.com/bobg/modver to be generics-aware. This package can compare two versions of a Go module and tell whether a major, minor, or patchlevel version-number bump is needed, according to semver rules.
If two versions of a Go object differ only in the constraint on a type parameter, what should the result be? The answer depends on the type set implied by the constraint.
If the two type sets are identical, the result should be None. If the older type set is a strict subset of the newer type set, that is a backward-compatible change and the result should be Minor. Otherwise it's an incompatible change and the result should be Major.
I had hoped to use types.Identical and types.AssignableTo to distinguish these cases, but if I can't, what's the best way to do it instead? Must I write my own type-set-comparing logic, or is there something in the stdlib I can use?
In the current language no variable can have the type comparable, so at least at present it doesn't necessarily make sense to call AssignableTo. On the other hand, Implements does make sense, but it also returns true for both directions.
The question you want to ask seems like a reasonable one but I don't know whether the go/types package supports answering that question yet.
changed the title [-]go/types: AssignableTo does not distinguish between any and comparable[/-][+]go/types, types2: AssignableTo does not distinguish between any and comparable[/+]on Jan 17, 2022
When we check whether a (a value of) type V can be assigned to a (variable of) interface type T (types.AssignableTo(V, T)), because T is an interface, we need to check if V implements T. In this specific case we need to check if the empty interface (any) implements comparable. The comparable interface is implemented by any type that supports == and !=. (Even) in non-generic Go, == and != can be used with a variable of interface type (even the empty interface!), it's just that we might get a run-time panic if the dynamic type doesn't support comparison with == and !=. Thus, any interface (including any) supports == and !=, which leads to the (admittedly counter-intuitive at first) conclusion that any actually implements comparable and thus any is assignable to comparable.
If comparable is used as (or embedded in a constraint), it is therefore ok to instantiate the respective type parameter with an arbitrary interface (that satisfies all the other aspects of the constraint), because comparison is always supported by all interfaces. However, when running that instantiated code, we may get a run-time panic if the dynamic type of the interface does not support comparison (as we do in regular Go).
In short, this is not a bug but simply a consequence of existing rules.
Leaving this issue open for now so we can close it with a CL that adds some more tests.
changed the title [-]go/types, types2: AssignableTo does not distinguish between any and comparable[/-][+]go/types, types2: document that all interfaces implement `comparable`[/+]on Jan 24, 2022
changed the title [-]go/types, types2: document that all interfaces implement `comparable`[/-][+]spec: document that all interfaces implement `comparable`[/+]on Jan 25, 2022
@griesemer "... This is something we cannot change."
Why not? We can now still introduce a new notation like =! for this case that was not legal Go pre 1.18 while all previously working Go programs would still compile and 1.18+ programs will still remain fully backward compatible.
@tinytina2021 I'm not sure I follow: we cannot change the behavior of == and != for interfaces as that would invalidate existing programs. But we can have different rules for type parameters, which is what we're doing. I'm not sure how introducing =! (and what about the negation of =!?) would help or why it would be useful.
@griesemer Just to flag, the change so that any no longer counts as comparable makes it difficult to write generic code in some quite common situations
It kinda seems like this change has been made a little last-minute and without particularly widespread discussion, but may have a fairly significant impact on people's ability to use generics in Go
For example, when using golang-set, it's pretty reasonable that you might want to make a set containing any comparable type - e.g.:
The code cannot be written as NewSetFromSlice[comparable]([]comparable{ - this results in the error interface is (or embeds) comparable compiler(ErrorCode(142))
So as far as I can see, it is no longer possible to write this code at all - though maybe I'm missing something obvious?
If you're firm that any will never implement comparable, maybe comparable should be allowed in non-type constraint interfaces?
Honestly, this seems significant enough to be a release blocker, although it probably could be fixed later without a breaking change
Edit: opened as a new issue - #51257 - as I appreciate this one has been closed for a couple of weeks now
Activity
bobg commentedon Jan 16, 2022
A little more context: I am updating github.com/bobg/modver to be generics-aware. This package can compare two versions of a Go module and tell whether a major, minor, or patchlevel version-number bump is needed, according to semver rules.
If two versions of a Go object differ only in the constraint on a type parameter, what should the result be? The answer depends on the type set implied by the constraint.
If the two type sets are identical, the result should be
None. If the older type set is a strict subset of the newer type set, that is a backward-compatible change and the result should beMinor. Otherwise it's an incompatible change and the result should beMajor.I had hoped to use
types.Identicalandtypes.AssignableToto distinguish these cases, but if I can't, what's the best way to do it instead? Must I write my own type-set-comparing logic, or is there something in the stdlib I can use?Thanks...
ianlancetaylor commentedon Jan 16, 2022
CC @griesemer @findleyr
In the current language no variable can have the type
comparable, so at least at present it doesn't necessarily make sense to callAssignableTo. On the other hand,Implementsdoes make sense, but it also returnstruefor both directions.The question you want to ask seems like a reasonable one but I don't know whether the go/types package supports answering that question yet.
griesemer commentedon Jan 17, 2022
This is simply a bug and needs to be fixed.Thanks for the report.[-]go/types: AssignableTo does not distinguish between any and comparable[/-][+]go/types, types2: AssignableTo does not distinguish between any and comparable[/+]griesemer commentedon Jan 24, 2022
When we check whether a (a value of) type
Vcan be assigned to a (variable of) interface typeT(types.AssignableTo(V, T)), becauseTis an interface, we need to check ifVimplementsT. In this specific case we need to check if the empty interface (any) implementscomparable. Thecomparableinterface is implemented by any type that supports==and!=. (Even) in non-generic Go,==and!=can be used with a variable of interface type (even the empty interface!), it's just that we might get a run-time panic if the dynamic type doesn't support comparison with==and!=. Thus, any interface (includingany) supports==and!=, which leads to the (admittedly counter-intuitive at first) conclusion thatanyactually implementscomparableand thusanyis assignable tocomparable.If
comparableis used as (or embedded in a constraint), it is therefore ok to instantiate the respective type parameter with an arbitrary interface (that satisfies all the other aspects of the constraint), because comparison is always supported by all interfaces. However, when running that instantiated code, we may get a run-time panic if the dynamic type of the interface does not support comparison (as we do in regular Go).In short, this is not a bug but simply a consequence of existing rules.
Leaving this issue open for now so we can close it with a CL that adds some more tests.
[-]go/types, types2: AssignableTo does not distinguish between any and comparable[/-][+]go/types, types2: document that all interfaces implement `comparable`[/+]gopherbot commentedon Jan 25, 2022
Change https://golang.org/cl/380654 mentions this issue:
cmd/compile: all interfaces implement comparable (add tests)[-]go/types, types2: document that all interfaces implement `comparable`[/-][+]spec: document that all interfaces implement `comparable`[/+]35 remaining items
go/types, types2: fix implements and identical predicates
griesemer commentedon Jan 31, 2022
This is now fixed in the type checker. What is outstanding is the documentation in the spec. Not a release blocker anymore.
tinytina2021 commentedon Feb 1, 2022
@griesemer "... This is something we cannot change."
Why not? We can now still introduce a new notation like =! for this case that was not legal Go pre 1.18 while all previously working Go programs would still compile and 1.18+ programs will still remain fully backward compatible.
spec: add section on comparable constraint
griesemer commentedon Feb 1, 2022
@tinytina2021 I'm not sure I follow: we cannot change the behavior of
==and!=for interfaces as that would invalidate existing programs. But we can have different rules for type parameters, which is what we're doing. I'm not sure how introducing=!(and what about the negation of=!?) would help or why it would be useful.griesemer commentedon Feb 1, 2022
The implementation of
AssignableTohas been fixed and https://golang.org/cl/381896 documents the behavior orcomparable. Closing.billinghamj commentedon Feb 18, 2022
@griesemer Just to flag, the change so that
anyno longer counts ascomparablemakes it difficult to write generic code in some quite common situationsIt kinda seems like this change has been made a little last-minute and without particularly widespread discussion, but may have a fairly significant impact on people's ability to use generics in Go
For example, when using golang-set, it's pretty reasonable that you might want to make a set containing any comparable type - e.g.:
The code cannot be written as
NewSetFromSlice[comparable]([]comparable{- this results in the errorinterface is (or embeds) comparable compiler(ErrorCode(142))So as far as I can see, it is no longer possible to write this code at all - though maybe I'm missing something obvious?
If you're firm that
anywill never implementcomparable, maybecomparableshould be allowed in non-type constraint interfaces?Honestly, this seems significant enough to be a release blocker, although it probably could be fixed later without a breaking change
Edit: opened as a new issue - #51257 - as I appreciate this one has been closed for a couple of weeks now
griesemer commentedon Feb 23, 2022
I think the correct answer here is to allow
comparableas a regular type. See #51257 for more.gopherbot commentedon Apr 23, 2022
Change https://go.dev/cl/401874 mentions this issue:
spec: clarify type set and interface are differentgo/types, types2: all interfaces implement comparable (add tests)