Reputation: 350
For example I have a class Value and a implicit function convert string to Value:
case class Value(v: String)
implicit def strToValue(s: String): Value = Value(s)
And here is a trait which has a method returns Value:
trait ReturnValue {
def f: Value
}
Because the implicit conversion exists, I can implement method f by just return a String literal:
object Obj1 extends ReturnValue {
override def f: Value = "123"
}
And of course return a variable of type String just works fine:
object Obj2 extends ReturnValue {
override def f: Value = {
val v = Some("123").getOrElse("234")
v
}
}
But when I trying to use the result of Option.getOrElse directly as the return value:
object Obj3 extends ReturnValue {
override def f: Value = Some("123").getOrElse("234") // Compilation error: type mismatch
}
A compilation error occurred:
Error:(23, 50) type mismatch;
found : java.io.Serializable
required: Entry.Value
override def f: Value = Some("123").getOrElse("234") // Compilation error: type mismatch
It seems that type inference here is failed. Type String is not inferred, and then the implicit conversion cannot be matched. (Full file is here)
I have tried other functions which have type parameter, such as "map", they all works perfectly.
Why is the Option.getOrElse so special that type inference failed here?
Upvotes: 4
Views: 641
Reputation: 11
This appears to be a compiler bug. Here's an example showing the odd behavior.
case class ValString(string: String)
object ValString {
implicit def string2ValString(string: String): ValString = ValString(string)
}
case class ValThing( one: ValString, two: ValString, three: ValString, four: ValString, five: ValString)
val opt = Option("aString")
val x = ValThing(
if(opt.isEmpty) "" else opt.get,
opt.fold("")(identity),
opt.orElse(Option("")).get,
opt.getOrElse(""): String,
ValString(opt.getOrElse(""))
)
The above all works, however
val x = ValThing( opt.getOrElse(""), .... )
fails because it interprets the output of the getOrElse as a Serializable
Upvotes: 0
Reputation: 34393
This variants leads to the same compile error, and probably shows how compiler deconstructs the expression:
object Obj3 extends ReturnValue {
override def f: Value = {
val v = Some("123") // is of type Some(String)
v.getOrElse("234": Value) // Compilation error: type mismatch
}
}
Same error can also be achieved without any traits, with following simple repro:
case class Value(v: String)
implicit def strToValue(s: String): Value = Value(s)
val vs = Some("123")
val v: Value = vs.getOrElse("234")
It seems the compiler applies the conversion to Value
on the argument of getOrElse
, instead of on its result. The fact is does can be confirmed with output with enabled scalacOptions in Compile ++= Seq("-Xprint-types", "-Xprint:typer")
(cleaned a bit - deleted obviously unrelated annotations):
private[this] val v: Value =
vs.getOrElse{[B >: String](default: => B)B}[java.io.Serializable]{(default: => java.io.Serializable)java.io.Serializable}( strToValue{(s: String)Value}("234"{String("234")}){Value} ){
I think the inference works as follows:
vs
type is known as Some[String]
getOrElse
declaration is def getOrElse[B >: A](default: => B): B
(A
is String
here)B
as Value
, as this is the expected result type of the expression.Value
and String
is Serializable
You can also note how it behaves when you remove the implicit conversion completely. The error for val v: Value = vs.getOrElse("234")
is then: type mismatch;
found : String("234")
required: Value
Upvotes: 3