"Private" primary constructor vs MiMa

The primary constructor of a class can be defined as private.
But it is not private at the level of bytecode, which is what MiMa works with.

Thus changes in such private primary constructor is deemed breaking by MiMa. But these changes are invisible to regular Scala code, so it shouldn’t break. But again, the constructor is visible to Java code which could break.

What to do with it?

  • Keep things as they are
  • “Fix” MiMa, so that it doesn’t mark private constructors as breaking
  • Find a way to make the primary constructor private even at the bytecode level
  • Anything else?

This issue came up when discussing how to evolve case classes without breaking backward compatibility.

/cc @julienrf @odersky @sjrd

2 Likes

I just checked and if I write class Foo private (x: Int), then I do get a private constructor in Scala 3.
However, this isn’t the case in Scala 2, I think the best we could do there is to emit the constructor as ACC_SYNTHETIC to make it invisible to javac without affecting binary compatibility.

Even if you have both class and companion object?

case class Foo private (x: Int)

object Foo:
  def apply(x: Int): Foo = new Foo(x)

Ah yeah, in that case we’re back to the public constructor which could be hidden with ACC_SYNTHETIC (we could do better with java 9+ nestmates but that’s more work).

2 Likes

Have we ever considered making these things package private (the real JVM package private, not Scala’s version)?

1 Like

Thank you for the suggestion, that sounds like a good solution. I have created issues for Scala 2 and Scala 3:

1 Like

After more discussion, we concluded that this should be handled at the MiMa level, so I have created that issue:

1 Like