All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dotty.tools.dotc.transform.GenericSignatures.scala Maven / Gradle / Ivy

The newest version!
package dotty.tools
package dotc
package transform

import core.Annotations.Annotation
import core.Contexts.Context
import core.Definitions
import core.Flags._
import core.Names.Name
import core.Symbols._
import core.TypeApplications.TypeParamInfo
import core.TypeErasure.erasure
import core.Types._
import core.classfile.ClassfileConstants
import ast.Trees._
import SymUtils._
import TypeUtils._
import java.lang.StringBuilder

/** Helper object to generate generic java signatures, as defined in
 *  the Java Virtual Machine Specification, §4.3.4
 */
object GenericSignatures {

  /** Generate the signature for `sym0`, with type `info`, as defined in
   *  the Java Virtual Machine Specification, §4.3.4
   *
   *  @param sym0 The symbol for which to define the signature
   *  @param info The type of the symbol
   *  @return The signature if it could be generated, `None` otherwise.
   */
  def javaSig(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = {
    // Avoid generating a signature for local symbols.
    if (sym0.isLocal) None
    else javaSig0(sym0, info)(ctx.withPhase(ctx.erasurePhase))
  }

  @noinline
  private final def javaSig0(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = {
    val builder = new StringBuilder(64)
    val isTraitSignature = sym0.enclosingClass.is(Trait)

    def superSig(cls: Symbol, parents: List[Type]): Unit = {
      def isInterfaceOrTrait(sym: Symbol) = sym.is(PureInterface) || sym.is(Trait)

      // a signature should always start with a class
      def ensureClassAsFirstParent(tps: List[Type]) = tps match {
        case Nil => defn.ObjectType :: Nil
        case head :: tail if isInterfaceOrTrait(head.typeSymbol) => defn.ObjectType :: tps
        case _ => tps
      }

      val minParents = minimizeParents(cls, parents)
      val validParents =
        if (isTraitSignature)
          // java is unthrilled about seeing interfaces inherit from classes
          minParents filter (p => isInterfaceOrTrait(p.classSymbol))
        else minParents

      val ps = ensureClassAsFirstParent(validParents)
      ps.foreach(boxedSig)
    }

    def boxedSig(tp: Type): Unit = jsig(tp.widenDealias, primitiveOK = false)

    def boundsSig(bounds: List[Type]): Unit = {
      val (isTrait, isClass) = bounds partition (_.typeSymbol.is(Trait))
      isClass match {
        case Nil    => builder.append(':') // + boxedSig(ObjectTpe)
        case x :: _ => builder.append(':'); boxedSig(x)
      }
      isTrait.foreach { tp =>
        builder.append(':')
        boxedSig(tp)
      }
    }

    def paramSig(param: LambdaParam): Unit = {
      builder.append(sanitizeName(param.paramName))
      boundsSig(hiBounds(param.paramInfo.bounds))
    }

    def polyParamSig(tparams: List[LambdaParam]): Unit =
      if (tparams.nonEmpty) {
        builder.append('<')
        tparams.foreach(paramSig)
        builder.append('>')
      }

    def typeParamSig(name: Name): Unit = {
      builder.append(ClassfileConstants.TVAR_TAG)
      builder.append(sanitizeName(name))
      builder.append(';')
    }

    def methodResultSig(restpe: Type): Unit = {
      val finalType = restpe.finalResultType
      val sym = finalType.typeSymbol
      if (sym == defn.UnitClass || sym == defn.BoxedUnitModule || sym0.isConstructor) {
        builder.append(ClassfileConstants.VOID_TAG)
      } else {
        jsig(finalType)
      }
    }

    // This will reject any name that has characters that cannot appear in
    // names on the JVM. Interop with Java is not guaranteed for those, so we
    // dont need to generate signatures for them.
    def sanitizeName(name: Name): String = {
      val nameString = name.mangledString
      if (nameString.forall(c => c == '.' || Character.isJavaIdentifierPart(c))) {
        nameString
      } else {
        throw new UnknownSig
      }
    }

    // Anything which could conceivably be a module (i.e. isn't known to be
    // a type parameter or similar) must go through here or the signature is
    // likely to end up with Foo.Empty where it needs Foo.Empty$.
    def fullNameInSig(sym: Symbol): Unit = {
      val name = ctx.atPhase(ctx.genBCodePhase) { implicit ctx => sanitizeName(sym.fullName).replace('.', '/') }
      builder.append('L').append(name)
    }

    def classSig(sym: Symbol, pre: Type = NoType, args: List[Type] = Nil): Unit = {
      def argSig(tp: Type): Unit =
        tp match {
          case bounds: TypeBounds =>
            if (!(defn.AnyType <:< bounds.hi)) {
              builder.append('+')
              boxedSig(bounds.hi)
            }
            else if (!(bounds.lo <:< defn.NothingType)) {
              builder.append('-')
              boxedSig(bounds.lo)
            }
            else builder.append('*')
          case PolyType(_, res) =>
            builder.append('*') // scala/bug#7932
          case _: HKTypeLambda =>
            fullNameInSig(tp.typeSymbol)
            builder.append(';')
          case _ =>
            boxedSig(tp)
        }

      if (pre.exists) {
        val preRebound = pre.baseType(sym.owner) // #2585
        if (needsJavaSig(preRebound, Nil)) {
          val i = builder.length()
          jsig(preRebound)
          if (builder.charAt(i) == 'L') {
            builder.delete(builder.length() - 1, builder.length())// delete ';'
                                                                  // If the prefix is a module, drop the '$'. Classes (or modules) nested in modules
                                                                  // are separated by a single '$' in the filename: `object o { object i }` is o$i$.
            if (preRebound.typeSymbol.is(ModuleClass))
              builder.delete(builder.length() - 1, builder.length())

            // Ensure every '.' in the generated signature immediately follows
            // a close angle bracket '>'.  Any which do not are replaced with '$'.
            // This arises due to multiply nested classes in the face of the
            // rewriting explained at rebindInnerClass.

            // TODO revisit this. Does it align with javac for code that can be expressed in both languages?
            val delimiter = if (builder.charAt(builder.length() - 1) == '>') '.' else '$'
            builder.append(delimiter).append(sanitizeName(sym.name.asSimpleName))
          } else fullNameInSig(sym)
        } else fullNameInSig(sym)
      } else fullNameInSig(sym)

      if (args.nonEmpty) {
        builder.append('<')
        args foreach argSig
        builder.append('>')
      }
      builder.append(';')
    }

    @noinline
    def jsig(tp0: Type, toplevel: Boolean = false, primitiveOK: Boolean = true): Unit = {

      val tp = tp0.dealias
      tp match {

        case ref @ TypeParamRef(_: PolyType, _) =>
          typeParamSig(ref.paramName.lastPart)

        case RefOrAppliedType(sym, pre, args) =>
          // If args isEmpty, Array is being used as a type constructor
          if (sym == defn.ArrayClass && args.nonEmpty) {
            if (unboundedGenericArrayLevel(tp) == 1) jsig(defn.ObjectType)
            else {
              builder.append(ClassfileConstants.ARRAY_TAG)
              args.foreach(jsig(_))
            }
          }
          else if (sym == defn.PairClass && tp.tupleArity > Definitions.MaxTupleArity)
            jsig(defn.TupleXXLType)
          else if (isTypeParameterInSig(sym, sym0)) {
            assert(!sym.isAliasType, "Unexpected alias type: " + sym)
            typeParamSig(sym.name.lastPart)
          }
          else if (defn.specialErasure.contains(sym))
            jsig(defn.specialErasure(sym).typeRef)
          else if (sym == defn.UnitClass || sym == defn.BoxedUnitModule)
            jsig(defn.BoxedUnitType)
          else if (sym == defn.NothingClass)
            jsig(defn.RuntimeNothingModuleRef)
          else if (sym == defn.NullClass)
            jsig(defn.RuntimeNullModuleRef)
          else if (sym.isPrimitiveValueClass) {
            if (!primitiveOK) jsig(defn.ObjectType)
            else if (sym == defn.UnitClass) jsig(defn.BoxedUnitType)
            else builder.append(defn.typeTag(sym.info))
          }
          else if (ValueClasses.isDerivedValueClass(sym)) {
            val unboxed     = ValueClasses.valueClassUnbox(sym.asClass).info.finalResultType
            val unboxedSeen = tp.memberInfo(ValueClasses.valueClassUnbox(sym.asClass)).finalResultType
            if (unboxedSeen.isPrimitiveValueType && !primitiveOK)
              classSig(sym, pre, args)
            else
              jsig(unboxedSeen, toplevel, primitiveOK)
          }
          else if (defn.isXXLFunctionClass(sym))
            classSig(defn.FunctionXXLClass)
          else if (sym.isClass)
            classSig(sym, pre, args)
          else
            jsig(erasure(tp), toplevel, primitiveOK)

        case ExprType(restpe) if toplevel =>
          builder.append("()")
          methodResultSig(restpe)

        case ExprType(restpe) =>
          jsig(defn.FunctionType(0).appliedTo(restpe))

        case PolyType(tparams, mtpe: MethodType) =>
          assert(tparams.nonEmpty)
          if (toplevel) polyParamSig(tparams)
          jsig(mtpe)

        // Nullary polymorphic method
        case PolyType(tparams, restpe) =>
          assert(tparams.nonEmpty)
          if (toplevel) polyParamSig(tparams)
          builder.append("()")
          methodResultSig(restpe)

        case mtpe: MethodType =>
          // erased method parameters do not make it to the bytecode.
          def effectiveParamInfoss(t: Type)(implicit ctx: Context): List[List[Type]] = t match {
            case t: MethodType if t.isErasedMethod => effectiveParamInfoss(t.resType)
            case t: MethodType => t.paramInfos :: effectiveParamInfoss(t.resType)
            case _ => Nil
          }
          val params = effectiveParamInfoss(mtpe).flatten
          val restpe = mtpe.finalResultType
          builder.append('(')
          // TODO: Update once we support varargs
          params.foreach { tp =>
            jsig(tp)
          }
          builder.append(')')
          methodResultSig(restpe)

        case AndType(tp1, tp2) =>
          jsig(intersectionDominator(tp1 :: tp2 :: Nil), primitiveOK = primitiveOK)

        case ci: ClassInfo =>
          def polyParamSig(tparams: List[TypeParamInfo]): Unit =
            if (tparams.nonEmpty) {
              builder.append('<')
              tparams.foreach { tp =>
                builder.append(sanitizeName(tp.paramName.lastPart))
                boundsSig(hiBounds(tp.paramInfo.bounds))
              }
              builder.append('>')
            }
          val tParams = tp.typeParams
          if (toplevel) polyParamSig(tParams)
          superSig(ci.typeSymbol, ci.parents)

        case AnnotatedType(atp, _) =>
          jsig(atp, toplevel, primitiveOK)

        case hktl: HKTypeLambda =>
          jsig(hktl.finalResultType, toplevel, primitiveOK)

        case _ =>
          val etp = erasure(tp)
          if (etp eq tp) throw new UnknownSig
          else jsig(etp, toplevel, primitiveOK)
      }
    }
    val throwsArgs = sym0.annotations flatMap ThrownException.unapply
    if (needsJavaSig(info, throwsArgs)) {
      try {
        jsig(info, toplevel = true)
        throwsArgs.foreach { t =>
          builder.append('^')
          jsig(t, toplevel = true)
        }
        Some(builder.toString)
      }
      catch { case _: UnknownSig => None }
    }
    else None
  }

  private class UnknownSig extends Exception

  /** The intersection dominator (SLS 3.7) of a list of types is computed as follows.
    *
    *  - If the list contains one or more occurrences of scala.Array with
    *    type parameters El1, El2, ... then the dominator is scala.Array with
    *    type parameter of intersectionDominator(List(El1, El2, ...)).           <--- @PP: not yet in spec.
    *  - Otherwise, the list is reduced to a subsequence containing only types
    *    which are not subtypes of other listed types (the span.)
    *  - If the span is empty, the dominator is Object.
    *  - If the span contains a class Tc which is not a trait and which is
    *    not Object, the dominator is Tc.                                        <--- @PP: "which is not Object" not in spec.
    *  - Otherwise, the dominator is the first element of the span.
    */
  private def intersectionDominator(parents: List[Type])(implicit ctx: Context): Type = {
    if (parents.isEmpty) defn.ObjectType
    else {
      val psyms = parents map (_.typeSymbol)
      if (psyms contains defn.ArrayClass) {
        // treat arrays specially
        defn.ArrayType.appliedTo(intersectionDominator(parents.filter(_.typeSymbol == defn.ArrayClass).map(t => t.argInfos.head)))
      } else {
        // implement new spec for erasure of refined types.
        def isUnshadowed(psym: Symbol) =
          !(psyms exists (qsym => (psym ne qsym) && (qsym isSubClass psym)))
        val cs = parents.iterator.filter { p => // isUnshadowed is a bit expensive, so try classes first
          val psym = p.classSymbol
          psym.ensureCompleted()
          psym.isClass && !psym.is(Trait) && isUnshadowed(psym)
        }
        (if (cs.hasNext) cs else parents.iterator.filter(p => isUnshadowed(p.classSymbol))).next()
      }
    }
  }

  /* Drop redundant types (ones which are implemented by some other parent) from the immediate parents.
   * This is important on Android because there is otherwise an interface explosion.
   */
  private def minimizeParents(cls: Symbol, parents: List[Type])(implicit ctx: Context): List[Type] = if (parents.isEmpty) parents else {
    // val requiredDirect: Symbol => Boolean = requiredDirectInterfaces.getOrElse(cls, Set.empty)
    var rest   = parents.tail
    var leaves = collection.mutable.ListBuffer.empty[Type] += parents.head
    while (rest.nonEmpty) {
      val candidate = rest.head
      val candidateSym = candidate.typeSymbol
      // val required = requiredDirect(candidateSym) || !leaves.exists(t => t.typeSymbol isSubClass candidateSym)
      val required = !leaves.exists(t => t.typeSymbol.isSubClass(candidateSym))
      if (required) {
        leaves = leaves filter { t =>
          val ts = t.typeSymbol
          !(ts.is(Trait) || ts.is(PureInterface)) || !candidateSym.isSubClass(ts)
          // requiredDirect(ts) || !ts.isTraitOrInterface || !candidateSym.isSubClass(ts)
        }
        leaves += candidate
      }
      rest = rest.tail
    }
    leaves.toList
  }

  private def hiBounds(bounds: TypeBounds)(implicit ctx: Context): List[Type] = bounds.hi.widenDealias match {
    case AndType(tp1, tp2) => hiBounds(tp1.bounds) ::: hiBounds(tp2.bounds)
    case tp => tp :: Nil
  }

  /** Arrays despite their finality may turn up as refined type parents,
    *  e.g. with "tagged types" like Array[Int] with T.
    */
  private def unboundedGenericArrayLevel(tp: Type)(implicit ctx: Context): Int = tp match {
    case GenericArray(core, level) if !(core <:< defn.AnyRefType) =>
      level
    case AndType(tp1, tp2) =>
      unboundedGenericArrayLevel(tp1) max unboundedGenericArrayLevel(tp2)
    case _ =>
      0
  }

  // only refer to type params that will actually make it into the sig, this excludes:
  // * higher-order type parameters
  // * type parameters appearing in method parameters
  // * type members not visible in an enclosing template
  private def isTypeParameterInSig(sym: Symbol, initialSymbol: Symbol)(implicit ctx: Context) = {
    !sym.maybeOwner.isTypeParam &&
      sym.isTypeParam && (
      sym.isContainedIn(initialSymbol.topLevelClass) ||
        (initialSymbol.is(Method) && initialSymbol.typeParams.contains(sym))
      )
  }

  /** Extracts the type of the thrown exception from an AnnotationInfo.
    *
    * Supports both “old-style” `@throws(classOf[Exception])`
    * as well as “new-style” `@throws[Exception]("cause")` annotations.
    */
  private object ThrownException {
    def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] = {
      ann.tree match {
        case Apply(TypeApply(fun, List(tpe)), _) if tpe.isType && fun.symbol.owner == defn.ThrowsAnnot && fun.symbol.isConstructor =>
          Some(tpe.typeOpt)
        case _ =>
          None
      }
    }
  }

  // @M #2585 when generating a java generic signature that includes
  // a selection of an inner class p.I, (p = `pre`, I = `cls`) must
  // rewrite to p'.I, where p' refers to the class that directly defines
  // the nested class I.
  //
  // See also #2585 marker in javaSig: there, type arguments must be
  // included (use pre.baseType(cls.owner)).
  //
  // This requires that cls.isClass.
  private def rebindInnerClass(pre: Type, cls: Symbol)(implicit ctx: Context): Type = {
    val owner = cls.owner
    if (owner.is(PackageClass) || owner.isTerm) pre else cls.owner.info /* .tpe_* */
  }

  object GenericArray {

    /** Is `tp` an unbounded generic type (i.e. which could be instantiated
      *  with primitive as well as class types)?.
      */
    private def genericCore(tp: Type)(implicit ctx: Context): Type = tp.widenDealias match {
      /* A Java Array is erased to Array[Object] (T can only be a reference type), where as a Scala Array[T] is
       * erased to Object. However, there is only symbol for the Array class. So to make the distinction between
       * a Java and a Scala array, we check if the owner of T comes from a Java class.
       * This however caused issue scala/bug#5654. The additional test for EXISTENTIAL fixes it, see the ticket comments.
       * In short, members of an existential type (e.g. `T` in `forSome { type T }`) can have pretty arbitrary
       * owners (e.g. when computing lubs,  is used). All packageClass symbols have `isJavaDefined == true`.
       */
      case RefOrAppliedType(sym, tp, _) =>
        if (sym.isAbstractOrParamType && (!sym.owner.is(JavaDefined) || sym.is(Scala2Existential)))
          tp
        else
          NoType

      case bounds: TypeBounds =>
        bounds

      case _ =>
        NoType
    }

    /** If `tp` is of the form Array[...Array[T]...] where `T` is an abstract type
      *  then Some((N, T)) where N is the number of Array constructors enclosing `T`,
      *  otherwise None. Existentials on any level are ignored.
      */
    def unapply(tp: Type)(implicit ctx: Context): Option[(Type, Int)] = tp.widenDealias match {
      case defn.ArrayOf(arg) =>
        genericCore(arg) match {
          case NoType =>
            arg match {
              case GenericArray(core, level) => Some((core, level + 1))
              case _ => None
            }
          case core =>
            Some((core, 1))
        }
      case _ =>
        None
    }

  }

  private object RefOrAppliedType {
    def unapply(tp: Type)(implicit ctx: Context): Option[(Symbol, Type, List[Type])] = tp match {
      case TypeParamRef(_, _) =>
        Some((tp.typeSymbol, tp, Nil))
      case TermParamRef(_, _) =>
        Some((tp.termSymbol, tp, Nil))
      case TypeRef(pre, _) if !tp.typeSymbol.isAliasType =>
        val sym = tp.typeSymbol
        Some((sym, pre, Nil))
      case AppliedType(pre, args) =>
        Some((pre.typeSymbol, pre, args))
      case _ =>
        None
    }
  }

  private def needsJavaSig(tp: Type, throwsArgs: List[Type])(implicit ctx: Context): Boolean = !ctx.settings.YnoGenericSig.value && {
      def needs(tp: Type) = (new NeedsSigCollector).apply(false, tp)
      needs(tp) || throwsArgs.exists(needs)
  }

  private class NeedsSigCollector(implicit ctx: Context) extends TypeAccumulator[Boolean] {
    override def apply(x: Boolean, tp: Type): Boolean =
      if (!x) {
        tp match {
          case RefinedType(parent, refinedName, refinedInfo) =>
            val sym = parent.typeSymbol
            if (sym == defn.ArrayClass) foldOver(x, refinedInfo)
            else true
          case tref @ TypeRef(pre, name) =>
            val sym = tref.typeSymbol
            if (sym.is(TypeParam) || sym.typeParams.nonEmpty) true
            else if (sym.isClass) foldOver(x, rebindInnerClass(pre, sym)) // #2585
            else foldOver(x, pre)
          case PolyType(_, _) =>
            true
          case ClassInfo(_, _, parents, _, _) =>
            foldOver(tp.typeParams.nonEmpty, parents)
          case AnnotatedType(tpe, _) =>
            foldOver(x, tpe)
          case proxy: TypeProxy =>
            foldOver(x, proxy)
          case _ =>
            foldOver(x, tp)
        }
      } else x
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy