bug
bug copied to clipboard
`class LazyDefns$1 requires premature access` error using by-name implicits and subclasses
Reproduction steps
Scala version: 2.13.8
trait ByNameImplicit[A, B]
object ByNameImplicit {
implicit def invert[A, B](implicit ab: => ByNameImplicit[A, B]): ByNameImplicit[B, A] =
new ByNameImplicit[B, A] {}
}
abstract class Class1[A](implicit val byNameImpl: ByNameImplicit[A, A])
abstract class Class2 extends Class1[Int]
// Implementation restriction: class LazyDefns$1 requires premature access to class Class2.
Here's the same code in a scastie: https://scastie.scala-lang.org/mrdziuban/KhMLbN5dSQCMIbaQARLN7g/1
Problem
I'm not 100% sure what's expected here, but the error caught me off guard. It didn't point to any particular code so I only determined it was related to by-name implicits by googling and finding mention of LazyDefns$1 in the documentation for implicits.
For what it's worth, the error does not happen with Scala 3.x.
It's an older check and a newer feature. I was also curious about documentation; by-name implicits merit a mention on the release notes but are swamped by other items https://github.com/scala/scala/releases/tag/v2.13.0
The error comes from: https://github.com/scala/scala/blob/2.13.x/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala#L422
Looks like Scala 3 has a HoistSuperArgs which makes code like this simpler:
https://github.com/lampepfl/dotty/blob/main/compiler/src/dotty/tools/dotc/transform/HoistSuperArgs.scala
Another issue is that the synthetic LazyDefns$1 tree has no position.
Scala 2
❯ scala -Vprint:typer
Welcome to Scala 2.13.8 (OpenJDK 64-Bit Server VM, Java 17.0.2).
Type in expressions for evaluation. Or try :help.
scala> trait ByNameImplicit[A, B]
| object ByNameImplicit {
| implicit def invert[A, B](implicit ab: => ByNameImplicit[A, B]): ByNameImplicit[B, A] =
| new ByNameImplicit[B, A] {}
| }
|
| abstract class Class1[A](implicit val byNameImpl: ByNameImplicit[A, A])
| abstract class Class2 extends Class1[Int]
| // Implementation restriction: class LazyDefns$1 requires premature access to class Class2.
[[syntax trees at end of typer]] // <console>
package $line3 {
sealed class $read extends AnyRef with Serializable {
def <init>(): $line3.$read = {
$read.super.<init>();
()
};
sealed class $iw extends AnyRef with java.io.Serializable {
def <init>(): $iw = {
$iw.super.<init>();
()
};
abstract trait ByNameImplicit[A, B] extends scala.AnyRef;
object ByNameImplicit extends scala.AnyRef {
def <init>(): ByNameImplicit.type = {
ByNameImplicit.super.<init>();
()
};
implicit def invert[A, B](implicit ab: => ByNameImplicit[A,B]): ByNameImplicit[B,A] = {
final class $anon extends AnyRef with ByNameImplicit[B,A] {
def <init>(): <$anon: ByNameImplicit[B,A]> = {
$anon.super.<init>();
()
};
<empty>
};
new $anon()
}
};
abstract class Class1[A] extends scala.AnyRef {
<paramaccessor> private[this] val byNameImpl: ByNameImplicit[A,A] = _;
implicit <stable> <accessor> <paramaccessor> def byNameImpl: ByNameImplicit[A,A] = Class1.this.byNameImpl;
def <init>()(implicit byNameImpl: ByNameImplicit[A,A]): Class1[A] = {
Class1.super.<init>();
()
}
};
abstract class Class2 extends Class1[Int] {
def <init>(): Class2 = {
Class2.super.<init>()({
final <synthetic> class LazyDefns$1 extends AnyRef with java.io.Serializable {
def <init>(): LazyDefns$1 = {
LazyDefns$1.super.<init>();
()
};
final private[this] val rec$1: ByNameImplicit[Int,Int] = $iw.this.ByNameImplicit.invert[Int, Int](rec$1);
final <synthetic> <stable> <accessor> def rec$1: ByNameImplicit[Int,Int] = LazyDefns$1.this.rec$1
};
final <synthetic> val lazyDefns$1: LazyDefns$1 = new LazyDefns$1();
lazyDefns$1.rec$1
});
()
}
}
};
private[this] val $iw: $iw = new $read.this.$iw();
<stable> <accessor> def $iw: $iw = $read.this.$iw
};
object $read extends scala.AnyRef with java.io.Serializable {
def <init>(): type = {
$read.super.<init>();
()
};
private[this] val INSTANCE: $line3.$read = new $read();
<stable> <accessor> def INSTANCE: $line3.$read = $read.this.INSTANCE;
<synthetic> private def writeReplace(): Object = new scala.runtime.ModuleSerializationProxy(classOf[$line3.$read$])
}
}
error: Implementation restriction: class LazyDefns$1 requires premature access to class Class2.
Scala 3
❯ scala -Vprint:constructors
Welcome to Scala 3.1.1 (18, Java OpenJDK 64-Bit Server VM).
Type in expressions for evaluation. Or try :help.
scala> trait ByNameImplicit[A, B]
| object ByNameImplicit {
| implicit def invert[A, B](implicit ab: => ByNameImplicit[A, B]): ByNameImplicit[B, A] =
| new ByNameImplicit[B, A] {}
| }
|
| abstract class Class1[A](implicit val byNameImpl: ByNameImplicit[A, A])
| abstract class Class2 extends Class1[Int]
| // Implementation restriction: class LazyDefns$1 requires premature access to class Class2.
[[syntax trees at end of constructors]] // rs$line$1
package repl {
final lazy module val rs$line$1: repl.rs$line$1 = new repl.rs$line$1()
@SourceFile("rs$line$1") final module class rs$line$1 extends Object {
def <init>(): Unit =
{
super()
()
}
private def writeReplace(): Object =
new scala.runtime.ModuleSerializationProxy(classOf[repl.rs$line$1])
trait ByNameImplicit() extends Object {}
final lazy module val ByNameImplicit: ByNameImplicit = new ByNameImplicit()
final module class ByNameImplicit extends Object {
def <init>(): Unit =
{
super()
()
}
private def writeReplace(): Object =
new scala.runtime.ModuleSerializationProxy(classOf[ByNameImplicit])
implicit def invert(implicit ab: Function0): ByNameImplicit =
{
final class $anon extends Object, ByNameImplicit {
def <init>(): Unit =
{
super()
()
}
}
new Object with ByNameImplicit {...}():ByNameImplicit
}
}
abstract class Class1 extends Object {
def <init>(implicit byNameImpl: ByNameImplicit): Unit =
{
this.byNameImpl = byNameImpl
super()
()
}
private val byNameImpl: ByNameImplicit
implicit def byNameImpl(): ByNameImplicit = this.byNameImpl
}
abstract class Class2 extends Class1 {
def <init>(): Unit =
{
super(Class2$superArg$1())
()
}
}
private def Class2$superArg$1(): ByNameImplicit =
{
final class $_lazy_implicit_$2 extends Object, java.io.Serializable {
def <init>(): Unit =
{
super()
this.$_lazy_implicit_$1 =
ByNameImplicit.invert(
{
def $anonfun(): ByNameImplicit = this.$_lazy_implicit_$1()
closure($anonfun:Function0)
}
)
()
}
private val $_lazy_implicit_$1: ByNameImplicit
def $_lazy_implicit_$1(): ByNameImplicit = this.$_lazy_implicit_$1
}
val $_lazy_implicit_$3: $_lazy_implicit_$2 = new $_lazy_implicit_$2()
$_lazy_implicit_$3.$_lazy_implicit_$1()
}
}
}
// defined trait ByNameImplicit
// defined object ByNameImplicit
// defined class Class1
// defined class Class2
Not sure whether to label this "help wanted" or not. Definitely a valid bug, but almost certainly not an easy fix. Emitting the same code as Scala 3 is feasible but the implementation would be complex and error-prone due to the hoisting involved. Whether hoisting is the only possible solution here isn't clear to me.