-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
spec: when unifying against interface types, consider common methods #60353
Comments
Change https://go.dev/cl/497015 mentions this issue: |
Change https://go.dev/cl/497119 mentions this issue: |
I think you mean
This implies that two non-empty interfaces with no methods in common will unify. Is that useful? We won't be able to assign a value of one interface type to the other interface type. At least at the top level, this means that a function call can't work. Should the rule instead be that one interface must have a subset of the methods of the other, and the common methods must unify? Or are there cases where requiring a subset will cause inference to fail unreasonably? |
Agreed that requiring a subset would be better. The proposed rule is simpler but may produce false positives (that then lead to an error later). The subset approach doesn't cause any problems with existing code and tests. The proposal and implementation have been updated accordingly. |
…faces When unifying two types A and B where one or both of them are interfaces, consider the shared method signatures in unification. 1) If a defined interface (an interface with a type name) is unified with another (defined) interface, currently they must originate in the same type declaration (same origin) for unification to succeed. This is more restrictive than necessary for assignments: when interfaces are assigned to each other, corresponding methods must match, but the interfaces don't have to be identical. In unification, we don't know which direction the assignment is happening (or if we have an assignment in the first place), but in any case one interface must implement the other. Thus, we check that one interface has a subset of the methods of the other and that corresponding method signatures unify. The assignment or instantiation may still not be possible but that will be checked when instantiation and parameter passing is checked. If two interfaces are compared as part of another type during unification, the types must be equal. If they are not, unifying a method subset may still succeed (and possibly produce more type arguments), but that is ok: again, subsequent instantiation and assignment will fail if the types are indeed not identical. 2) In a non-interface type is unified with an interface, currently unification fails. If this unification is a consequence of an assignment (parameter passing), this is again too restrictive: the non-interface type must only implement the interface (possibly among other type set requirements). In any case, all methods of the interface type must be present in the non-interface type and unify with the corresponding interface methods. If they don't, unification will fail either way. If they do, we may infer additional type arguments. Again, the resulting types may still not be correct but that will be determined by the instantiation and parameter passing or assignment checks. If the non-interface type and the interface type appear as component of another type, unification may now produce additional type arguments. But that is again ok because the respective types won't pass instantiation or assignment checks since they are different types. This CL introduces a new Config flag, EnableInterfaceInference, to enable this new behavior. If not set, unification remains unchanged. To be able to test the flag durign unification, a *Checker is passed and stored with the unifier. For #60353. Fixes #41176. Fixes #57192. Change-Id: I6b167a9afa378d0682e9b101d9d86f5777308af7 Reviewed-on: https://go-review.googlesource.com/c/go/+/497015 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@google.com> Run-TryBot: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Auto-Submit: Robert Griesemer <gri@google.com>
Change https://go.dev/cl/497656 mentions this issue: |
…faces When unifying two types A and B where one or both of them are interfaces, consider the shared method signatures in unification. 1) If a defined interface (an interface with a type name) is unified with another (defined) interface, currently they must originate in the same type declaration (same origin) for unification to succeed. This is more restrictive than necessary for assignments: when interfaces are assigned to each other, corresponding methods must match, but the interfaces don't have to be identical. In unification, we don't know which direction the assignment is happening (or if we have an assignment in the first place), but in any case one interface must implement the other. Thus, we check that one interface has a subset of the methods of the other and that corresponding method signatures unify. The assignment or instantiation may still not be possible but that will be checked when instantiation and parameter passing is checked. If two interfaces are compared as part of another type during unification, the types must be equal. If they are not, unifying a method subset may still succeed (and possibly produce more type arguments), but that is ok: again, subsequent instantiation and assignment will fail if the types are indeed not identical. 2) In a non-interface type is unified with an interface, currently unification fails. If this unification is a consequence of an assignment (parameter passing), this is again too restrictive: the non-interface type must only implement the interface (possibly among other type set requirements). In any case, all methods of the interface type must be present in the non-interface type and unify with the corresponding interface methods. If they don't, unification will fail either way. If they do, we may infer additional type arguments. Again, the resulting types may still not be correct but that will be determined by the instantiation and parameter passing or assignment checks. If the non-interface type and the interface type appear as component of another type, unification may now produce additional type arguments. But that is again ok because the respective types won't pass instantiation or assignment checks since they are different types. This CL introduces a new unifier flag, enableInterfaceInference, to enable this new behavior. It is currently disabled. For #60353. For #41176. For #57192. Change-Id: I983d0ad5f043c7fe9d377dbb95f6b9342f36f45f Reviewed-on: https://go-review.googlesource.com/c/go/+/497656 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Run-TryBot: Robert Griesemer <gri@google.com> Auto-Submit: Robert Griesemer <gri@google.com>
This proposal has been added to the active column of the proposals project |
Change https://go.dev/cl/499282 mentions this issue: |
Based on the discussion above, this proposal seems like a likely accept. |
For #39661. For #41176. For #51593. For #52397. For #57192. For #58645. For #58650. For #58671. For #59338. For #59750. For #60353. Change-Id: Ib731c9f2879beb541f44cb10e40c36a8677d3ad4 Reviewed-on: https://go-review.googlesource.com/c/go/+/499282 TryBot-Bypass: Robert Griesemer <gri@google.com> Reviewed-by: Ian Lance Taylor <iant@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
No change in consensus, so accepted. 🎉 |
This has been implemented and documented in the spec for Go 1.21. |
Background
Currently, type inference fails when trying to unify two interfaces of different type, or an interface and a non-interface type, even though the respective types may have the correct methods and type sets if they could be unified somehow.
Example 1 (see #57192)
In this case,
I2
implementsI1
if the type parametersT
ofI1
andI2
could be inferred to beint
. Currently (Go 1.20) the compiler reports an error in this case.Example 2 (see #41176):
In this case,
S
implementsI
if the type parameterT
ofI
could be inferred to bebyte
. Currently (Go 1.20) the compiler reports an error in this case.The problem in the first case is that unification requires matching interfaces to be identical or from the same type declaration. The problem in the second case is that unification fails if the two types being compared (interface vs struct) are not of the same kind.
This does not accurately reflect Go's assignment rules for interfaces.
Proposal
We propose to change the type inference rules such that when interfaces are involved, type unification considers Go's assignment rules for interfaces. Specifically:
I
and a non-interface typeT
unify if all the methods ofI
exist inT
and unify.When unifying two defined types that are both interfaces originating in the same type declaration, use the current unification approach (type parameters must unify); i.e. there's no change in this case.
This is the entire proposal.
Discussion
The proposed changes are fully backward-compatible: the only case to consider is the unification of two interfaces (unifying an interface against a non-interface always failed in the past). In Go 1.20, two unnamed interfaces unify only if they have identical type terms, the same number of methods, and all methods unify. The proposed new rule will succeed in this case as well.
We don't change the existing behavior when unifying two defined types that are both interfaces originating in the same type declaration: first of all, the methods will unify if the type arguments unify (it's the same interface), but we also want to preserve existing behavior when type parameters are not used:
In this case we want to infer the type argument for
P
to beint
string
(as we do now). If we were only considering methods, this code would not work anymore. (That said, arguably this is a bug, see #60377: if we accept this as a bug then we don't need to separate between named/unnamed interface types for unification.)Currently, if we try to unify two different interfaces, unification fails. The proposed new rule effectively means that one interface must implement the other. This may not be sufficient (e.g. if the interfaces are component types of other types) but if the two interfaces are compatible at all, it is a necessary condition.
Similarly, when unifying a non-interface type
T
with an interfaceI
, at the very leastT
must implement all the methods ofI
; i.e. they must exist and they must unify. There are additional restrictions with respect to the type set but it's ok to ignore them for now (see next paragraph).The proposed changes allow us to infer additional type arguments where unification (and thus type inference) currently fails immediately. It is still possible that the inferred type arguments lead to invalid instantiations and invalid parameter passing/assignments, in which case we will fail later.
Implementation
We have implemented this proposal (CL 497015) and enabled it through CL 497657.
Should this proposal not be accepted or should we run into problems during the freeze, the implementation can be safely disabled by issuing a revert of CL 497657.
cc: @ianlancetaylor @findleyr for visibility
The text was updated successfully, but these errors were encountered: