4

scala.Enumerator.nextName and .nextNameOrNull currently read:

/** The string to use to name the next created value. */
protected var nextName: Iterator[String] = _

private def nextNameOrNull =
  if (nextName != null && nextName.hasNext) nextName.next() else null

nextNameOrNull is subsequently called to get the name to use for the item being created in the Enumeration.

How does this code actually achieve this?

When I copy-paste it into a simple example:

class MyBaseClass extends Serializable {
  /** The string to use to name the next created value. */
  protected var nextName: Iterator[String] = _

  private def nextNameOrNull =
    if (nextName != null && nextName.hasNext) nextName.next() else null

  protected final def Value(): Val = Val(nextNameOrNull)

  case class Val(name:String)
}

object MyObject extends MyBaseClass {
  val myValue = Value

  println("Hello from MyObject, myValue: " + myValue)
}

it prints: Hello from MyObject, myValue: Val(null) instead of the hoped for Val(myValue)

What do I need to add to make it work?

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
Jxtps
  • 654
  • 5
  • 15
  • Summary: the cited Enumeration code doesn't actually achieve this (null is always returned) and reflection is used if you follow the call-chain more deeply. Use reflection (a la Enumeration) or a macro (see link in selected answer) to get the same effect. – Jxtps Apr 01 '15 at 20:45

2 Answers2

4

In Scala JVM, Enumeration uses reflection to get the name of the val to which a Value was assigned to if nextNameOrNull returns null.

In Scala.js, we do not have this luxury (no reflection support). Therefore, the Scala.js compiler special cases scala.Enumeration, so that code that uses it can work.

If you want to implement some method that knows the name of the val it is assigned to, have a look at sbt's project macro. Scala's Enumerations could have been implemented that way starting 2.10, but are older.

Suma
  • 33,181
  • 16
  • 123
  • 191
gzm0
  • 14,752
  • 1
  • 36
  • 64
  • Couldn't you just override `nextName` in your concrete implementation of Enumeration? In Scala.js, this works ([test case](https://github.com/scala-js/scala-js/blob/master/test-suite/src/test/scala/org/scalajs/testsuite/scalalib/EnumerationTest.scala#L79)). – gzm0 Apr 01 '15 at 16:32
  • Sure, you can even assign the iterator directly to it, but according to `2.10` deprecation docs "Names should be specified individually or discovered via reflection" - it's not a good idea (and definetly not a normal way), it seems like they just forgot to remove it (maybe for reverse compatibility). – dk14 Apr 01 '15 at 18:14
  • 1
    you can override `Value() = Val(nextNameOrNull)` (like in OP's example) - you will receive Val(null), or you can also override it with `Val(myIterator.next)` and receive expected value (there is no need for `nextName /nextNameOrNull`). So encapsulation of this class leaves a lot to be desired. – dk14 Apr 01 '15 at 18:23
3

nextNameOrNull isn't not working anymore even for original Scala - as passing a sequence of names to constructor is deprecated and removed. Here is the execution for 2.11.2 using original scala's Enumeration (not the replaced one from scala-js):

scala> object MyObject extends Enumeration {
     |   val MyValue1, MyValue2 = Value
     | 
     |   println("nextName: " + nextName)
     | }
defined object MyObject

scala> MyObject
nextName: null //still null

In 2.10.x nextName used inside one of constructor to specify names explicitly as sequence (which is removed in 2.11.x):

@deprecated("Names should be specified individually or discovered via reflection", "2.10.0")
  def this(initial: Int, names: String*) = {
    this(initial)
    this.nextName = names.iterator
  }
}

Now this constructor is removed and nextName is just a dead code. Scala uses populateNameMap() to provide names for nameOf (if they're not specified:

private def populateNameMap() {
    val fields = getClass.getDeclaredFields
    def isValDef(m: JMethod) = fields exists (fd => fd.getName == m.getName && fd.getType == m.getReturnType)

    // The list of possible Value methods: 0-args which return a conforming type
    val methods = getClass.getMethods filter (m => m.getParameterTypes.isEmpty &&
                                                   classOf[Value].isAssignableFrom(m.getReturnType) &&
                                                   m.getDeclaringClass != classOf[Enumeration] &&
                                                   isValDef(m))
    methods foreach { m =>
      val name = m.getName
      // invoke method to obtain actual `Value` instance
      val value = m.invoke(this).asInstanceOf[Value]
      // verify that outer points to the correct Enumeration: ticket #3616.
      if (value.outerEnum eq thisenum) {
        val id = Int.unbox(classOf[Val] getMethod "id" invoke value)
        nmap += ((id, name))
      }
    }
  }

So it uses reflection by default. You can explicitly specify the name for every value as it's described here.

I think same for ScalaJs, excluding that it has no populateNameMap() method as there is no such kind of reflection for JavaScript - so result for non-explicitly named parameters is:

override def toString() =
  if (name != null) name //only if you did `Value("explicitName")` somwhere inside enumeration
  // Scala.js specific
  else s"<Unknown name for enum field #$i of class ${getClass}>"

But again, nextNameOrNull is dead in both Scala and Scala-Js - it always returns null.

Community
  • 1
  • 1
dk14
  • 22,206
  • 4
  • 51
  • 88