Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler crash using export instead of import #13490

Closed
oscar-broman opened this issue Sep 8, 2021 · 5 comments · Fixed by #14461
Closed

Compiler crash using export instead of import #13490

oscar-broman opened this issue Sep 8, 2021 · 5 comments · Fixed by #14461

Comments

@oscar-broman
Copy link

Compiler version

3.1.1-RC1-bin-20210907-a47a81a-NIGHTLY

Minimized code

object MyApi {
  enum MyEnum(a: Int) {
    case A extends MyEnum(1)
  }
  case class Foo(a: MyEnum)
}

object Test {
  export MyApi.*
  import MyEnum.*
  Foo(MyEnum.A) match {
    case Foo(a) =>
      a match {
        case A =>
      }
  }
}

Note: changing export MyApi.* to import MyApi.* works just fine.

Output

Error while emitting rs$line$2
Exception in thread "main" java.lang.AssertionError: assertion failed: val <none>
	at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
	at dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.assertClassNotArray(BCodeHelpers.scala:241)
	at dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.assertClassNotArrayNotPrimitive(BCodeHelpers.scala:246)
	at dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.getClassBTypeAndRegisterInnerClass(BCodeHelpers.scala:265)
	at dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.getClassBTypeAndRegisterInnerClass$(BCodeHelpers.scala:210)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.getClassBTypeAndRegisterInnerClass(BCodeSkelBuilder.scala:63)
	at dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.internalName(BCodeHelpers.scala:237)
	at dotty.tools.backend.jvm.BCodeHelpers$BCInnerClassGen.internalName$(BCodeHelpers.scala:210)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.internalName(BCodeSkelBuilder.scala:63)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.fieldOp(BCodeBodyBuilder.scala:467)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.fieldLoad(BCodeBodyBuilder.scala:454)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:384)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genEqEqPrimitive(BCodeBodyBuilder.scala:1393)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.dotty$tools$backend$jvm$BCodeBodyBuilder$PlainBodyBuilder$$genCond(BCodeBodyBuilder.scala:1311)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoadIf(BCodeBodyBuilder.scala:212)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:299)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genStat(BCodeBodyBuilder.scala:96)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock$$anonfun$1(BCodeBodyBuilder.scala:895)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:333)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock(BCodeBodyBuilder.scala:895)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:417)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLabeled(BCodeBodyBuilder.scala:528)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:302)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genReturn(BCodeBodyBuilder.scala:570)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:305)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:416)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock(BCodeBodyBuilder.scala:896)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:417)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoadIf(BCodeBodyBuilder.scala:220)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:299)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genStat(BCodeBodyBuilder.scala:96)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock$$anonfun$1(BCodeBodyBuilder.scala:895)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:333)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock(BCodeBodyBuilder.scala:895)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:417)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLabeled(BCodeBodyBuilder.scala:528)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:302)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genStat(BCodeBodyBuilder.scala:96)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock$$anonfun$1(BCodeBodyBuilder.scala:895)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:333)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genBlock(BCodeBodyBuilder.scala:895)
	at dotty.tools.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:417)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.emitNormalMethodBody$1(BCodeSkelBuilder.scala:765)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genDefDef(BCodeSkelBuilder.scala:800)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:608)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen$$anonfun$1(BCodeSkelBuilder.scala:614)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.immutable.List.foreach(List.scala:333)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.gen(BCodeSkelBuilder.scala:614)
	at dotty.tools.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder.genPlainClass(BCodeSkelBuilder.scala:229)
	at dotty.tools.backend.jvm.GenBCodePipeline$Worker1.visit(GenBCode.scala:229)
	at dotty.tools.backend.jvm.GenBCodePipeline$Worker1.run(GenBCode.scala:194)
	at dotty.tools.backend.jvm.GenBCodePipeline.buildAndSendToDisk(GenBCode.scala:529)
	at dotty.tools.backend.jvm.GenBCodePipeline.run(GenBCode.scala:495)
	at dotty.tools.backend.jvm.GenBCode.run(GenBCode.scala:60)
	at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:303)
	at scala.collection.immutable.List.map(List.scala:246)
	at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:304)
	at dotty.tools.backend.jvm.GenBCode.runOn(GenBCode.scala:64)
	at dotty.tools.dotc.Run.runPhases$4$$anonfun$4(Run.scala:205)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
	at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
	at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
	at dotty.tools.dotc.Run.runPhases$5(Run.scala:216)
	at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:224)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.scala:18)
	at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:67)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:231)
	at dotty.tools.dotc.Run.compileUnits(Run.scala:172)
	at dotty.tools.repl.ReplCompiler.runCompilationUnit(ReplCompiler.scala:152)
	at dotty.tools.repl.ReplCompiler.compile(ReplCompiler.scala:162)
	at dotty.tools.repl.ReplDriver.compile(ReplDriver.scala:249)
	at dotty.tools.repl.ReplDriver.interpret(ReplDriver.scala:212)
	at dotty.tools.repl.ReplDriver.loop$1(ReplDriver.scala:146)
	at dotty.tools.repl.ReplDriver.runUntilQuit$$anonfun$1(ReplDriver.scala:149)
	at dotty.tools.repl.ReplDriver.withRedirectedOutput(ReplDriver.scala:168)
	at dotty.tools.repl.ReplDriver.runUntilQuit(ReplDriver.scala:149)
	at dotty.tools.repl.ReplDriver.tryRunning(ReplDriver.scala:115)
	at dotty.tools.repl.Main$.main(Main.scala:6)
	at dotty.tools.repl.Main.main(Main.scala)

Expectation

No crash & no compile error

@dwijnand dwijnand changed the title Compiler crash Compiler crash using export instead of import Dec 20, 2021
@SethTisue
Copy link
Member

SethTisue commented Feb 9, 2022

@dwijnand and I were able to minimize this further:

object MyApi:
  enum MyEnum:
    case A
object Test:
  export MyApi.MyEnum
  import MyEnum.A
  def foo: Any = A

an alternative form, not sure if more or less minimized:

object MyApi:
  enum MyEnum:
    case A
object Test:
  export MyApi.MyEnum
object Test2:
  import Test.MyEnum.A
  def foo: Any = A

@SethTisue
Copy link
Member

SethTisue commented Feb 9, 2022

after typer, Test2 looks like

  Test2:
    import Test.MyEnum.A
    def foo: Any = MyApi.MyEnum#A

already the #A seems peculiar, because there is no class A, in the enum desugaring A is a val (val A: MyApi.MyEnum = MyApi.MyEnum.$new(0, "A"))

but maybe it's normal and we're just not used to seeing it

then going into erasure, we have

  Test:
    final def MyEnum: MyApi.MyEnum.type = MyApi.MyEnum
    @Child[(MyApi.MyEnum.A : MyApi.MyEnum)] final type MyEnum = MyApi.MyEnum
  Test2:
    def foo: Any = MyApi.MyEnum#A

and coming out of erasure,

  Test:
    final def MyEnum(): MyApi.MyEnum = MyApi.MyEnum   // forwarder made by `export`; singleton type widened (?!)
  Test2:
    def foo(): Object = ((): MyApi.MyEnum)#A

and ((): MyApi.MyEnum)#A seems very very peculiar and probably wrong, why on earth is erasure inserting Unit here? (unless the () isn't the Unit literal but is some printer phantom?)

when we compile the same code without the export, the compiler doesn't crash, and it also doesn't do the (): MyApi.MyEnum thing so our best theory so far is that erasure is wrong to do that, and then of course thing go even further haywire later in the pipeline.

that's as far Dale and I got with this in our session today.

neither of has any familiarity with the internals of the erasure phase, so I guess the next thing is just to go in either with logging and printlns or a debugger and try and figure where exactly this (): ... thing gets added

@SethTisue SethTisue assigned dwijnand and SethTisue and unassigned bishabosha Feb 9, 2022
@SethTisue
Copy link
Member

SethTisue commented Feb 10, 2022

one thing we've done on the wip branch is separate the code into i13490.lib.scala (contains MyEnum), i13490.api.scala (contains the export), and i13490.use.scala (with the actual use site that fails to compile). that way we can compile them separately and that helps produce more focused debugging output. Within sbt we're doing:

scalac -d out2 i13490.lib.scala
scalac -d out2 i13490.api.scala
// then at this stage add -Vprint:all, or similar
scalac i13490.use.scala

(This only works because Dale tweaked the build so that out2 is on scalac's class path.)

unless the () isn't the Unit literal but is some printer phantom

oops: it's neither.

as we said above, in the enum desugaring A is a val inside the MyEnum companion object. so in ((): MyApi.MyEnum)#A, (): MyApi.MyEnum is the method type of the nilary accessor that returns the MyEnum companion, and then we select A from that. (in user code it wouldn't be valid to do a selection off a method type, since it isn't stable, but we're partway through compilation.)

so we now suspect ((): MyApi.MyEnum)#A is actually right, and we can forget about delving into the mechanics of erasure. at next session we'll return to the actual backend crash and try to work backward from there — what isn't meeting the back end's expectations?

@SethTisue
Copy link
Member

SethTisue commented Feb 11, 2022

We found what seems to be a fix, but we'd still like to understand better how this came about.

val <none> is just how NoSymbol prints, so adding the following NoSymbol check fixes it:

In BCodeBodyBuilder:

     private def fieldOp(field: Symbol, isLoad: Boolean, specificReceiver: Symbol): Unit = {
-      val useSpecificReceiver = specificReceiver != null && !field.isScalaStatic
+      val useSpecificReceiver = specificReceiver != null && specificReceiver != NoSymbol && !field.isScalaStatic

fa42b81 is relevant; it's a port from Scala 2 to 3 and in both versions, in the old code findHostClass had a NoSymbol check but the new code does not

so there's precedent for simply adding the check back in

and perhaps that's the fix we should make, but regardless, we would like to understand where the NoSymbol is coming from, when export is involved, to see if some more fundamental fix is possible.

@SethTisue
Copy link
Member

SethTisue commented Feb 11, 2022

We could go one level up and alter this code (in genStat) to dereference the type proxy:

          // receiverClass is used in the bytecode to access the field. using sym.owner may lead to IllegalAccessError
          val receiverClass = qual.tpe.typeSymbol

or, Dale has an idea of handling it during erasure, that he'll explain or even just PR.

@Kordyjan Kordyjan added this to the 3.1.3 milestone Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment