Skip to content

Cannot access enum from dependency at runtime #7991

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

Closed
nicolasstucki opened this issue Jan 15, 2020 · 7 comments · Fixed by #8008
Closed

Cannot access enum from dependency at runtime #7991

nicolasstucki opened this issue Jan 15, 2020 · 7 comments · Fixed by #8008

Comments

@nicolasstucki
Copy link
Contributor

minimized code

Num.scala

enum Num { case One }

Test.scala

object Test extends App {
  Num.One
}
$ mkdir out2
$ sbt
> dotc -d out2 Num.scala
> dotc -d out2 -classpath out2 Test.scala
> dotr -classpath out2 Test
Exception in thread "main" java.lang.NoSuchFieldError: One
        at Test$.<init>(Test_2.scala:2)
        at Test$.<clinit>(Test_2.scala)
        at Test.main(Test_2.scala)

expectation

Should run

@bishabosha
Copy link
Member

I would say this is duplication but its the most minimal

@bishabosha bishabosha mentioned this issue Jan 15, 2020
22 tasks
@odersky
Copy link
Contributor

odersky commented Jan 15, 2020

Here's the output of Num.scala after typer:

result of Num_1.scala after typer:
package <empty> {
  @scala.annotation.internal.Child[(Num.One : Num)]() sealed abstract class Num(
    )
   extends Object(), Enum {
    import Num.One
  }
  final lazy module val Num: Num$ = new Num$()
  final module class Num$() extends AnyRef(), _root_.scala.Serializable { 
    this: Num.type =>
    def values: Array[Num] = 
      Num.$values.values.toArray[Num](
        scala.reflect.ClassTag.apply[Num](classOf[Num])
      )
    private[this] val $values: scala.runtime.EnumValues[Num] = 
      new scala.runtime.EnumValues[Num]()
    def valueOf($name: String): Num = 
      try Num.$values.fromName.apply($name) catch 
        {
          case ex$ @ _:NoSuchElementException => 
            throw new IllegalArgumentException("key not found: ".concat($name))
        }
    private[this] def $new(_$ordinal: Int, $name: String): Num = 
      {
        final class $anon() extends Num() {
          def $ordinal: Int = _$ordinal
          override def toString: String = $name
          Num.$values.register(this)
        }
        new $anon():Num
      }
    case <static> val One: Num = Num.$new(0, "One")
  }
}

The issue is that Num is declared <static>, which is needed for Java interop. The Scala backend does not recognize that and tries to access Num via

Num$.MODULE$:LNum$;

The backend should honor <static> flags and translate them correctly. Alternatively, we need to install a forwarder for Num in Num$.MODULE$.

@odersky
Copy link
Contributor

odersky commented Jan 15, 2020

Actually, it works fine if both files are compiled together. So maybe it's not the backend's fault.

@odersky
Copy link
Contributor

odersky commented Jan 15, 2020

Or maybe it is? The produced trees with types are exactly the same for Test_2 when compiled separately and when compiled alone.

More concretely, I ran:

sc Num_1.scala Test_2.scala -Xprint:moveStatic -Xprint-types
sc Test_2.scala -Xprint:moveStatic -Xprint-types

The two outputs are exactly the same for Test_2. But the backend translates the call differently on separate compilation. I am mystified.

@odersky
Copy link
Contributor

odersky commented Jan 15, 2020

Somebody else should take this over please. It looks like this will require some effort to figure out where it goes wrong.

@liufengyun liufengyun self-assigned this Jan 15, 2020
@liufengyun
Copy link
Contributor

If we compile the two files together, the class file generated for Test:

#37 = NameAndType        #35:#36        // One:LNum;
#38 = Fieldref           #31.#37        // Num$.One:LNum;

If we compile the two files separately, the class file generated for Test:

#39 = NameAndType        #37:#38        // One:LNum;
#40 = Fieldref           #36.#39        // Num.One:LNum;

The second is incorrect, as One is defined on Num$, not Num.

@liufengyun
Copy link
Contributor

When we generate code for field access, somehow internalName(Num$) returns Num when they compiled separately. I think it's related to unpickler.

    final def internalName(sym: Symbol): String = {
      // For each java class, the scala compiler creates a class and a module (thus a module class).
      // If the `sym` is a java module class, we use the java class instead. This ensures that we
      // register the class (instead of the module class) in innerClassBufferASM.
      // The two symbols have the same name, so the resulting internalName is the same.
      val classSym = if (sym.isJavaDefined && sym.isModuleClass) sym.linkedClassOfClass else sym
      getClassBTypeAndRegisterInnerClass(classSym).internalName
    }

liufengyun added a commit to dotty-staging/dotty that referenced this issue Jan 15, 2020
Top-level Dotty Enum classes have the flag JAVA_ACC_ENUM.
We cannot tell from the flag whether a class is JavaDefined or not.
The `moduleRoot` already has the flag `JavaDefined` set, it suffices
to test the flag.
anatoliykmetyuk added a commit that referenced this issue Jan 17, 2020
Fix #7991: don't set JavaDefined for Dotty Enum module class
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants