lxohi
lxohi

Reputation: 350

Why type inference failed in Scala when using implicit conversion on return value of Option.getOrElse?

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

Answers (2)

user8600324
user8600324

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

Suma
Suma

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)
  • compilers infers the B as Value, as this is the expected result type of the expression.
  • the most specific supertype of 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

Related Questions