Open godcong opened 1 month ago
cc @griesemer
Simple case: https://go.dev/play/p/t8l45ebTndw
package main
import (
"fmt"
)
type Foo interface {
any
}
func main() {
var f Foo = 42
fmt.Println(f)
}
It seems that interface{ any }
is treated the same as interface{}
. I am also unsure whether this is intended or a bug.
I think that interface{ any }
being the same as interface{}
makes sense, same as interface{io.Reader}
is the same as io.Reader
.
@mateusz834 Good point, thanks. With a single interface this is clearly an embedded interface.
Then the remaining question is with the original example, where any | []uint8
is effectively "simplified" to any
, which then allows embedding.
I think we might want a different definition of type identity for basic interface as opposed to union ones.
If we ever end up with union implemented as interfaces this will simplify discrimination.
In that sense, the union here should not be usable as a type yet, just like any union we have nowadays. (because crypto.PublicKey is a defined interface type and the union with the slice type creates a full-blown 'union' interface type.
That should be relevant when embedding union interface types. The "union" part remains part of the type identity.
Probably a bug. (but something to think over)
This might make #57644 unintuitive.
Consider:
type Foo interface {
any | int
}
type Bar interface {
Foo | string
}
func main() {
var _ Bar = struct{}{}
}
Then the remaining question is with the original example, where any | []uint8 is effectively "simplified" to any, which then allows embedding.
IIUC, this is the right behavior because:
type AnyType interface {
any | []uint8
}
is a Basic Interface. While:
type AnyType interface {
[]uint8
}
is not, since its type sets can not be defined entirely by a list of methods.
To add, the clearer semantics would be that Bar accepts either interface values or string values. It's definitely weird to say that, given our current definition of type set and since interfaces have a dynamic type which is never an interface.
But, if a
is a regular type, it implements itself. Where it's unclear nowadays is whether a
implements a | b because a | b would be an interface and that would come with implementing operations that only interface types have (assertions, comparisons : a
might not be comparable etc)
In theory it does, so we would have to think about it.
That's the type identity part.
So I guess we might need to wrap union typed values before assigning them. (no need, the issue is simply about the definition of unions and disjointness, having to explicitly mention the intersection as a separate case)
I'll note that any (or rather, empty interfaces) is the only interface this works with. IMO this is pretty clearly a bug.
IIUC, this is the right behavior because: […] is a Basic Interface.
I don't think the spec is all that clear about whether it is actually a basic interface. It says
In its most basic form an interface specifies a (possibly empty) list of methods. The type set defined by such an interface is the set of types which implement all of those methods, and the corresponding method set consists exactly of the methods specified by the interface. Interfaces whose type sets can be defined entirely by a list of methods are called basic interfaces.
Now, on the one hand, the type set of any | []uint8
is "all types", which can be defined by the empty "list of methods", so sure, strictly speaking it fulfills the definition. But ISTM the intent here is a syntactical one, i.e. "if it is an interface which neither embeds another interface nor contains any other type elements, except listing methods". The section headings of "Basic interfaces", "Embedded interfaces" and "General interfaces" also make that clear.
I just can't imagine anyone thinking it is intended for any | []uint8
to be interpreted the way it is.
type AnyType interface {
any | []uint8
}
is equivalent to and automatically simplified by compiler as
type AnyType interface {
any
}
So, it is a basic interface type. Both spec and implementation have no problems here.
@zigo101 that's arguable. The interface is defined as a set of methods that is empty AND a union of terms.
any | uint8
is equivalent to either any other type that is not uint8
OR uint8
only OR uint8
and any
(which is still uint8 in terms of constraints).
So the union might not be simplifiable? In that case it would not be a basic interface.
@zigo101 that's arguable. The interface is defined as a set of methods that is empty AND a union of terms.
any | uint8
is equivalent to either any other type that is notuint8
ORuint8
only ORuint8
andany
(which is still uint8 in terms of constraints).So the union might not be simplifiable? In that case it would not be a basic interface.
any
denotes all non-interface type, including uint8. So any | <anything> | ...
is just any
.
@cuonglm I understand what you mean. But in terms of sets, the union is decomposable in disjoint sets. That might be the definition that we need when/if interface types are involved.
Otherwise, that would mean that we could consider the type set of the union term {[]uint8} to be empty/uninhabited since all types satisfy any
. (to be understood as it not bringing anything to the union, being a redundant term).
That would make terms of a union non commutable.
[edit] It's perhaps easier to see that it is not true if we replace the interface by a union where each term is a distinct element of the type set: we'd get after simplification: {any\{[]uint8}} ∪ {[]uint8}
The decomposition in disjoint sets should be preferable.
type AnyType interface {
any | []uint8
}
One question.
Because of the use of |
, it is possible to identify the behavior as a type definition.
At this point, should any
be a union?
So this any
, then, should be recognized as an
type AnyType interface {
interface{ /* nothing */ } | []uint8
}
Or should be recognized as
type AnyType interface {
interface { /* all types */ } | []uint8
}
Should this any
not be recognized as an interface type.
If you want to add an interface type, I think it should be in the following format:
type AnyType interface {
interface{ /* types all or nothing? */ } | []uint8
any // interface type
}
Just since I don't think this has been stated yet, this is not new behavior -- the behavior described by OP appears to be present in Go 1.21+.
It has been present since Go 1.18.
Proposal Details
some issue content about this: https://github.com/golang-jwt/jwt/issues/401
The defined generic type
AnyType
can be used directly as a type and is compiled.If you delete
any
, you will be prompted withI haven't found anything about it, so I don't know if it's intentional or not.
If so, could you provide some relevant information?