-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd/compile: type constraint containing any used directly as type #68710
Comments
any
to bypass go's generic checking?
cc @griesemer |
Simple case: https://go.dev/play/p/t8l45ebTndw
It seems that |
I think that |
@mateusz834 Good point, thanks. With a single interface this is clearly an embedded interface. Then the remaining question is with the original example, where |
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. 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{}{}
} |
IIUC, this is the right behavior because:
is a Basic Interface. While:
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 That's the type identity part.
|
I'll note that any (or rather, empty interfaces) is the only interface this works with. IMO this is pretty clearly a bug.
I don't think the spec is all that clear about whether it is actually a basic interface. It says
Now, on the one hand, the type set of I just can't imagine anyone thinking it is intended for |
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.
So the union might not be simplifiable? |
|
@cuonglm I understand what you mean. But in terms of sets, the union is decomposable in disjoint sets. Otherwise, that would mean that we could consider the type set of the union term {[]uint8} to be empty/uninhabited since all types satisfy [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: The decomposition in disjoint sets should be preferable. |
One question. So this type AnyType interface {
interface{ /* nothing */ } | []uint8
} Or should be recognized as type AnyType interface {
interface { /* all types */ } | []uint8
} Should this 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. |
I believe this is working as intended, even if the behavior is admittedly surprising:
Existing implementation restrictions based on the current implementation (which represents type sets via lists of types and lists of methods) may lead to the appearance that Note also that the spec says:
This restriction is not based on the presence or absence of union terms, it is only based on the whether an interface is basic or not. And basic interfaces are (per spec):
Therefore, Closing as working as intended. |
PS: @Merovius I'd be interested to hear what you think the correct behavior should be. Presumably you want a syntactic distinction? I suppose one could add a marker to interfaces when they include a union of sorts and then say that those are not basic interfaces. Not sure what kind of ripple effects that might have. It would also not be backward compatible, but perhaps that's such a rare situation (and maybe a bug, as you alluded to), that it would be ok to make the change. The spec's grouping of the section on interfaces into three parts is essentially for didactic purposes. I think we could simply define general interfaces (and perhaps basic interfaces as interfaces that are representable as method sets only) and be done with it. But when we introduced generics, after having used basic interfaces for > 10 years, I was looking for a gradual path to explain the expansion of the interface definition, and connect the general interfaces with what we had in the past. The fact that we have what seems like three different kinds of interfaces is not "real" in the implementation. There's just one, and it's based on the notion of a type set. The implementation of these type sets is crude, which is why we have implementation restrictions. As you know better than I, representing type sets in full generality is complicated. Feel free to re-open if you think the existing behavior is a mistake and you have good arguments. |
@griesemer then why type a interface{ io.Reader | *os.File }
func main() {
var b a
_ = b
} Leads to a compile error? |
@mateusz834
Without the implementation restriction (if the compiler were more sophisticated in its type set computation) something like this might work - though you'd have to use two interfaces, not an |
I think @mateusz834 reasoning might also be that since *os.File implements io.Reader, the constraint should be equivalent to io.Reader similarly to what happens in this issue. I would be happier if this was reopened. (If we compute Card of the Union of two overlapping typesets, we end up with 1 == 0 otherwise.) |
@griesemer I don't have any real, firm arguments, except of a vague dissatisfaction with using non-constructive definitions in the spec. A lexical rule like "does contain a union-element node" is easy to understand and it is extremely clear how to implement it. A semantic rule like "can the type set be expressed as…" is "more squishy" (I don't have good words for what I mean) and it requires some reasoning to understand and implement. I'm not even 100% clear on how to interpret its implications for constraints with empty type sets, for example. I don't think there really are problems, to be clear, but I don't like that I feel I'd need to think to form a real opinion. From what I can tell, the only case where there is a difference between a lexical rule and the rule-as-written is there is a union element containing an empty interface. And in that case, the interface is equivalent to the union just not being there. So, using a purely lexical rule seems simpler while not costing anything in expressive power. "What would happen instead" is that the compiler would reject using any interface (potentially recursively) containing a union element as a value, so people would either not do it, or delete the meaningless union. On the other hand, you are of course right that changing it now would be backwards-incompatible. And the argument could be spun the other way around: The only complexity in understanding/implementing the rule is to throw out unions containing empty interfaces, which isn't that bad. At least if my understanding is correct. So yeah, I won't argue that changing anything is necessarily worth it either. I just like the lexical interpretation better and wish that's how it had gotten implemented in the first place. |
Thanks, @Merovius, very sensible feedback, as always. |
I don't get what are the rules here. Do you mean the reason of why In my opinion, |
@mateusz834 FWIW if you are interested in the reasoning for rejecting |
Proposal Details
some issue content about this:
golang-jwt/jwt#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?
The text was updated successfully, but these errors were encountered: