…s $init$.
Scala 2 uses a very simple mechanism to detect traits that have or
don't have a `$init$` method:
* At parsing time, a `$init$` method is created if and only if
there at least one member in the body that "requires" it.
* `$init$` are otherwise never created nor removed.
* A call to `T.$init$` in a subclass `C` is generated if and only
if `T.$init$` exists.
Scala 3 has a flags-based approach to the above:
* At parsing time, a `<init>` method is always created for traits.
* The `NoInits` flag is added if the body does not contain any
member that "requires" an initialization.
* In addition, the constructor receives the `StableRealizable` flag
if the trait *and all its base classes/traits* have the `NoInits`
flag. This is then used for purity analysis in the inliner.
* The `Constructors` phase creates a `$init$` method for traits
that do not have the `NoInits` flag, and generates calls based on
the same criteria.
Now, one might think that this is all fine, except it isn't when we
mix Scala 2 and 3, and in particular when a Scala 2 class extend a
Scala 3 trait.
Indeed, since Scala 3 *always* defines a `<init>` method in traits,
which Scala 2 translates as `$init$`, Scala 2 would think that it
always needs to emit a call to `$init$` (even for traits where
Scala 3 does not, in fact, emit a `$init$` method in the end).
This was mitigated in the TASTy reader of Scala 2 by removing
`$init$` if it has the `STABLE` flag (coming from
`StableRealizable`).
This would have been fine if `StableRealizable` was present if and
only if the owner trait has `NoInits`. But until this commit, that
was not the case: a trait without initialization in itself, but
extending a trait with initialization code, would have the flag
`NoInits` but its constructor would not have the `StableRealizable`
flag.
Therefore, this commit basically reconciles the `NoInits` and
`StableRealizable` flags, so that Scala 2 understands whether or
not a Scala 3 trait has a `$init$` method. We also align those
flags when reading from Scala 2 traits, so that Scala 3 also
understands whether or not a Scala 2 trait has a `$init$` trait.
This solves the compatibility issue between Scala 2 and Scala 3.
One detail remains. The attentive reader may have noticed the
quotes in 'an element of the body that "requires" initialization'.
Scala 2 and Scala 3 do not agree on what requires initialization:
notably, Scala 2 thinks that a concrete `def` requires
initialization, whereas Scala 3 knows that this is not the case.
This is not an issue for synchronous interoperability between Scala
2 and 3, since each decides on its own which of its traits has a
`$init$` method, and communicates that fact to the other version.
However, this still poses an issue for "diachronic" compatibility:
if a library defines a trait with a concrete `def` and is compiled
by Scala 2 in version v1, that trait will have a `$init$`. When the
library upgrades to Scala 3 in version v2, the trait will *lose*
the `$init$`, which can break third-party subclasses.
This commit does not address this issue. There are two ways we
could do so:
* Precisely align the detection of whether a trait should have a
`$init$` method with Scala 2 (notably, adding one when there is a
concrete `def`), or
* *Always* emit an empty static `$init$` method, even for traits
that have the `NoInits` flag (but do not generate calls to them,
nor have that affect whether or not subclasses are considered
pure).