/* NSC -- new Scala compiler
 * Copyright 2005-2012 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala
package tools.nsc
package backend.jvm

import scala.tools.asm
import scala.collection.{ immutable, mutable }

/*
 *  Utilities to mediate between types as represented in Scala ASTs and ASM trees.
 *
 *  @author  Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded
 *  @version 1.0
 *
 */
abstract class BCodeTypes extends BCodeIdiomatic {

  import global._

  // when compiling the Scala library, some assertions don't hold (e.g., scala.Boolean has null superClass although it's not an interface)
  val isCompilingStdLib = !(settings.sourcepath.isDefault)

  val srBoxedUnit  = brefType("scala/runtime/BoxedUnit")

  // special names
  var StringReference             : BType = null
  var ThrowableReference          : BType = null
  var jlCloneableReference        : BType = null // java/lang/Cloneable
  var jlNPEReference              : BType = null // java/lang/NullPointerException
  var jioSerializableReference    : BType = null // java/io/Serializable
  var scalaSerializableReference  : BType = null // scala/Serializable
  var classCastExceptionReference : BType = null // java/lang/ClassCastException

  /* A map from scala primitive type-symbols to BTypes */
  var primitiveTypeMap: Map[Symbol, BType] = null
  /* A map from scala type-symbols for Nothing and Null to (runtime version) BTypes */
  var phantomTypeMap:   Map[Symbol, BType] = null
  /* Maps the method symbol for a box method to the boxed type of the result.
   *  For example, the method symbol for `Byte.box()`) is mapped to the BType `Ljava/lang/Integer;`. */
  var boxResultType:    Map[Symbol, BType] = null
  /* Maps the method symbol for an unbox method to the primitive type of the result.
   *  For example, the method symbol for `Byte.unbox()`) is mapped to the BType BYTE. */
  var unboxResultType:  Map[Symbol, BType] = null

  var hashMethodSym: Symbol = null // scala.runtime.ScalaRunTime.hash

  var AndroidParcelableInterface: Symbol = null
  var AndroidCreatorClass       : Symbol = null // this is an inner class, use asmType() to get hold of its BType while tracking in innerClassBufferASM

  var BeanInfoAttr: Symbol = null

  /* The Object => String overload. */
  var String_valueOf: Symbol = null

  var ArrayInterfaces: Set[Tracked] = null

  // scala.FunctionX and scala.runtim.AbstractFunctionX
  val FunctionReference                 = new Array[Tracked](definitions.MaxFunctionArity + 1)
  val AbstractFunctionReference         = new Array[Tracked](definitions.MaxFunctionArity + 1)
  val abstractFunctionArityMap = mutable.Map.empty[BType, Int]

  var PartialFunctionReference:         BType = null // scala.PartialFunction
  var AbstractPartialFunctionReference: BType = null // scala.runtime.AbstractPartialFunction

  var BoxesRunTime: BType = null

