bug icon indicating copy to clipboard operation
bug copied to clipboard

`class LazyDefns$1 requires premature access` error using by-name implicits and subclasses

Open mrdziuban opened this issue 3 years ago • 3 comments

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.

mrdziuban avatar Apr 07 '22 19:04 mrdziuban

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

som-snytt avatar Apr 07 '22 20:04 som-snytt

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

joroKr21 avatar Apr 07 '22 20:04 joroKr21

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.

SethTisue avatar Apr 19 '22 15:04 SethTisue