scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.82k stars 1.05k forks source link

Implicits are resolved circularly when extending a class with an implicit parameter and field #16581

Open coreywoodfield opened 1 year ago

coreywoodfield commented 1 year ago

Compiler version

3.2.1

Minimized code

import scala.reflect.ClassTag

abstract class Companion[C](implicit val ct: ClassTag[C])

case class Impl()
object Impl extends Companion[Impl]

Output

-- Error: Test.scala:7:35 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
7 |object Impl extends Companion[Impl]
  |                                   ^
  |                                   super constructor cannot be passed a self reference Impl.ct unless parameter is declared by-name
1 error found

Expectation

It's trying to use Impl.ct as the implicit value to pass to the super constructor, but that obviously doesn't work. I would expect this to get the implicit ClassTag from wherever implicit ClassTags are normally gotten from. This works fine in scala 2. Removing the val also fixes it, but then you don't have the ClassTag field

prolativ commented 1 year ago

This used to compile in 3.0.0. However we cannot really call this a regression as it produced invalid bytecode that caused a crash at runtime

import scala.reflect.ClassTag

abstract class Companion[C](implicit val ct: ClassTag[C])

case class Impl()
object Impl extends Companion[Impl]

object Main {
  def main(args: Array[String]) =
    println(Impl)
}
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    Impl$.<init>()V @2: invokevirtual
  Reason:
    Type uninitializedThis (current frame, stack[1]) is not assignable to 'Impl$'
  Current Frame:
    bci: @2
    flags: { flagThisUninit }
    locals: { uninitializedThis }
    stack: { uninitializedThis, uninitializedThis }
  Bytecode:
    0x0000000: 2a2a b600 15b7 0018 b1                 

        at Main$.main(Circular.scala:10)
        at Main.main(Circular.scala)
dwijnand commented 11 months ago

Here's what I'm thinking: maybe in typedImplicit we can return a failure result if the implicit candidate we're considering is on a companion object Impl, while compiling (through ctx.owner) the constructor of object Impl. Then we just need to exempt if pt is by-name, which I assume needs to work seeing as CheckNoSuperThis makes reference to by-name implicits.

dwijnand commented 11 months ago

The workaround is explicitly passing ClassTag[Foo](classOf[Impl]), which seems reasonable.