My thought was that generating a companion for every class would bloat the bytecode among other drawbacks.
Instead, a (jvm) static apply method could always be generated for each constructor, that delegates to it directly. The additional bytecode size for this is much smaller than a companion. Companion apply methods that return the class type would have their implementations in static methods on the class, which the companion delegates to.
So a library compiled to call
Animal("slug") on a plain class would be wired up to the static factory method, and adding an apply method on a companion later would then modify the contents of this static method.
This would mean the binary compat vs source compat story is a bit better - add a companion apply method, and code compiled previously would pick up the implementation without a recompile.
There are some issues that make this non-trivial – jvm static method signatures can collide with instance methods, etc.
But it would help with the java interop story in general if more companion members were actually static elements on the class (with forwarders from the companion object instance). It would make it a lot easier to write wrapper-apis that allow java or other jvm languages to call Scala libraries, for example, or better, to share more of the api between a scala-idiomatic api and one that is meant for other jvm languages to use.
There are drawbacks to any approach that ‘fixes’ this issue as well. Users might be surprised when suddenly
new Animal("ant") is not the same as
Animal("ant") when it was in previous versions of the library, so I fall back to most of my prior arguments – adding or modifying apply methods in your api contract has consequences and there is no free lunch. Either the behavior changes at link time or compile time, but there IS a change, and the library writer needs to be aware of what it is and communicate it to users.
In that sense, the current proposal is the simplest solution possible if one desires plain classes to not require ‘new’ to be used in the source code. All solutions have drawbacks, even the status-quo. There is wisdom in choosing the simple solution that doesn’t involve modifying the compiler back-end, and so I feel that this proposal has more positives than negatives.