-
Notifications
You must be signed in to change notification settings - Fork 18k
cmd/compile: unexpected type inference with interface types #60933
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
Comments
Analysis: In the first case ( Now, because Not yet sure what the right "fix" here is. |
Do we want an error in this case, such as:
? |
I think this may be more of an issue with The example in #60204 (comment) seems almost as confusing even when the types are explicit: var outputFile *os.File
{
output := Or[*os.File](outputFile, os.Stdout)
fmt.Println(output == outputFile, output == os.Stdout) // false true
}
{
output := Or[io.Writer](outputFile, io.Discard) // oops
fmt.Println(output == outputFile, output == io.Discard) // true false
} Perhaps the right solution is a |
We probably should never infer an interface type unless all types that need to unify are the same (possibly named) interface. In all other cases, inferring an interface type amounts to finding a "super type" implemented by other, non-interface types; or finding the most general interface type among a set of interface types (which may lead to inferring an interface type that doesn't exist in the program, or to If we follow this approach, we will get an error for the problematic |
That's strange. I have some questions. Is *os.File an unnamed type because it's a pointer? In the original issue, I think that the type inference is somewhat correct in inferring io.Writer (modulo what to do when an interface is inferred since many of them could be so I'd think that inference would fail to determine an exact type [edit] @griesemer is addressing that point already just above) but I'm a bit puzzled by the result of the equality between output and outputFile. These are not variables of the same type, I must be missing or forgetting something but does equality works on variables of different types? Maybe if these questions can help. |
The func IsFirstZero[T any](v1, v2 T) bool {
var zero T
return v1 == zero
}
func main() {
fmt.Println(IsFirstZero((*os.File)(nil), os.Stdout)) // true
fmt.Println(IsFirstZero((*os.File)(nil), rand.Reader) // false
} Thanks to the inference to an interface type, the first is no longer a zero value when it was before. This can happen without any explicit typing, making it extremely confusing and hard to track down. At the very least I think it should be an error to infer an interface type from multiple sources where at least one is not an interface type. Then you'd have to do an explicit conversion, such as |
Does it really solve the problem? It seems like you would actually want to be able to say “pick the first non-nil reader” and even if you make type inference manual, it’s still not correct to pass a nil OS file here |
I feel like if you have |
In a quick test, this does the inference if a and b are |
I'd lean towards making it an error to implicitly convert from a concrete type to an inferred interface type. That would make That seems like the minimal restriction to protect users from the mistake of accidentally converting zero-values of concrete type into non-zero-values of interface type, due to type inference. |
@griesemer As I understand your previous message, I think making that an error is conservative and okay for 1.21. But I'd argue that I think it's okay to infer io.Writer as the type argument, because io.ReadWriter is still an interface type. So the implicit conversion from assigning the io.ReadWriter argument to the io.Writer parameter type can't turn a zero value into a non-zero value. The error-prone zero-into-non-zero conversion only happens when turning a value of concrete type into an interface type. -- Incidentally, I notice that Is that intentional/known? I thought inference was supposed to be independent of argument order. Perhaps related to #60946. |
@carlmjohnson In your comment, note that |
I'm not sure if observed behavior is actually a bug. If I do something like this I would expect the result to be The main problem I see is that if I change some of the arguments to the different types (by adding or removing methods) it will start returning different types, which is not ideal on big codebases. But still - I would prefer |
The observed behavior is clearly a bug. If we use a |
Change https://go.dev/cl/505396 mentions this issue: |
Proposed solution:
The actual change is very localized and small. This should address this issue and also #60946, and is backward-compatible with 1.20. |
Will this https://go.dev/play/p/FsmiNHCX-IA?v=gotip ( |
@DmitriyMV No, because io.Discard is an interface type. That is exactly the problem we need to address here. You will need to instantiate explicitly, as in var set mySet = toSet[io.Writer](os.Stdout, os.Stdin, io.Discard, os.Stdout) |
Change https://go.dev/cl/505395 mentions this issue: |
Print the unification mode in human-readable form. Use a tab and // instead of ()'s to show unification mode and whether operands where swapped. These changes only affect inference trace output, which is disabled by default. For easier debugging. For #60933. Change-Id: I95299c6e09b90670fc45addc4f9196b6cdd3b59f Reviewed-on: https://go-review.googlesource.com/c/go/+/505395 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Robert Griesemer <gri@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
Change https://go.dev/cl/506396 mentions this issue: |
Print the unification mode in human-readable form. Use a tab and // instead of ()'s to show unification mode and whether operands where swapped. These changes only affect inference trace output, which is disabled by default. For easier debugging. For golang#60933. Change-Id: I95299c6e09b90670fc45addc4f9196b6cdd3b59f Reviewed-on: https://go-review.googlesource.com/c/go/+/505395 TryBot-Result: Gopher Robot <gobot@golang.org> Run-TryBot: Robert Griesemer <gri@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
When unification of two types succeeds and at least one of them is an interface, we must be more cautious about when to accept the unification, to avoid order dependencies and unexpected inference results. The changes are localized and only affect matching against interfaces; they further restrict what are valid unifications (rather than allowing more code to pass). We may be able to remove some of the restriotions in a future release. See comments in code for a detailed description of the changes. Also, factored out "asInterface" functionality into a function to avoid needless repetition in the code. Fixes golang#60933. Fixes golang#60946. Change-Id: I923f7a7c1a22e0f4fd29e441e016e7154429fc5e Reviewed-on: https://go-review.googlesource.com/c/go/+/505396 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@google.com> Run-TryBot: Robert Griesemer <gri@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com>
According to the spec,
|
Example program https://go.dev/play/p/rLW5osbRq6Z?v=gotip
With current HEAD running this program prints
The second result is reasonable. The first, less so. The compiler has taken a value of type
*os.File
and a value of typeio.Writer
, which must be of the same type, and inferred a type argument ofio.Writer
. This seems potentially quite confusing, as pointed out at #60204 (comment).Let's make sure this is what we want before we commit it to 1.21.
CC @griesemer @findleyr
The text was updated successfully, but these errors were encountered: