scala / scala3

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

Cannot resolve reference to type member inherited through self type #21219

Open m8nmueller opened 4 months ago

m8nmueller commented 4 months ago

Compiler version

3.4.2

Minimized code

I tried to minimize as much as possible, but the crash is solved when either removing map or fold. If the two files are merged together, the compile still crashes, but it prints a type error before. I know that the self types of Stream and PullReaderStream do not work out, but that will be my business :).

File Stream.scala:

trait StreamOps[+T]:
  self: Family[T] =>

  type Family[+V] <: { type Result[+W] }
  type ThisStream[+V] <: StreamOps[V] { type Family[T] = self.Family[T] }

  def map[V](mapper: T => V): ThisStream[V]
  def fold(): Result[Unit]

trait Stream[+T] extends StreamOps[T]:
  override type Family[T] = Object { type Result[+V] =  V }

File PullStream.scala

trait PullReaderStream[+T] extends PullReaderStreamOps[T] with Stream[T]:
  override type ThisStream[+V] = PullReaderStream[V]

  override def map[V](mapper: T => V): PullReaderStream[V] =
    new PullReaderStream[V] {}

trait PullReaderStreamOps[+T] extends StreamOps[T]:
  self: Family[T] =>
  override type ThisStream[+V] <: PullReaderStreamOps[V] { type Family[T] = self.Family[T] }

  override def fold(): self.Result[Unit] = ???

Output (click arrow to expand)

```scala unhandled exception while running MegaPhase{crossVersionChecks, firstTransform, checkReentrant, elimPackagePrefixes, cookComments, checkLoopingImplicits, betaReduce, inlineVals, expandSAMs, elimRepeated, refchecks} on PullStream.scala An unhandled exception was thrown in the compiler. Please file a crash report here: https://github.com/scala/scala3/issues/new/choose For non-enriched exceptions, compile with -Yno-enrich-error-messages. while compiling: PullStream.scala during phase: MegaPhase{crossVersionChecks, firstTransform, checkReentrant, elimPackagePrefixes, cookComments, checkLoopingImplicits, betaReduce, inlineVals, expandSAMs, elimRepeated, refchecks} mode: Mode(ImplicitsEnabled) library version: version 2.13.12 compiler version: version 3.4.2 settings: cannot resolve reference to type ($anon.this : Object with PullReaderStream[V] {...}).Result the classfile defining the type might be missing from the classpath ``` When merging the files, this is prefixed by: ```scala -- [E058] Type Mismatch Error: Stream.scala:10:6 ------------------------------- 10 |trait Stream[+T] extends StreamOps[T]: | ^ |illegal inheritance: self type Stream[T] of trait Stream does not conform to self type Stream.this.Family[T] |of parent trait StreamOps | | longer explanation available when compiling with `-explain` ```
m8nmueller commented 4 months ago

I have a follow-up crash when I fix that problem (by adding type Result[+V] = V to Stream), though they don't seem to be directly related (this one also happens in a much later phase).

Again it involves two files, but this time, it only happens when compiling one after another (so scalac Stream.scala followed by scalac test.scala). I could simplify the type parameters a bit but the Family/Result types seem to be involved, as the crash vanishes when I remove them.

File Stream.scala

trait StreamOps:
  self: Family =>
  type Family
  type Result[+W]

trait PullReaderStreamOps extends StreamOps:
  self: Family =>

  def parallelismHint: Int
  def fold(): Result[Unit]

File test.scala

def test(stream: PullReaderStreamOps): Int = stream.parallelismHint

Output (click arrow to expand)

```scala unhandled exception while running MegaPhase{elimErasedValueType, pureStats, vcElideAllocations, etaReduce, arrayApply, elimPolyFunction, tailrec, completeJavaEnums, mixin, lazyVals, memoize, nonLocalReturns, capturedVars} on test.scala An unhandled exception was thrown in the compiler. Please file a crash report here: https://github.com/scala/scala3/issues/new/choose For non-enriched exceptions, compile with -Yno-enrich-error-messages. while compiling: test.scala during phase: MegaPhase{elimErasedValueType, pureStats, vcElideAllocations, etaReduce, arrayApply, elimPolyFunction, tailrec, completeJavaEnums, mixin, lazyVals, memoize, nonLocalReturns, capturedVars} mode: Mode(ImplicitsEnabled) library version: version 2.13.12 compiler version: version 3.4.2 settings: Exception in thread "main" Exception: dotty.tools.dotc.core.RecursionOverflow thrown from the UncaughtExceptionHandler in thread "main" ```

Another Edit: Dropping the second file, even reading the first (scalac -decompile PullReaderStreamOps.tasty) fails with the same dotty.tools.dotc.core.RecursionOverflow thrown from the UncaughtExceptionHandler in thread "main".

KacperFKorban commented 4 months ago

It looks like the original issue doesn't crash with nightly anymore, however both the original snippet and the new snippet crash with -Ytest-pickler. Which is just a shorthand for testing decompilation i.e. the same thing as described in the last comment, just easier to test.

I managed to minimize it even further with -Ytest-pickler

Code:

//> using scala 3.nightly
//> using options -Ytest-pickler

trait Parent:
  type Family

trait Child extends Parent:
  self: Family =>

Crash:

```scala exception occurred while compiling List(~/bugs/i21219.scala) An unhandled exception was thrown in the compiler. Please file a crash report here: https://github.com/scala/scala3/issues/new/choose For non-enriched exceptions, compile with -Xno-enrich-error-messages. while compiling: during phase: parser mode: Mode(ImplicitsEnabled) library version: version 2.13.14 compiler version: version 3.6.0-RC1-bin-20240717-45dcb51-NIGHTLY-git-45dcb51 settings: -Ytest-pickler true -classpath ~/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.6.0-RC1-bin-20240717-45dcb51-NIGHTLY/scala3-library_3-3.6.0-RC1-bin-20240717-45dcb51-NIGHTLY.jar:~/.cache/coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.14/scala-library-2.13.14.jar -d ~/bugs/.scala-build/bugs_c9872a121f-a023cf1e0c/classes/main -java-output-version 11 -sourceroot ~/bugs Exception in thread "main" dotty.tools.dotc.core.RecursionOverflow: TypeError Compilation failed ```
m8nmueller commented 4 months ago

Thanks for your help!

When you look at the tasty generated for your example, it contains the following definition:

Tasty Excerpt ```scala 4: TYPEDEF(53) 2 [Child] 7: TEMPLATE(31) 9: TYPEREF 3 [Object] 11: TERMREFpkg 6 [java[Qualified . lang]] 13: IDENTtpt 7 [Parent] 15: TYPEREF 7 [Parent] 17: THIS 18: TYPEREFpkg 1 [] 20: SELFDEF 8 [self] 22: IDENTtpt 9 [Family] 24: TYPEREF 9 [Family] 26: THIS 27: TYPEREFsymbol 4 29: SHAREDtype 17 ```

The type reference TYPEREFsymbol 4 makes the unpickler recurse endlessly:

Stack trace from the debugger ``` Types$ClassInfo.selfType(Contexts$Context): Types$Type (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:5518) Types$ThisType.underlying(Contexts$Context): Types$Type (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:3178) Types$Type.goThis$1(Contexts$Context,Names$Name,Types$Type,long,long,Types$ThisType): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:888) Types$Type.go$1(Contexts$Context,Names$Name,Types$Type,long,long,Types$Type): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:798) Types$Type.findMember(Names$Name,Types$Type,long,long,Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:962) Types$Type.memberBasedOnFlags(Names$Name,long,long,Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:754) Types$Type.member(Names$Name,Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:738) Types$NamedType.memberDenot(Types$Type,Names$Name,boolean,Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2591) Types$NamedType.memberDenot(Names$Name,boolean,Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2578) Types$NamedType.fromDesignator$1(Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2533) Types$NamedType.computeDenot(Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2558) Types$NamedType.denot(Contexts$Context): Denotations$Denotation (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2513) Types$NamedType.computeSymbol(Contexts$Context): Symbols$Symbol (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2462) Types$NamedType.symbol(Contexts$Context): Symbols$Symbol (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:2455) Types$Type.dealias1(Function1,boolean,Contexts$Context): Types$Type (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:1470) Types$Type.dealiasKeepAnnots(Contexts$Context): Types$Type (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:1506) Types$ClassInfo.selfType(Contexts$Context): Types$Type (/scala3/compiler/src/dotty/tools/dotc/core/Types.scala:5518) ```

If, instead, I add an override type Family to child, the tasty looks like below. Since the self type references the symbol from its own template (24: TYPEREFsymbol 40), this seems to be cacheable.

Tasty Excerpt with override ```scala 4: TYPEDEF(67) 2 [Child] 7: TEMPLATE(45) 9: TYPEREF 3 [Object] 11: TERMREFpkg 6 [java[Qualified . lang]] 13: IDENTtpt 7 [Parent] 15: TYPEREF 7 [Parent] 17: THIS 18: TYPEREFpkg 1 [] 20: SELFDEF 8 [self] 22: IDENTtpt 9 [Family] 24: TYPEREFsymbol 40 26: THIS 27: TYPEREFsymbol 4 29: SHAREDtype 17 31: DEFDEF(7) 10 [] 34: EMPTYCLAUSE 35: TYPEREF 11 [Unit] 37: TERMREFpkg 12 [scala] 39: STABLE 40: TYPEDEF(12) 9 [Family] 43: TYPEBOUNDStpt(8) 45: TYPEREF 13 [Nothing] 47: SHAREDtype 37 49: TYPEREF 14 [Any] 51: SHAREDtype 37 53: OVERRIDE ```

Somehow the typer manages to trace the self type to the type definition (though it is arguably cyclic and self: this.Family is rejected). If the type definition happens to be in the same class this will also be correctly unpickled.

But I don't understand the compiler well enough to track it down completely. I guess, the type reference to the Family type should actually reference it from Parent, not from Child itself.