Scala Wart: Adding a companion object breaks seemingly-unrelated parts the codebase

I’ve been meaning to bring this up for a while. Consider the following code:

case class Data(int: Int)

def genericExampleFn[A, B](f: A => B) = ()

val great = genericExampleFn(Data)

So far, so good. Now imagine that code and much more like it is committed into a codebase and the world moves on. …Then later someone adds the following:

object Data {
  val empty = apply(0)
}

Now all those working instances of code similar to our val great = genericExampleFn(Data) example no longer compile. It’s quite a frustrating experience, especially for juniors who have no idea why or how they’ve broken the codebase.

The reason is that fully-generated companion objects extend (A => B), where as when one adds their own companion object, they obviously don’t write extends (A => B) themselves (or maybe AbstractFunction[A,B] - one of the two) and so that breaks causes where the companion object was being used as a function. Quite a common scenario.

I’d love to see this improved. My suggestion is when an object X (that isn’t a function), is supplied in cases where a function is required, rather than immediate rejection like we have at the moment, the compiler should also try X.apply. How would it work in the case of overloaded apply methods? I’m proposing a tiny AST rewrite so the behaviour would be identical to whatever would happen if the user had written X.apply instead of just X.

Thoughts?

2 Likes

I think https://github.com/lampepfl/dotty/pull/7207 addresses this problem.

4 Likes

Yeah looks like it. Awesome & thank you

1 Like