Search code examples
scalascala-idescala-compiler

How do I use this object "erasure" in Scala?


I'm trying to restore a project named Scala-IDE.

One of the scala files is JavaSig.scala:

package org.scalaide.core.internal.compiler

import org.scalaide.core.compiler.IScalaPresentationCompiler.Implicits._

trait JavaSig { pc: ScalaPresentationCompiler =>

  /** Returns the symbol's `JavaSignature`*/
  def javaSigOf(sym: Symbol): JavaSignature = new JavaSignature(sym)

  /*
   * An utility class that allows to extract information from the `symbol`'s java signature.
   * If the passed `symbol` does not need a Java signature, empty values are returned.
   *
   * @example Given the following method declaration:
   * {{{ def foo[T <: Foo, U <: ArrayList[_ >: Class[T]]](u: U, t: T):T  = t }}}
   *
   * The following are the values computed by JavaSignatureConverter:
   * {{{
   *   sig <- <T:LFoo;U:Ljava.util.ArrayList<-Ljava.lang.Class<TT;>;>;>(TU;TT;)TT;
   *   paramsCount <- 2
   *   paramsType <- [[U], [T]]
   *   paramsTypeSig <-  [TU;, TT;]
   *   typeVars <- [T, U]
   *   typeParamsSig <- [T:LFoo;, U:Ljava.util.ArrayList<-Ljava.lang.Class<TT;>;>;]
   *   typeParamsBounds <- [[LFoo;], [Ljava.util.ArrayList<-Ljava.lang.Class<TT;>;>;]]
   *   typeParamsBoundsReadable <- [[Foo], [java.util.ArrayList<? super java.lang.Class<T>>]]
   *   returnTypeSig <- TT;
   *   returnType <- T
   *   exceptionTypes <- []
   * }}}
   *
   * @example Given the following method declaration:
   * {{{ def foo[U <: List[Array[String]]](u: U, s: Int):U  = u }}}
   *
   * The following are the values computed by JavaSignatureConverter:
   * {{{
   *   sig <- <U:Lscala.collection.immutable.List<[Ljava.lang.String;>;>(TU;I)TU;
   *   paramsCount <- 2
   *   paramsType <- [[U], [i, n, t]]
   *   paramsTypeSig <- [TU;, I]
   *   typeVars <- [U]
   *   typeParamsSig <- [U:Lscala.collection.immutable.List<[Ljava.lang.String;>;]
   *   typeParamsBounds <- [[Lscala.collection.immutable.List<[Ljava.lang.String;>;]]
   *   typeParamsBoundsReadable <- [[scala.collection.immutable.List<java.lang.String[]>]]
   *   returnTypeSig <- TU;
   *   returnType <- U
   *   expceptionTypes <- []
   * }}}
   */
  class JavaSignature(symbol: Symbol) {
    import org.eclipse.jdt.core.Signature

    // see scala/scala commit e5ea3ab
    private val markClassUsed: Symbol => Unit = _ => ()

    private lazy val sig: Option[String] = {
      // make sure to execute this call in the presentation compiler's thread
      pc.asyncExec {
        def needsJavaSig: Boolean = {
          // there is no need to generate the generic type information for local symbols
          val throwsArgs = symbol.annotations flatMap ThrownException.unapply
          !symbol.isLocalToBlock && erasure.needsJavaSig(symbol, symbol.info, throwsArgs)
        }

        if (needsJavaSig) {
          // it's *really* important we ran pc.atPhase so that symbol's type is updated! (atPhase does side-effects on the type!)
          for (signature <- erasure.javaSig(symbol, pc.enteringPhase(pc.currentRun.erasurePhase)(symbol.info), markClassUsed))
            yield signature.replace("/", ".")
        } else None
      }.getOrElse(None)()
    }

    def isDefined: Boolean = sig.isDefined

    def paramsCount: Int =
      sig.map(Signature.getParameterCount).getOrElse(0)

    def paramsType: Array[Array[Char]] =
      paramsTypeSig.map(p => Signature.toCharArray(p.toArray))

    def paramsTypeSig: Array[String] =
      sig.map(Signature.getParameterTypes).getOrElse(Array.empty)

    def typeVars: Array[String] =
      typeParamsSig.map(Signature.getTypeVariable)

    def typeParamsSig: Array[String] =
      sig.map(Signature.getTypeParameters).getOrElse(Array.empty)

    def typeParamsBounds: Array[Array[String]] =
      typeParamsSig.map(Signature.getTypeParameterBounds)

    def typeParamsBoundsReadable: Array[Array[Array[Char]]] =
      typeParamsBounds.map(_.map(s => Signature.toCharArray(s.toArray)))

    def returnTypeSig: Option[String] =
      sig.map(Signature.getReturnType)

    def returnType: Option[String] =
      returnTypeSig.map(r => Signature.toCharArray(r.toArray).mkString)

    def exceptionTypes: Array[Array[Char]] =
      sig.map(Signature.getThrownExceptionTypes).getOrElse(Array.empty[String]).map(_.toCharArray)
  }
}

It uses an object of type scala.tools.nsc.transform.Erasure named erasure which appears to be defined in scala.tools.nsc.Global. These are both from scala-compiler-2.12.19.jar.

When I try to compile JavaSig.scala, I have the following error:

[ERROR] [Error] C:/Users/nicol/git/scala-ide/org.scala-ide.sdt.core/src/org/scalaide/core/internal/compiler/JavaSig.scala:61: value needsJavaSig is not a member of object JavaSig.this.erasure

When I try to use a fully qualified name on erasure like scala.tools.nsc.Global.erasure instead of just erasure, I have the following error:

[ERROR] [Error] C:/Users/nicol/git/scala-ide/org.scala-ide.sdt.core/src/org/scalaide/core/internal/compiler/JavaSig.scala:61: value erasure is not a member of object scala.tools.nsc.Global

What is actually missing so that I can use this object erasure ?

Please note I've just slightly adapted the code of JavaSig. Now this method from Erasure takes 3 parameters instead of 2, the Type being added:

private def needsJavaSig(sym: Symbol, tp: Type, throwsArgs: List[Type]) = !settings.Ynogenericsig && {
  def needs(tp: Type) = NeedsSigCollector(sym.isClassConstructor).collect(tp)
  needs(tp) || throwsArgs.exists(needs)
}

So I adapted based on the existing code from Erasure :

val throwsArgs = symbol.annotations flatMap ThrownException.unapply
!symbol.isLocalToBlock && erasure.needsJavaSig(symbol, symbol.info, throwsArgs)

EDIT:

  • pc is of type ScalaPresentationCompiler which extends scala.tools.nsc.Global
  • erasure is defined in scala.tools.nsc.Global

=> erasure should be available inside pc.asyncExec { ... } without prefix/import


Solution

  • The error message was misleading. The real reason was that the method needsJavaSig from Erasure was private.

    A temporary reflection hack is still possible to wait for a better solution such as a proper API replacement:

    val method: Method = classOf[Erasure].getDeclaredMethod("needsJavaSig", classOf[Symbol], classOf[Type], classOf[List[Annotation]])
    method.setAccessible(true)
    val javaSigNeeded: Boolean = method.invoke(erasure, symbol, symbol.info, throwsArgs).asInstanceOf[Boolean]