  /*
   * must-single-thread
   */
  def initBCodeTypes() {
    import definitions._

    primitiveTypeMap =
      Map(
        UnitClass     -> UNIT,
        BooleanClass  -> BOOL,
        CharClass     -> CHAR,
        ByteClass     -> BYTE,
        ShortClass    -> SHORT,
        IntClass      -> INT,
        LongClass     -> LONG,
        FloatClass    -> FLOAT,
        DoubleClass   -> DOUBLE
      )

    phantomTypeMap =
      Map(
        NothingClass -> RT_NOTHING,
        NullClass    -> RT_NULL,
        NothingClass -> RT_NOTHING, // we map on purpose to RT_NOTHING, getting rid of the distinction compile-time vs. runtime for NullClass.
        NullClass    -> RT_NULL     // ditto.
      )

    boxResultType =
      for((csym, msym) <- currentRun.runDefinitions.boxMethod)
      yield (msym -> classLiteral(primitiveTypeMap(csym)))

    unboxResultType =
      for((csym, msym) <- currentRun.runDefinitions.unboxMethod)
      yield (msym -> primitiveTypeMap(csym))

    // boxed classes are looked up in the `exemplars` map by jvmWiseLUB().
    // Other than that, they aren't needed there (e.g., `isSubtypeOf()` special-cases boxed classes, similarly for others).
    val boxedClasses = List(BoxedBooleanClass, BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass)
    for(csym <- boxedClasses) {
      val key = brefType(csym.javaBinaryName.toTypeName)
      val tr  = buildExemplar(key, csym)
      symExemplars.put(csym, tr)
      exemplars.put(tr.c, tr)
    }

    // reversePrimitiveMap = (primitiveTypeMap map { case (s, pt) => (s.tpe, pt) } map (_.swap)).toMap

    hashMethodSym = getMember(ScalaRunTimeModule, nme.hash_)

    // TODO avoiding going through through missingHook for every line in the REPL: https://github.com/scala/scala/commit/8d962ed4ddd310cc784121c426a2e3f56a112540
    AndroidParcelableInterface = rootMirror.getClassIfDefined("android.os.Parcelable")
    AndroidCreatorClass        = rootMirror.getClassIfDefined("android.os.Parcelable$Creator")

    // the following couldn't be an eager vals in Phase constructors:
    // that might cause cycles before Global has finished initialization.
    BeanInfoAttr = rootMirror.getRequiredClass("scala.beans.BeanInfo")

    String_valueOf = {
      getMember(StringModule, nme.valueOf) filter (sym =>
        sym.info.paramTypes match {
          case List(pt) => pt.typeSymbol == ObjectClass
          case _        => false
        }
      )
    }

    exemplar(JavaCloneableClass)
    exemplar(JavaSerializableClass)
    exemplar(SerializableClass)

    StringReference             = exemplar(StringClass).c
    StringBuilderReference      = exemplar(StringBuilderClass).c
    ThrowableReference          = exemplar(ThrowableClass).c
    jlCloneableReference        = exemplar(JavaCloneableClass).c
    jlNPEReference              = exemplar(NullPointerExceptionClass).c
    jioSerializableReference    = exemplar(JavaSerializableClass).c
    scalaSerializableReference  = exemplar(SerializableClass).c
    classCastExceptionReference = exemplar(ClassCastExceptionClass).c

    /*
     *  The bytecode emitter special-cases String concatenation, in that three methods of `JCodeMethodN`
     *  ( `genStartConcat()` , `genStringConcat()` , and `genEndConcat()` )
     *  don't obtain the method descriptor of the callee via `asmMethodType()` (as normally done)
     *  but directly emit callsites on StringBuilder using literal constant for method descriptors.
     *  In order to make sure those method descriptors are available as BTypes, they are initialized here.
     */
    BType.getMethodType("()V")                   // necessary for JCodeMethodN.genStartConcat
    BType.getMethodType("()Ljava/lang/String;")  // necessary for JCodeMethodN.genEndConcat

    PartialFunctionReference    = exemplar(PartialFunctionClass).c
    for(idx <- 0 to definitions.MaxFunctionArity) {
      FunctionReference(idx)           = exemplar(FunctionClass(idx))
      AbstractFunctionReference(idx)   = exemplar(AbstractFunctionClass(idx))
      abstractFunctionArityMap        += (AbstractFunctionReference(idx).c -> idx)
      AbstractPartialFunctionReference = exemplar(AbstractPartialFunctionClass).c
    }

    // later a few analyses (e.g. refreshInnerClasses) will look up BTypes based on descriptors in instructions
    // we make sure those BTypes can be found via lookup as opposed to creating them on the fly.
    BoxesRunTime = brefType("scala/runtime/BoxesRunTime")
    asmBoxTo.values   foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) }
    asmUnboxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) }

  }

  /*
   * must-single-thread
   */
  def clearBCodeTypes() {
    symExemplars.clear()
    exemplars.clear()
  }

  val PublicStatic      = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC
  val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL

  val strMODULE_INSTANCE_FIELD = nme.MODULE_INSTANCE_FIELD.toString

  // ------------------------------------------------
  // accessory maps tracking the isInterface, innerClasses, superClass, and supportedInterfaces relations,
  // allowing answering `conforms()` without resorting to typer.
  // ------------------------------------------------

  val exemplars       = new java.util.concurrent.ConcurrentHashMap[BType,  Tracked]
  val symExemplars    = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked]

  /*
   *  Typically, a question about a BType can be answered only by using the BType as lookup key in one or more maps.
   *  A `Tracked` object saves time by holding together information required to answer those questions:
   *
   *    - `sc`     denotes the bytecode-level superclass if any, null otherwise
   *
   *    - `ifaces` denotes the interfaces explicitly declared.
   *               Not included are those transitively supported, but the utility method `allLeafIfaces()` can be used for that.
   *
   *    - `innersChain` denotes the containing classes for a non-package-level class `c`, null otherwise.
   *               Note: the optimizer may inline anonymous closures, thus eliding those inner classes
   *               (no physical class file is emitted for elided classes).
   *               Before committing `innersChain` to bytecode, cross-check with the list of elided classes (SI-6546).
   *
   *  All methods of this class can-multi-thread
   */
  case class Tracked(c: BType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) {

    // not a case-field because we initialize it only for JVM classes we emit.
    private var _directMemberClasses: List[BType] = null

    def directMemberClasses: List[BType] = {
      assert(_directMemberClasses != null, s"getter directMemberClasses() invoked too early for $c")
      _directMemberClasses
    }

    def directMemberClasses_=(bs: List[BType]) {
      if (_directMemberClasses != null) {
        // TODO we enter here when both mirror class and plain class are emitted for the same ModuleClassSymbol.
        assert(_directMemberClasses == bs.sortBy(_.off))
      }
      _directMemberClasses = bs.sortBy(_.off)
    }

    /* `isCompilingStdLib` saves the day when compiling:
     *     (1) scala.Nothing (the test `c.isNonSpecial` fails for it)
     *     (2) scala.Boolean (it has null superClass and is not an interface)
     */
    assert(c.isNonSpecial || isCompilingStdLib /*(1)*/, s"non well-formed plain-type: $this")
    assert(
        if (sc == null) { (c == ObjectReference) || isInterface || isCompilingStdLib /*(2)*/ }
        else            { (c != ObjectReference) && !sc.isInterface }
      , "non well-formed plain-type: " + this
    )
    assert(ifaces.forall(i => i.c.isNonSpecial && i.isInterface), s"non well-formed plain-type: $this")

    import asm.Opcodes._
    def hasFlags(mask: Int) = (flags & mask) != 0
    def isInterface  = hasFlags(ACC_INTERFACE)
    def isFinal      = hasFlags(ACC_FINAL)
    def isInnerClass = { innersChain != null }
    def isLambda = {
      // ie isLCC || isTraditionalClosureClass
      isFinal && (c.getSimpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c)
    }

    /* can-multi-thread */
    def superClasses: List[Tracked] = {
      if (sc == null) Nil else sc :: sc.superClasses
    }

    /* can-multi-thread */
    def isSubtypeOf(other: BType): Boolean = {
      assert(other.isNonSpecial, "so called special cases have to be handled in BCodeTypes.conforms()")

      if (c == other) return true;

      val otherIsIface = exemplars.get(other).isInterface

      if (this.isInterface) {
        if (other == ObjectReference) return true;
        if (!otherIsIface) return false;
      }
      else {
        if (sc != null && sc.isSubtypeOf(other)) return true;
        if (!otherIsIface) return false;
      }

      var idx = 0
      while (idx < ifaces.length) {
        if (ifaces(idx).isSubtypeOf(other)) return true;
        idx += 1
      }

      false
    }

    /*
     *  The `ifaces` field lists only those interfaces declared by `c`
     *  From the set of all supported interfaces, this method discards those which are supertypes of others in the set.
     */
    def allLeafIfaces: Set[Tracked] = {
      if (sc == null) { ifaces.toSet }
      else { minimizeInterfaces(ifaces.toSet ++ sc.allLeafIfaces) }
    }

    /*
     *  This type may not support in its entirety the interface given by the argument, however it may support some of its super-interfaces.
     *  We visualize each such supported subset of the argument's functionality as a "branch". This method returns all such branches.
     *
     *  In other words, let Ri be a branch supported by `ib`,
     *  this method returns all Ri such that this <:< Ri, where each Ri is maximally deep.
     */
    def supportedBranches(ib: Tracked): Set[Tracked] = {
      assert(ib.isInterface, s"Non-interface argument: $ib")

      val result: Set[Tracked] =
        if (this.isSubtypeOf(ib.c)) { Set(ib) }
        else { ib.ifaces.toSet[Tracked].flatMap( bi => supportedBranches(bi) ) }

      checkAllInterfaces(result)

      result
    }

    override def toString = { c.toString }

  }

  /* must-single-thread */
  final def isDeprecated(sym: Symbol): Boolean = { sym.annotations exists (_ matches definitions.DeprecatedAttr) }

  /* must-single-thread */
  final def hasInternalName(sym: Symbol) = { sym.isClass || (sym.isModule && !sym.isMethod) }

  /* must-single-thread */
  def getSuperInterfaces(csym: Symbol): List[Symbol] = {

    // Additional interface parents based on annotations and other cues
    def newParentForAttr(ann: AnnotationInfo): Symbol = ann.symbol match {
      case definitions.RemoteAttr => definitions.RemoteInterfaceClass
      case _                      => NoSymbol
    }

    /* Drop redundant interfaces (which are implemented by some other parent) from the immediate parents.
     *  In other words, no two interfaces in the result are related by subtyping.
     *  This method works on Symbols, a similar one (not duplicate) works on Tracked instances.
     */
    def minimizeInterfaces(lstIfaces: List[Symbol]): List[Symbol] = {
      var rest   = lstIfaces
      var leaves = List.empty[Symbol]
      while (!rest.isEmpty) {
        val candidate = rest.head
        val nonLeaf = leaves exists { lsym => lsym isSubClass candidate }
        if (!nonLeaf) {
          leaves = candidate :: (leaves filterNot { lsym => candidate isSubClass lsym })
        }
        rest = rest.tail
      }

      leaves
    }

    val superInterfaces0: List[Symbol] = csym.mixinClasses
    val superInterfaces = existingSymbols(superInterfaces0 ++ csym.annotations.map(newParentForAttr)).distinct

    assert(!superInterfaces.contains(NoSymbol), s"found NoSymbol among: ${superInterfaces.mkString}")
    assert(superInterfaces.forall(s => s.isInterface || s.isTrait), s"found non-interface among: ${superInterfaces.mkString}")

    minimizeInterfaces(superInterfaces)
  }

  /*
   * Records the superClass and supportedInterfaces relations,
   * so that afterwards queries can be answered without resorting to typer.
   * This method does not add to `innerClassBufferASM`, use `internalName()` or `asmType()` or `toTypeKind()` for that.
   * On the other hand, this method does record the inner-class status of the argument, via `buildExemplar()`.
   *
   * must-single-thread
   */
  final def exemplar(csym0: Symbol): Tracked = {
    assert(csym0 != NoSymbol, "NoSymbol can't be tracked")

    val csym = {
      if (csym0.isJavaDefined && csym0.isModuleClass) csym0.linkedClassOfClass
      else if (csym0.isModule) csym0.moduleClass
      else csym0 // we track only module-classes and plain-classes
    }

    assert(!primitiveTypeMap.contains(csym) || isCompilingStdLib, s"primitive types not tracked here: ${csym.fullName}")
    assert(!phantomTypeMap.contains(csym), s"phantom types not tracked here: ${csym.fullName}")

    val opt = symExemplars.get(csym)
    if (opt != null) {
      return opt
    }

    val key = brefType(csym.javaBinaryName.toTypeName)
    assert(key.isNonSpecial || isCompilingStdLib, s"Not a class to track: ${csym.fullName}")

    // TODO accomodate the fix for SI-5031 of https://github.com/scala/scala/commit/0527b2549bcada2fda2201daa630369b377d0877
    // TODO Weaken this assertion? buildExemplar() needs to be updated, too. In the meantime, pos/t5031_3 has been moved to test/disabled/pos.
    val whatWasInExemplars = exemplars.get(key)
    assert(whatWasInExemplars == null, "Maps `symExemplars` and `exemplars` got out of synch.")
    val tr = buildExemplar(key, csym)
    symExemplars.put(csym, tr)
    if (csym != csym0) { symExemplars.put(csym0, tr) }
    exemplars.put(tr.c, tr) // tr.c is the hash-consed, internalized, canonical representative for csym's key.
    tr
  }

  val EMPTY_TRACKED_ARRAY  = Array.empty[Tracked]

  /*
   * must-single-thread
   */
  private def buildExemplar(key: BType, csym: Symbol): Tracked = {
    val sc =
     if (csym.isImplClass) definitions.ObjectClass
     else csym.superClass
    assert(
      if (csym == definitions.ObjectClass)
        sc == NoSymbol
      else if (csym.isInterface)
        sc == definitions.ObjectClass
      else
        ((sc != NoSymbol) && !sc.isInterface) || isCompilingStdLib,
      "superClass out of order"
    )
    val ifaces    = getSuperInterfaces(csym) map exemplar;
    val ifacesArr =
     if (ifaces.isEmpty) EMPTY_TRACKED_ARRAY
     else {
      val arr = new Array[Tracked](ifaces.size)
      ifaces.copyToArray(arr)
      arr
     }

    val flags = mkFlags(
      javaFlags(csym),
      if (isDeprecated(csym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo access flag
    )

    val tsc = if (sc == NoSymbol) null else exemplar(sc)

    val innersChain = saveInnerClassesFor(csym, key)

    Tracked(key, flags, tsc, ifacesArr, innersChain)
  }

  // ---------------- utilities around interfaces represented by Tracked instances. ----------------

  /*  Drop redundant interfaces (those which are implemented by some other).
   *  In other words, no two interfaces in the result are related by subtyping.
   *  This method works on Tracked elements, a similar one (not duplicate) works on Symbols.
   */
  def minimizeInterfaces(lstIfaces: Set[Tracked]): Set[Tracked] = {
    checkAllInterfaces(lstIfaces)
    var rest   = lstIfaces.toList
    var leaves = List.empty[Tracked]
    while (!rest.isEmpty) {
      val candidate = rest.head
      val nonLeaf = leaves exists { leaf => leaf.isSubtypeOf(candidate.c) }
      if (!nonLeaf) {
        leaves = candidate :: (leaves filterNot { leaf => candidate.isSubtypeOf(leaf.c) })
      }
      rest = rest.tail
    }

    leaves.toSet
  }

  def allInterfaces(is: Iterable[Tracked]): Boolean = { is forall { i => i.isInterface } }
  def nonInterfaces(is: Iterable[Tracked]): Iterable[Tracked] = { is filterNot { i => i.isInterface } }

  def checkAllInterfaces(ifaces: Iterable[Tracked]) {
    assert(allInterfaces(ifaces), s"Non-interfaces: ${nonInterfaces(ifaces).mkString}")
  }

  /*
   * Subtype check `a <:< b` on BTypes that takes into account the JVM built-in numeric promotions (e.g. BYTE to INT).
   * Its operation can be visualized more easily in terms of the Java bytecode type hierarchy.
   * This method used to be called, in the ICode world, TypeKind.<:<()
   *
   * can-multi-thread
   */
  final def conforms(a: BType, b: BType): Boolean = {
    if (a.isArray) { // may be null
      /* Array subtyping is covariant here, as in Java bytecode. Also necessary for Java interop. */
      if ((b == jlCloneableReference)     ||
          (b == jioSerializableReference) ||
          (b == AnyRefReference))    { true  }
      else if (b.isArray)            { conforms(a.getComponentType, b.getComponentType) }
      else                           { false }
    }
    else if (a.isBoxed) { // may be null
      if (b.isBoxed)                 { a == b }
      else if (b == AnyRefReference) { true   }
      else if (!(b.hasObjectSort))   { false  }
      else                           { exemplars.get(a).isSubtypeOf(b) } // e.g., java/lang/Double conforms to java/lang/Number
    }
    else if (a.isNullType) { // known to be null
      if (b.isNothingType)      { false }
      else if (b.isValueType)   { false }
      else                      { true  }
    }
    else if (a.isNothingType) { // known to be Nothing
      true
    }
    else if (a.isUnitType) {
      b.isUnitType
    }
    else if (a.hasObjectSort) { // may be null
      if (a.isNothingType)      { true  }
      else if (b.hasObjectSort) { exemplars.get(a).isSubtypeOf(b) }
      else if (b.isArray)       { a.isNullType } // documentation only, because `if(a.isNullType)` (above) covers this case already.
      else                      { false }
    }
    else {

      def msg = s"(a: $a, b: $b)"

      assert(a.isNonUnitValueType, s"a isn't a non-Unit value type. $msg")
      assert(b.isValueType, s"b isn't a value type. $msg")

      (a eq b) || (a match {
        case BOOL | BYTE | SHORT | CHAR => b == INT || b == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt().
        case _                          => a == b
      })
    }
  }

  /* The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative values of Byte and Short. See ticket #2087.
   *
   * can-multi-thread
   */
  def maxValueType(a: BType, other: BType): BType = {
    assert(a.isValueType, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).")

    def uncomparable: Nothing = {
      abort(s"Uncomparable BTypes: $a with $other")
    }

    if (a.isNothingType)      return other;
    if (other.isNothingType)  return a;
    if (a == other)           return a;

    a match {

      case UNIT => uncomparable
      case BOOL => uncomparable

      case BYTE =>
        if (other == CHAR)             INT
        else if (other.isNumericType)  other
        else                           uncomparable

      case SHORT =>
        other match {
          case BYTE                          => SHORT
          case CHAR                          => INT
          case INT  | LONG  | FLOAT | DOUBLE => other
          case _                             => uncomparable
        }

      case CHAR =>
        other match {
          case BYTE | SHORT                 => INT
          case INT  | LONG | FLOAT | DOUBLE => other
          case _                            => uncomparable
        }

      case INT =>
        other match {
          case BYTE | SHORT | CHAR   => INT
          case LONG | FLOAT | DOUBLE => other
          case _                     => uncomparable
        }

      case LONG =>
        if (other.isIntegralType)   LONG
        else if (other.isRealType)  DOUBLE
        else                        uncomparable

      case FLOAT =>
        if (other == DOUBLE)           DOUBLE
        else if (other.isNumericType)  FLOAT
        else                           uncomparable

      case DOUBLE =>
        if (other.isNumericType)  DOUBLE
        else                      uncomparable

      case _ => uncomparable
    }
  }

  /* Takes promotions of numeric primitives into account.
   *
   *  can-multi-thread
   */
  final def maxType(a: BType, other: BType): BType = {
    if (a.isValueType) { maxValueType(a, other) }
    else {
      if (a.isNothingType)     return other;
      if (other.isNothingType) return a;
      if (a == other)          return a;
       // Approximate `lub`. The common type of two references is always AnyRef.
       // For 'real' least upper bound wrt to subclassing use method 'lub'.
      assert(a.isArray || a.isBoxed || a.hasObjectSort, s"This is not a valuetype and it's not something else, what is it? $a")
      // TODO For some reason, ICode thinks `REFERENCE(...).maxType(BOXED(whatever))` is `uncomparable`. Here, that has maxType AnyRefReference.
      //      BTW, when swapping arguments, ICode says BOXED(whatever).maxType(REFERENCE(...)) == AnyRefReference, so I guess the above was an oversight in REFERENCE.maxType()
      if (other.isRefOrArrayType) { AnyRefReference }
      else                        { abort(s"Uncomparable BTypes: $a with $other") }
    }
  }

  /*
   *  Whether the argument is a subtype of
   *    scala.PartialFunction[-A, +B] extends (A => B)
   *  N.B.: this method returns true for a scala.runtime.AbstractPartialFunction
   *
   *  can-multi-thread
   */
  def isPartialFunctionType(t: BType): Boolean = {
    (t.hasObjectSort) && exemplars.get(t).isSubtypeOf(PartialFunctionReference)
  }

  /*
   *  Whether the argument is a subtype of scala.FunctionX where 0 <= X <= definitions.MaxFunctionArity
   *
   *  can-multi-thread
   */
  def isFunctionType(t: BType): Boolean = {
    if (!t.hasObjectSort) return false
    var idx = 0
    val et: Tracked = exemplars.get(t)
    while (idx <= definitions.MaxFunctionArity) {
      if (et.isSubtypeOf(FunctionReference(idx).c)) {
        return true
      }
      idx += 1
    }
    false
  }

  /*
   * must-single-thread
   */
  def isTopLevelModule(sym: Symbol): Boolean = {
    exitingPickler { sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass }
  }

  /*
   * must-single-thread
   */
  def isStaticModule(sym: Symbol): Boolean = {
    sym.isModuleClass && !sym.isImplClass && !sym.isLifted
  }

  // ---------------------------------------------------------------------
  // ---------------- InnerClasses attribute (JVMS 4.7.6) ----------------
  // ---------------------------------------------------------------------

  val INNER_CLASSES_FLAGS =
    (asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE   | asm.Opcodes.ACC_PROTECTED |
     asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_INTERFACE | asm.Opcodes.ACC_ABSTRACT  | asm.Opcodes.ACC_FINAL)

  /*
   * @param name the internal name of an inner class.
   * @param outerName the internal name of the class to which the inner class belongs.
   *                  May be `null` for non-member inner classes (ie for a Java local class or a Java anonymous class).
   * @param innerName the (simple) name of the inner class inside its enclosing class. It's `null` for anonymous inner classes.
   * @param access the access flags of the inner class as originally declared in the enclosing class.
   */
  case class InnerClassEntry(name: String, outerName: String, innerName: String, access: Int) {
    assert(name != null, "Null isn't good as class name in an InnerClassEntry.")
  }

  /* For given symbol return a symbol corresponding to a class that should be declared as inner class.
   *
   *  For example:
   *  class A {
   *    class B
   *    object C
   *  }
   *
   *  then method will return:
   *    NoSymbol for A,
   *    the same symbol for A.B (corresponding to A$B class), and
   *    A$C$ symbol for A.C.
   *
   * must-single-thread
   */
  def innerClassSymbolFor(s: Symbol): Symbol =
    if (s.isClass) s else if (s.isModule) s.moduleClass else NoSymbol

  /*
   *  Computes the chain of inner-class (over the is-member-of relation) for the given argument.
   *  The resulting chain will be cached in `exemplars`.
   *
   *  The chain thus cached is valid during this compiler run, see in contrast
   *  `innerClassBufferASM` for a cache that is valid only for the class being emitted.
   *
   *  The argument can be any symbol, but given that this method is invoked only from `buildExemplar()`,
   *  in practice it has been vetted to be a class-symbol.
   *
   *  Returns:
   *
   *    - a non-empty array of entries for an inner-class argument.
   *      The array's first element is the outermost top-level class,
   *      the array's last element corresponds to csym.
   *
   *    - null otherwise.
   *
   *  This method does not add to `innerClassBufferASM`, use instead `exemplar()` for that.
   *
   *  must-single-thread
   */
  final def saveInnerClassesFor(csym: Symbol, csymTK: BType): Array[InnerClassEntry] = {

    val ics = innerClassSymbolFor(csym)
    if (ics == NoSymbol) {
      return null
    }
    assert(ics == csym, s"Disagreement between innerClassSymbolFor() and exemplar()'s tracked symbol for the same input: ${csym.fullName}")

    var chain: List[Symbol] = Nil
    var x = ics
    while (x ne NoSymbol) {
      assert(x.isClass, s"not a class symbol: ${x.fullName}")
      val isInner = !x.rawowner.isPackageClass
      if (isInner) {
        chain ::= x
        x = innerClassSymbolFor(x.rawowner)
      } else {
        x = NoSymbol
      }
    }

    // now that we have all of `ics` , `csym` , and soon the inner-classes-chain, it's too tempting not to cache.
    if (chain.isEmpty) { null }
    else {
      val arr = new Array[InnerClassEntry](chain.size)
      (chain map toInnerClassEntry).copyToArray(arr)

      arr
    }
  }

  /*
   * must-single-thread
   */
  private def toInnerClassEntry(innerSym: Symbol): InnerClassEntry = {

    /* The outer name for this inner class. Note that it returns null
     *  when the inner class should not get an index in the constant pool.
     *  That means non-member classes (anonymous). See Section 4.7.5 in the JVMS.
     */
    def outerName(innerSym: Symbol): Name = {
      if (innerSym.originalEnclosingMethod != NoSymbol)
        null
      else {
        val outerName = innerSym.rawowner.javaBinaryName
        if (isTopLevelModule(innerSym.rawowner)) nme.stripModuleSuffix(outerName)
        else outerName
      }
    }

    def innerName(innerSym: Symbol): String = {
      if (innerSym.isAnonymousClass || innerSym.isAnonymousFunction)
        null
      else
        innerSym.rawname + innerSym.moduleSuffix
    }

    val flagsWithFinal: Int = mkFlags(
      if (innerSym.rawowner.hasModuleFlag) asm.Opcodes.ACC_STATIC else 0,
      javaFlags(innerSym),
      if (isDeprecated(innerSym)) asm.Opcodes.ACC_DEPRECATED else 0 // ASM pseudo-access flag
    ) & (INNER_CLASSES_FLAGS | asm.Opcodes.ACC_DEPRECATED)
    val flags = if (innerSym.isModuleClass) flagsWithFinal & ~asm.Opcodes.ACC_FINAL else flagsWithFinal // For SI-5676, object overriding.

    val jname = innerSym.javaBinaryName.toString // never null
    val oname = { // null when method-enclosed
      val on = outerName(innerSym)
      if (on == null) null else on.toString
    }
    val iname = { // null for anonymous inner class
      val in = innerName(innerSym)
      if (in == null) null else in.toString
    }

    InnerClassEntry(jname, oname, iname, flags)
  }

  // --------------------------------------------
  // ---------------- Java flags ----------------
  // --------------------------------------------

  /*
   * can-multi-thread
   */
  final def hasPublicBitSet(flags: Int) = ((flags & asm.Opcodes.ACC_PUBLIC) != 0)

  /*
   * must-single-thread
   */
  final def isRemote(s: Symbol) = (s hasAnnotation definitions.RemoteAttr)

  /*
   * Return the Java modifiers for the given symbol.
   * Java modifiers for classes:
   *  - public, abstract, final, strictfp (not used)
   * for interfaces:
   *  - the same as for classes, without 'final'
   * for fields:
   *  - public, private (*)
   *  - static, final
   * for methods:
   *  - the same as for fields, plus:
   *  - abstract, synchronized (not used), strictfp (not used), native (not used)
   *
   *  (*) protected cannot be used, since inner classes 'see' protected members,
   *      and they would fail verification after lifted.
   *
   * must-single-thread
   */
  def javaFlags(sym: Symbol): Int = {
    // constructors of module classes should be private
    // PP: why are they only being marked private at this stage and not earlier?
    val privateFlag =
      sym.isPrivate || (sym.isPrimaryConstructor && isTopLevelModule(sym.owner))

    // Final: the only fields which can receive ACC_FINAL are eager vals.
    // Neither vars nor lazy vals can, because:
    //
    // Source: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5.3
    // "Another problem is that the specification allows aggressive
    // optimization of final fields. Within a thread, it is permissible to
    // reorder reads of a final field with those modifications of a final
    // field that do not take place in the constructor."
    //
    // A var or lazy val which is marked final still has meaning to the
    // scala compiler. The word final is heavily overloaded unfortunately;
    // for us it means "not overridable". At present you can't override
    // vars regardless; this may change.
    //
    // The logic does not check .isFinal (which checks flags for the FINAL flag,
    // and includes symbols marked lateFINAL) instead inspecting rawflags so
    // we can exclude lateFINAL. Such symbols are eligible for inlining, but to
    // avoid breaking proxy software which depends on subclassing, we do not
    // emit ACC_FINAL.
    // Nested objects won't receive ACC_FINAL in order to allow for their overriding.

    val finalFlag = (
         (((sym.rawflags & symtab.Flags.FINAL) != 0) || isTopLevelModule(sym))
      && !sym.enclClass.isInterface
      && !sym.isClassConstructor
      && !sym.isMutable // lazy vals and vars both
    )

    // Primitives are "abstract final" to prohibit instantiation
    // without having to provide any implementations, but that is an
    // illegal combination of modifiers at the bytecode level so
    // suppress final if abstract if present.
    import asm.Opcodes._
    mkFlags(
      if (privateFlag) ACC_PRIVATE else ACC_PUBLIC,
      if (sym.isDeferred || sym.hasAbstractFlag) ACC_ABSTRACT else 0,
      if (sym.isInterface) ACC_INTERFACE else 0,
      if (finalFlag && !sym.hasAbstractFlag) ACC_FINAL else 0,
      if (sym.isStaticMember) ACC_STATIC else 0,
      if (sym.isBridge) ACC_BRIDGE | ACC_SYNTHETIC else 0,
      if (sym.isArtifact) ACC_SYNTHETIC else 0,
      if (sym.isClass && !sym.isInterface) ACC_SUPER else 0,
      if (sym.hasEnumFlag) ACC_ENUM else 0,
      if (sym.isVarargsMethod) ACC_VARARGS else 0,
      if (sym.hasFlag(symtab.Flags.SYNCHRONIZED)) ACC_SYNCHRONIZED else 0
    )
  }

  /*
   * must-single-thread
   */
  def javaFieldFlags(sym: Symbol) = {
    javaFlags(sym) | mkFlags(
      if (sym hasAnnotation definitions.TransientAttr) asm.Opcodes.ACC_TRANSIENT else 0,
      if (sym hasAnnotation definitions.VolatileAttr)  asm.Opcodes.ACC_VOLATILE  else 0,
      if (sym.isMutable) 0 else asm.Opcodes.ACC_FINAL
    )
  }

} // end of class BCodeTypes
