Scala-2.13 wierdness

In 2.12 this compiles:

val name: Option[String] = None
val displayName: String = name.getOrElse("nisse")
val nsName: StringOrNodeSeq = displayName
val frullName: StringOrNodeSeq = name.getOrElse("nisse")

But in 2.13 it fails with:

Error:(40, 50) type mismatch;
 found   : Object
 required: net.liftweb.common.StringOrNodeSeq
val frullName: StringOrNodeSeq = name.getOrElse("nisse")

Any idea why?

Looks like a problem where the libraries you are using (Lift and scala-xml) may not have adapted correctly to the 2.13 collections changes.

One such issue, https://github.com/scala/scala-xml/issues/392, was already fixed. Whether you have the fix depends the version of scala-xml on your classpath.

1 Like

I don’t understand how this relates to the scala-xml fixes? I’m using the latest 1.3.0 with the fixes you mention.
If I change the line to
val frullName: StringOrNodeSeq = name.getOrElse("nisse"):String
it works…

I don’t understand how name.getOrElse("nisse") is any different from the above cast?

Maybe it doesn’t, except as an illustration of the point that you need to suspect your libraries first.

Are you sure you’re not compiling two different versions of the code with the two Scala versions?

Is it name.getOrElse or nsName.getElse? Because if the type of name is Option[String], then the type of name.getOrElse is String, full stop, in either Scala version. So I don’t see how name.getOrElse could possibly be giving you the error message you included. But it does seem plausible that nsName.getOrElse would give that error.

Perhaps in reducing your real code to this example for posting, you made a mistake about what the compiler thinks the type of name is.

Are you able to reproduce the problem on https://scastie.scala-lang.org , or in a small standalone sbt project or Ammonite session you could share? Without easily being able to try it myself, I can only make guesses at what might be wrong.

2 Likes

It is actually name.getOrElse("nisse") which is inferred to be Object in this exact line of code.
I’ll cook up a self-contained example.

Here is an example in Scastie:

It doesn’t work on 2.12 either.

BTW, what did you tried to do with [T <% String]?

Also, implicit conversions are discouraged for a reason, and for what I can imagine, your code uses many of them; that would make the code hard to maintain in the long run.

Finally, if StringOrName is used as a magnet I would also recommend you look into another pattern. But that is most of a personal opinion than a fact, whereas most of the community agree on not using implicit conversions.

Aha… now we’re getting somewhere!

Note that I shouldn’t have said " the type of name.getOrElse is String, full stop, in either Scala version", because getOrElse has a type parameter that must be inferred:

def getOrElse[B >: A](default: => B): B =

Depending on context, the compiler may infer B to be String, but it might also infer some supertype such as AnyRef or Any.

In my defense, from a false premise (“this code is behaving differently in 2.12 and 2.13”) you can correctly infer anything at all :slight_smile:

Hm, I’ve tried to copy the relevant code from the Lift-framework StringOrNodeSeq and I definitly have code which works in 2.12 calling a function taking a StringOrNodeSeq, but doesn’t compile in 2.13. But in my example I cannot make it work with 2.12 either, strange.
But still - I cannot understand why it infers Object and not String in this case?

I’ve modified the Scastie-example to include [String] as type-param for getOrElse:

val frullName: StringOrName = name.getOrElse[String]("nisse")
Why is this necessary, how can “nisse” not be a String?

Did you read what I wrote about the signature of getOrElse?

Yes, and I still don’t understand how the compiler can in this case infer anything but String, when the type of the passed arguement is String. I mean, it must first compute the complete type of the rhs of the expression, then use that type and apply whatever conversion to the type of the lhs of the expression?

Not exactly. getOrElse expects a value of type B which may be whatever that is a super type of the type contained in the option. And in this case it sees that it has to return an StringOrName which is not a super type of String thus it fails. This is one of the proofs that implicit conversions are bad. If you instead had a toName extension method, it would have worked without any problems.

Right, that is obviously the way it works, but I don’t understand why it works that way (by design). B must be a supertype of the container-value, which is String, and the passed value is also of type String, so why is the type of the value to assign this to, StringOrName, even relevant for B?
Take this:

val a = name.getOrElse("nisse")

Here a is infered to be String, not Nothing. I don’t understand how it is not possible to drop-in this expression anywhere without affecting the infered type.

That isn’t how type inference in Scala works; your mental model is too simple. Types aren’t inferred in a vacuum, they’re inferred w/r/t some expected type bounds, and those bounds come from context.

Consider the following interaction:

scala 2.13.1> trait SomeTrait
defined trait SomeTrait

scala 2.13.1> val a: SomeTrait = Option("foo").getOrElse("bar")
                                                         ^
              error: type mismatch;
               found   : String("bar")
               required: SomeTrait

Look at the error closely and note where the caret is pointing. It isn’t saying the result of getOrElse is the wrong type, it’s saying "bar" is the wrong type! What’s happening here? Well, when the getOrElse call is being typechecked, the expected type of the call is SomeTrait — that’s coming from the left-hand-side. The only way that’s going to work is if B is some type compatible with SomeTrait — perhaps SomeTrait itself. So the typechecker says to itself, “B now has an upper bound of SomeTrait”. With that information in mind, tries to type check the argument to getOrElse. That argument doesn’t conform to B’s bounds, hence the error above.

I worked through this simpler example because it shows what I mean by an “expected type” guiding type inference, and proves that it’s happening, because if it weren’t, the error would be different.

OK, that’s enough for one post. I haven’t actually worked out where Object is coming from in your full example. I may have accidentally given the impression that I have the answer all worked out already. But I don’t, I’m exploring this with you and Luis a piece at time.

1 Like

Thanks for taking the time to explain, I get it now.