Skip to content
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

Upper bound makes compiler infer Nothing #14218

Closed
pweisenburger opened this issue Jan 5, 2022 · 5 comments · Fixed by #16786
Closed

Upper bound makes compiler infer Nothing #14218

pweisenburger opened this issue Jan 5, 2022 · 5 comments · Fixed by #16786
Assignees
Milestone

Comments

@pweisenburger
Copy link
Contributor

pweisenburger commented Jan 5, 2022

Hi, for the following code, the type of res0 is inferred as Z[String] in Scala 3:

scala> class Z[S](v: S => Unit)
scala> new Z((s: String) => ())
val res0: Z[String] = /* ... */

If you add an upper bound to the S type variable, the type of res1 is inferred as Z[Nothing]:

scala> class Z[S <: String](v: S => Unit)
scala> new Z((s: String) => ())
val res1: Z[Nothing] = /* ... */

Tested with Scala 3.1.0 and 3.1.1-RC1. This is also different from Scala 2, where the type in both cases is Z[String]. I would expect the upper bound not to change type inference and the compiler to infer the more useful type String.

@smarter
Copy link
Member

smarter commented Jan 5, 2022

It's always delicate to decide whether type inference should instantiate a type variable to the lower or upper bound of its constraint when it appears in an invariant position, we end up relying on heuristics which won't always do what you expect. If you can make your type parameter variant that will let the compiler know which direction is more specific:

scala> class Z[-S <: String](v: S => Unit)
// defined class Z

scala> new Z((s: String) => ())
val res4: Z[String] = ...

@pweisenburger
Copy link
Contributor Author

Thanks, that helped! Making the type parameter contravariant fixes the problem. The full example in my code base fails to cross-compile with Scala 2 now (it seems variance checks for private members changed in Scala 3), but I think I will be able to sort this out.

I think what is confusing here is that the expected type String is kind of given explicitly but type inference chooses Nothing instead. This only happens in the particular case when the type written down coincides with the upper bound (but not if the type is a subtype of the upper bound). Intuitively, especially the case where the given type and the upper bound are identical should be obvious. But I assume the heuristics are hard to follow intuitively.

@joroKr21
Copy link
Member

joroKr21 commented Jan 5, 2022

it seems variance checks for private members changed in Scala 3

I think in Scala 3 private defaults to private[this] - which you have to specify explicitly in Scala 2.
private[this] members are exempt from variance checks because they can't be accessed from outside.

@smarter
Copy link
Member

smarter commented Jan 5, 2022

This only happens in the particular case when the type written down coincides with the upper bound (but not if the type is a subtype of the upper bound).

Yeah, the heuristic will try to pick what the user passed if it's more specific than the default bounds, I think it'd make sense to also do that if the user passed something that matches one of the bound, but it's hard for the compiler to distinguish that from the user not passing any constraint.

@pweisenburger
Copy link
Contributor Author

I think in Scala 3 private defaults to private[this] - which you have to specify explicitly in Scala 2.

Indeed, this seems to be the case. Thanks!

I think it'd make sense to also do that if the user passed something that matches one of the bound, but it's hard for the compiler to distinguish that from the user not passing any constraint.

Okay, I see. I agree it would make sense to do that since the behavior is quite surprising if you are not aware of the details of how type inference works. But it sounds like this would require quite some change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants