scala / scala3

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

Spurious CyclicReference error while unpickling #13904

Open Katrix opened 2 years ago

Katrix commented 2 years ago

Compiler version

3.1.0

This feels very much like #13190, except it has a few more moving parts I haven't been able to remove while minimizing it.

Minimized code

// fileA.scala

class DiscordRecord[Obj] extends Selectable
trait DiscordRecordCompanion[AbsStructural[A] <: DiscordRecord[A], Supertypes] {
  opaque type R <: AbsStructural[this.type] & Supertypes = AbsStructural[this.type] & Supertypes
}

type ChannelR[Obj] = DiscordRecord[Obj] {
  val id: SnowflakeType[Channel]
}
type Channel = Channel.R
object Channel extends DiscordRecordCompanion[ChannelR, Any]

type GuildChannelR[Obj] = ChannelR[Obj] {
  val id: SnowflakeType[GuildChannel]
}
type GuildChannel = GuildChannel.R
object GuildChannel extends DiscordRecordCompanion[GuildChannelR, Channel]

type TopLevelGuildChannelR[Obj] = GuildChannelR[Obj] {
  val id: SnowflakeType[TopLevelGuildChannel]
}
type TopLevelGuildChannel = TopLevelGuildChannel.R
object TopLevelGuildChannel extends DiscordRecordCompanion[TopLevelGuildChannelR, GuildChannel]

// fileB.scala
trait TC[C]

opaque type SnowflakeType[+A] = Long
object SnowflakeType {
  def useSubtype[C <: Channel]: TC[SnowflakeType[C]] = ???
  given TC[SnowflakeType[TopLevelGuildChannel]] = useSubtype[TopLevelGuildChannel]
}

val ev  = summon[GuildChannel <:< Channel]
//val ev2 = summon[GuildChannel <:< Channel]

Steps to reproduce

  1. Compile both files together in a project. Everything should compile fine.
  2. Uncomment ev2 in fileB.scala
  3. Try to compile again (and not cleaning beforehand). It will error for ev1 and ev2
  4. Clean and compile. It works again

Output

[error] -- Error: D:\DevProjects\Incubating\AckCordNext\src\main\scala\ackcordnext\data\datamodel\error\SnowflakeType.scala:11:42
[error] 11 |val ev  = summon[GuildChannel <:< Channel]
[error]    |                                          ^
[error]    |Cannot prove that ackcordnext.data.datamodel.error.GuildChannel <:< ackcordnext.data.datamodel.error.Channel.
[error] -- Error: D:\DevProjects\Incubating\AckCordNext\src\main\scala\ackcordnext\data\datamodel\error\SnowflakeType.scala:12:42
[error] 12 |val ev2 = summon[GuildChannel <:< Channel]
[error]    |                                          ^
[error]    |Cannot prove that ackcordnext.data.datamodel.error.GuildChannel <:< ackcordnext.data.datamodel.error.Channel.

Expectation

It should compile in both cases, also when using incremental compilation.

griggt commented 2 years ago

It turns out that opaque types are not necessary to reproduce this. And with a bit of simplification and reorganization, we can show this is purely a separate compilation (pickling) issue:

fileA_1.scala

class DiscordRecord[Obj]
trait DiscordRecordCompanion[AbsStructural[A], Supertypes] {
  type R = AbsStructural[this.type] & Supertypes
}

type ChannelR[Obj] = DiscordRecord[Obj]
type Channel = Channel.R
object Channel extends DiscordRecordCompanion[ChannelR, Any]

type GuildChannelR[Obj] = ChannelR[Obj] {
  val id: SnowflakeType[GuildChannel]
}
type GuildChannel = GuildChannel.R
object GuildChannel extends DiscordRecordCompanion[GuildChannelR, Channel]

type TopLevelGuildChannelR[Obj] = GuildChannelR[Obj]
type TopLevelGuildChannel = TopLevelGuildChannel.R
object TopLevelGuildChannel extends DiscordRecordCompanion[TopLevelGuildChannelR, GuildChannel]

type SnowflakeType[A] = Long

fileB_2.scala

//def bar: GuildChannel = ???    // problem goes away if this is uncommented
def foo: TopLevelGuildChannel = ???
val ev = summon[GuildChannel <:< Channel]
// joint compilation succeeds
$ scalac -3.1.0 fileA_1.scala fileB_2.scala

// separate compilation fails
$ scalac -3.1.0 fileA_1.scala && scalac -3.1.0 fileB_2.scala
-- Error: fileB_2.scala:3:41 ---------------------------------------------------
3 |val ev = summon[GuildChannel <:< Channel]
  |                                         ^
  |                               Cannot prove that GuildChannel <:< Channel.

Since #14884, we get an additional error which may help shed some light:

$ scalac -3.head fileA_1.scala && scalac -3.head fileB_2.scala
-- Error: fileB_2.scala:2:9 ----------------------------------------------------
2 |def foo: TopLevelGuildChannel = ???
  |         ^
  |Could not read definition of type TopLevelGuildChannel in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.TypeError: Could not read definition of type TopLevelGuildChannelR in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.TypeError: Could not read definition of type GuildChannelR in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.TypeError: Could not read definition of type GuildChannel in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.CyclicReference: 
  |Run with -Ydebug-unpickling to see full stack trace.
  |Run with -Ydebug-unpickling to see full stack trace.
  |Run with -Ydebug-unpickling to see full stack trace.
  |Run with -Ydebug-unpickling to see full stack trace.
-- Error: fileB_2.scala:3:41 ---------------------------------------------------
3 |val ev = summon[GuildChannel <:< Channel]
  |                                         ^
  |                               Cannot prove that GuildChannel <:< Channel.
2 errors found

Interestingly, compiling with -Ydebug-unpickling does not show the full stack trace as suggested, but instead just suppresses the error; the re-thrown CyclicReference exception ends up swallowed here: https://github.com/lampepfl/dotty/blob/1a4db8910e0c808d7323796efcd610a050dd70bb/compiler/src/dotty/tools/dotc/typer/Checking.scala#L328-L332

Compiling with -Ylog:all -Ydebug -Ydebug-unpickling shows

[log parser] cycle detected for GuildChannel, false, false

Turning on the completion printer we see

  completing type TopLevelGuildChannel
    completing val TopLevelGuildChannel
    completed TopLevelGuildChannel in package object fileA_1$package
    completing type TopLevelGuildChannel
      completing type DiscordRecordCompanion
      completed DiscordRecordCompanion in package <empty>
    completed TopLevelGuildChannel in package object fileA_1$package
    completing type DiscordRecordCompanion
    completed DiscordRecordCompanion in package <empty>
    completing type R
    completed R in trait DiscordRecordCompanion
    completing type TopLevelGuildChannelR
      completing type GuildChannelR
        completing type ChannelR
          completing type DiscordRecord
          completed DiscordRecord in package <empty>
          completing type DiscordRecord
          completed DiscordRecord in package <empty>
        completed ChannelR in package object fileA_1$package
        completing type SnowflakeType
        completed SnowflakeType in package object fileA_1$package
        completing val fileA_1$package
        completed fileA_1$package in package <empty>
        completing type GuildChannel
          completing val GuildChannel
          completed GuildChannel in package object fileA_1$package
          completing type GuildChannel
          completed GuildChannel in package object fileA_1$package
          completing type GuildChannelR
          completed GuildChannel in package object fileA_1$package
        completed GuildChannelR in package object fileA_1$package
      completed TopLevelGuildChannelR in package object fileA_1$package
    completed TopLevelGuildChannel in package object fileA_1$package
-- Error: tests/pos/i13904/fileB_2.scala:2:9 -----------------------------------
2 |def foo: TopLevelGuildChannel = ???
  |         ^
  |Could not read definition of type TopLevelGuildChannel in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.TypeError: Could not read definition of type TopLevelGuildChannelR in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.TypeError: Could not read definition of type GuildChannelR in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.TypeError: Could not read definition of type GuildChannel in ./fileA_1$package.class
  |An exception was encountered:
  |  dotty.tools.dotc.core.CyclicReference: 

Uncommenting the definition of bar in fileB_2.scala causes GuildChannel to be completed earlier and avoids the CyclicReference error.