Closed Swoorup closed 2 years ago
Appears to resolve the issue if I enforce the string type explicitly like
val pongBehavior: [O] => (Unit, PingMessage[O]) => (Unit, O) = [O] =>
(state: Unit, msg: PingMessage[O]) =>
msg match
case PingMessage.Ping(from) => ((), s"Pong from $from": O)
A slightly smaller minimization without a custom type:
val poly: [O] => Option[O] => O = [O] =>
(opt: Option[O]) =>
opt match
case Some(s: String) => (s: String)
[error] Found: [O] => (Option[O]) => String
[error] Required: [O] => (Option[O²]) => O²
[error]
[error] where: O is a type variable
[error] O² is a type variable
[error] case Some(s: String) => (s: String)
[error] ^
The error is quite confusing here. It should be either
Required: [O] => (Option[O]) => O
or
Required: [O²] => (Option[O²]) => O²
Secondly, it seems that the problem also occurs for methods:
def poly[O]: Option[O] => O =
(opt: Option[O]) =>
opt match
case Some(s: String) => (s: String)
[error] Found: String
[error] Required: O
[error] case Some(s: String) => (s: String)
[error] ^^^^^^^^^
Here the problem disappears when the type ascription gets removed
case Some(s: String) => s
@prolativ expanding your example, I found another issue with inference. Should I raise a different ticket for this?
val poly: [O] => Option[O] => O =
case Some(s: String) => s
Missing parameter type
I could not infer the type of the parameter x$1 of expanded function:
x$1 =>
x$1 match
{
case Some(s:String) =>
s
}.
Not found: s
I think the compiler is right rejecting this code although the compilation error might not be very informative here. The point is that case Some(s: String) => s
is a monomorphic (partial) function while [O] => Option[O] => O
is a polymorphic function type. Something like
val poly: [O] => Option[O] => O =
[O] => { case Some(s: String) => s }
might work in the future but currently it's not possible because of
Implementation restriction: polymorphic function literals must have a value parameter
Currently we have to live with
val poly: [O] => Option[O] => O =
[O] => (o: Option[O]) => o match { case Some(s: String) => s }
I would expect
val poly: [O] => Option[O] => O =
[O] => o => o match { case Some(s: String) => s }
to work too but this fails with
[error] cannot infer type; expected type <?> is not fully defined
[error] [O] => o => o match { case Some(s: String) => s }
[error] ^
[error] Not found: s
[error] [O] => o => o match { case Some(s: String) => s }
[error] ^
[error] Found: [O] => (<error cannot infer type; expected type <?> is not fully defined>) =>
[error] <error Not found: s>
[error] Required: [O] => (Option[O]) => O
[error] [O] => o => o match { case Some(s: String) => s }
[error]
which indeed looks like a bug to me
A slightly smaller minimization without a custom type:
val poly: [O] => Option[O] => O = [O] => (opt: Option[O]) => opt match case Some(s: String) => (s: String)
There's some funkiness that needs attention, but your minimisation is different because now the scrutinee type is co-variant. And I think the behaviour is right because O is more precise than String, so you're widening by using the type ascription, akin to expecting (s: Any)
to work.
Dale and I have been looking at this further.
Our current hypothesis is that the root cause here is that when a polymorphic function literal is desugared, the result type of the apply
method is always left blank and it's up to type inference to fill it in. Desugar.scala
line 1734 creates this DefDef
:
DefDef(nme.apply, applyTParams :: applyVParams :: Nil, TypeTree(), res)
where the TypeTree()
is the missing result type of the apply
.
Let's return to Dale's minimization. To keep this straight in your head, it's helpful not to reuse the same type parameter name. So the minimization becomes:
val poly: [O1] => Option[O1] => O1 = // line 1
[O2] => // line 2
(opt: Option[O2]) => ... // line 3
line 1 is where we say what the expected type of the polymorphic function is, and that expected type includes the information that the body's result type is O1
.
But when lines 2 and 3 are desugared, the desugaring includes def apply(...) =
, whereas what we want is def apply(...): O2 = ...
, since if we don't put O2
there, inference puts String
there instead, which is wrong.
So we'd need to take the expected type [O1] => Option[O1] => O1
, extract the third O1
, replace all of the type parameters according to their indices so that O1
becomes O2
, and then fill that in.
Again: keep an eye out for variance. If you switch to a covariant Option/Some instead of sticking to something invariant like PingMessage/Ping was, the behaviour is going to be different.
Compiler version
3.1.3
Minimized code
Scastie: https://scastie.scala-lang.org/omCKwdwSQ2uGyiYSPRbOLQ
Output
Expectation
Compiles successfully