From 63754e72984509c2ca64d06912778b57a87ab943 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 29 Sep 2017 09:29:22 +0200 Subject: [PATCH 01/13] Write generic java signatures to classfiles The code has been ported from scalac. --- .../backend/jvm/DottyBackendInterface.scala | 80 ++- .../tools/dotc/config/ScalaSettings.scala | 2 + .../dotty/tools/dotc/core/Definitions.scala | 2 + .../src/dotty/tools/dotc/core/NameOps.scala | 19 +- .../src/dotty/tools/dotc/core/Symbols.scala | 25 + .../src/dotty/tools/dotc/core/Types.scala | 18 + .../dotty/tools/dotc/transform/Erasure.scala | 485 ++++++++++++++++++ .../dotty/tools/dotc/CompilationTests.scala | 6 + .../tools/vulpix/TestConfiguration.scala | 1 + .../generic-java-signatures/arrayBound.check | 5 + .../generic-java-signatures/arrayBound.scala | 9 + .../boundParameters.check | 3 + .../boundParameters.scala | 9 + .../boundsInterfaces.check | 2 + .../boundsInterfaces.scala | 9 + .../higherKinded.check | 5 + .../higherKinded.scala | 11 + .../lowerBoundClass.check | 2 + .../lowerBoundClass.scala | 9 + .../primitiveArrayBound.scala | 9 + .../primitiveArrayBounds.check | 1 + .../generic-java-signatures/primitives.check | 9 + .../generic-java-signatures/primitives.scala | 17 + tests/generic-java-signatures/simple.check | 3 + tests/generic-java-signatures/simple.scala | 7 + .../simpleBoundParameters.check | 1 + .../simpleBoundParameters.scala | 9 + .../superClassParams.check | 4 + .../superClassParams.scala | 13 + .../valueClassBound.check | 1 + .../valueClassBound.scala | 10 + tests/generic-java-signatures/wildcards.check | 3 + tests/generic-java-signatures/wildcards.scala | 9 + 33 files changed, 780 insertions(+), 18 deletions(-) create mode 100644 tests/generic-java-signatures/arrayBound.check create mode 100644 tests/generic-java-signatures/arrayBound.scala create mode 100644 tests/generic-java-signatures/boundParameters.check create mode 100644 tests/generic-java-signatures/boundParameters.scala create mode 100644 tests/generic-java-signatures/boundsInterfaces.check create mode 100644 tests/generic-java-signatures/boundsInterfaces.scala create mode 100644 tests/generic-java-signatures/higherKinded.check create mode 100644 tests/generic-java-signatures/higherKinded.scala create mode 100644 tests/generic-java-signatures/lowerBoundClass.check create mode 100644 tests/generic-java-signatures/lowerBoundClass.scala create mode 100644 tests/generic-java-signatures/primitiveArrayBound.scala create mode 100644 tests/generic-java-signatures/primitiveArrayBounds.check create mode 100644 tests/generic-java-signatures/primitives.check create mode 100644 tests/generic-java-signatures/primitives.scala create mode 100644 tests/generic-java-signatures/simple.check create mode 100644 tests/generic-java-signatures/simple.scala create mode 100644 tests/generic-java-signatures/simpleBoundParameters.check create mode 100644 tests/generic-java-signatures/simpleBoundParameters.scala create mode 100644 tests/generic-java-signatures/superClassParams.check create mode 100644 tests/generic-java-signatures/superClassParams.scala create mode 100644 tests/generic-java-signatures/valueClassBound.check create mode 100644 tests/generic-java-signatures/valueClassBound.scala create mode 100644 tests/generic-java-signatures/wildcards.check create mode 100644 tests/generic-java-signatures/wildcards.scala diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index c8a587b3b71d..c4cff9349ba7 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -489,8 +489,86 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma def getSingleOutput: Option[AbstractFile] = None // todo: implement + // @M don't generate java generics sigs for (members of) implementation + // classes, as they are monomorphic (TODO: ok?) + private final def needsGenericSignature(sym: Symbol): Boolean = !( + // pp: this condition used to include sym.hasexpandedname, but this leads + // to the total loss of generic information if a private member is + // accessed from a closure: both the field and the accessor were generated + // without it. This is particularly bad because the availability of + // generic information could disappear as a consequence of a seemingly + // unrelated change. + ctx.base.settings.YnoGenericSig.value + || sym.is(Flags.Artifact) + || sym.is(Flags.allOf(Flags.Method, Flags.Lifted)) + || sym.is(Flags.Bridge) + ) + + private def verifySignature(sym: Symbol, sig: String)(implicit ctx: Context): Unit = { + import scala.tools.asm.util.CheckClassAdapter + def wrap(body: => Unit): Boolean = + try { body; true } + catch { case ex: Throwable => println(ex.getMessage); false } + + val valid = wrap { + if (sym.is(Flags.Method)) { + CheckClassAdapter.checkMethodSignature(sig) + } + else if (sym.isTerm) { + CheckClassAdapter.checkFieldSignature(sig) + } + else { + CheckClassAdapter.checkClassSignature(sig) + } + } + + if(!valid) { + ctx.warning( + i"""|compiler bug: created invalid generic signature for $sym in ${sym.denot.owner.showFullName} + |signature: $sig + |if this is reproducible, please report bug at https://github.com/lampepfl/dotty/issues + """.trim, sym.pos) + } + } + + /** + * Generates the generic signature for `sym` before erasure. + * + * @param sym The symbol for which to generate a signature. + * @param owner The owner of `sym`. + * @return The generic signature of `sym` before erasure, as specified in the Java Virtual + * Machine Specification, §4.3.4, or `null` if `sym` doesn't need a generic signature. + * @see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4 + */ + def getGenericSignature(sym: Symbol, owner: Symbol): String = { + ctx.atPhase(ctx.erasurePhase) { implicit ctx => + val memberTpe = + if (sym.is(Flags.Method)) sym.denot.info + else owner.denot.thisType.memberInfo(sym) + getGenericSignature(sym, owner, memberTpe).orNull + } + } + + private def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type)(implicit ctx: Context): Option[String] = + if (needsGenericSignature(sym)) { + val erasedTypeSym = sym.denot.info.typeSymbol + if (erasedTypeSym.isPrimitiveValueClass) { + None + } else { + val jsOpt = + ctx.atPhase(ctx.erasurePhase) { implicit ctx => + Erasure.javaSig(sym, memberTpe, _ => ()) + } - def getGenericSignature(sym: Symbol, owner: Symbol): String = null // todo: implement + if (ctx.settings.XverifySignatures.value) { + jsOpt.foreach(verifySignature(sym, _)) + } + + jsOpt + } + } else { + None + } def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = null // todo: implement diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f2b854f41fb1..f921ce5041a9 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -61,6 +61,7 @@ class ScalaSettings extends Settings.SettingGroup { val XnoValueClasses = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.") val XreplLineWidth = IntSetting("-Xrepl-line-width", "Maximial number of columns per line for REPL output", 390) val XfatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.") + val XverifySignatures = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.") /** -Y "Private" settings */ val overrideVars = BooleanSetting("-Yoverride-vars", "Allow vars to be overridden.") @@ -79,6 +80,7 @@ class ScalaSettings extends Settings.SettingGroup { val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.") + val YnoGenericSig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.") val Yskip = PhasesSetting("-Yskip", "Skip") val Ydumpclasses = StringSetting("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "") diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index bc97b86de979..966d7e268270 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -300,9 +300,11 @@ class Definitions { lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef)) def NothingType = NothingClass.typeRef + lazy val RuntimeNothingModuleRef = ctx.requiredModuleRef("scala.runtime.Nothing") lazy val NullClass: ClassSymbol = enterCompleteClassSymbol( ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef)) def NullType = NullClass.typeRef + lazy val RuntimeNullModuleRef = ctx.requiredModuleRef("scala.runtime.Null") lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 5041c003bb92..9c9e40cf00ea 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -229,23 +229,8 @@ object NameOps { def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = { - def typeToTag(tp: Types.Type): Name = { - tp.classSymbol match { - case t if t eq defn.IntClass => nme.specializedTypeNames.Int - case t if t eq defn.BooleanClass => nme.specializedTypeNames.Boolean - case t if t eq defn.ByteClass => nme.specializedTypeNames.Byte - case t if t eq defn.LongClass => nme.specializedTypeNames.Long - case t if t eq defn.ShortClass => nme.specializedTypeNames.Short - case t if t eq defn.FloatClass => nme.specializedTypeNames.Float - case t if t eq defn.UnitClass => nme.specializedTypeNames.Void - case t if t eq defn.DoubleClass => nme.specializedTypeNames.Double - case t if t eq defn.CharClass => nme.specializedTypeNames.Char - case _ => nme.specializedTypeNames.Object - } - } - - val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => typeToTag(x._1)) - val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => typeToTag(x._1)) + val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => x._1.toTag) + val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => x._1.toTag) name.likeSpaced(name ++ nme.specializedTypeNames.prefix ++ methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++ diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 2e3661a52039..6cffd6d75419 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -530,6 +530,18 @@ object Symbols { /** The current name of this symbol */ final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName] + def isHigherOrderTypeParameter(implicit ctx: Context): Boolean = this.maybeOwner.isTypeParameterOrSkolem + def isTypeParameterOrSkolem(implicit ctx: Context): Boolean = this.isTypeParam + def enclClassChain(implicit ctx: Context): List[Symbol] = this.maybeOwner.enclClassChain + final def isDerivedValueClass(implicit ctx: Context): Boolean = + isClass && !denot.is(Flags.Package) && !denot.is(Flags.Trait) && + !ctx.phase.erasedTypes && denot.info.firstParent.typeSymbol == defn.AnyValClass && !denot.isPrimitiveValueClass + + /** If this is a derived value class, return its unbox method + * or NoSymbol if it does not exist. + */ + def derivedValueClassUnbox(implicit ctx: Context): Symbol = NoSymbol + /** The source or class file from which this class or * the class containing this symbol was generated, null if not applicable. * Overridden in ClassSymbol @@ -636,17 +648,30 @@ object Symbols { denot.asInstanceOf[ClassDenotation] override protected def prefixString = "ClassSymbol" + + override def enclClassChain(implicit ctx: Context): List[Symbol] = + if (this.is(Flags.PackageClass)) Nil + else this :: denot.owner.enclClassChain + + override def derivedValueClassUnbox(implicit ctx: Context): Symbol = + // (info.decl(nme.unbox)) orElse uncomment once we accept unbox methods + (denot.info.decls.find(_.denot.is(Flags.ParamAccessor | Flags.Method))) + } class ErrorSymbol(val underlying: Symbol, msg: => String)(implicit ctx: Context) extends Symbol(NoCoord, ctx.nextId) { type ThisName = underlying.ThisName denot = underlying.denot + + override def enclClassChain(implicit ctx: Context): List[Symbol] = Nil } @sharable object NoSymbol extends Symbol(NoCoord, 0) { denot = NoDenotation override def associatedFile(implicit ctx: Context): AbstractFile = NoSource.file override def recomputeDenot(lastd: SymDenotation)(implicit ctx: Context): SymDenotation = NoDenotation + + override def enclClassChain(implicit ctx: Context): List[Symbol] = Nil } implicit class Copier[N <: Name](sym: Symbol { type ThisName = N })(implicit ctx: Context) { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5509415d827d..996dda686c84 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -745,6 +745,24 @@ object Types { else ctx.asSeenFrom(this, pre, cls) } + /** The JVM tag for this type, if it's a primitive, `java.lang.Object` otherwise. */ + final def toTag(implicit ctx: Context): Name = { + classSymbol match { + case t if t eq defn.IntClass => nme.specializedTypeNames.Int + case t if t eq defn.BooleanClass => nme.specializedTypeNames.Boolean + case t if t eq defn.ByteClass => nme.specializedTypeNames.Byte + case t if t eq defn.LongClass => nme.specializedTypeNames.Long + case t if t eq defn.ShortClass => nme.specializedTypeNames.Short + case t if t eq defn.FloatClass => nme.specializedTypeNames.Float + case t if t eq defn.UnitClass => nme.specializedTypeNames.Void + case t if t eq defn.DoubleClass => nme.specializedTypeNames.Double + case t if t eq defn.CharClass => nme.specializedTypeNames.Char + case _ => nme.specializedTypeNames.Object + } + } + + + // ----- Subtype-related -------------------------------------------- /** Is this type a subtype of that type? */ diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index ecb40b52a910..65dbcf186c3a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -30,6 +30,10 @@ import ExplicitOuter._ import core.Mode import core.PhantomErasure import reporting.trace +import dotty.tools.dotc.core.Annotations.Annotation +import dotty.tools.dotc.core.classfile.ClassfileConstants +import dotty.tools.dotc.core.TypeApplications.TypeParamInfo +import java.lang.StringBuilder class Erasure extends Phase with DenotTransformer { @@ -676,4 +680,485 @@ object Erasure { private def wasPhantom(tp: Type)(implicit ctx: Context): Boolean = tp.widenDealias.classSymbol eq defn.ErasedPhantomClass + + /** 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.typeParams.head.paramInfo))) + } 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.typeSymbol + psym.ensureCompleted() + psym.isClass && !psym.is(Flags.Trait) && isUnshadowed(psym) + } + (if (cs.hasNext) cs else parents.iterator.filter(p => isUnshadowed(p.typeSymbol))).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(Flags.Trait) || ts.is(Flags.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) => tp1 :: tp2 :: Nil + 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(level, core) if !(core <:< defn.AnyRefType) => + level + case AndType(tp1, tp2) => + Math.max(unboundedGenericArrayLevel(tp1), 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.isHigherOrderTypeParameter && + sym.isTypeParameterOrSkolem && ( + (initialSymbol.enclClassChain.exists(sym isContainedIn _)) || + (initialSymbol.is(Flags.Method) && initialSymbol.typeParams.contains(sym)) + ) + } + + final def javaSig(sym0: Symbol, info: Type, markClassUsed: Symbol => Unit)(implicit ctx: Context): Option[String] = + ctx.atPhase(ctx.erasurePhase) { implicit ctx => javaSig0(sym0, info, markClassUsed) } + + @noinline + private final def javaSig0(sym0: Symbol, info: Type, markClassUsed: Symbol => Unit)(implicit ctx: Context): Option[String] = { + val builder = new StringBuilder(64) + val isTraitSignature = sym0.enclosingClass.is(Flags.Trait) + + def superSig(cls: Symbol, parents: List[Type]): Unit = { + def isInterfaceOrTrait(sym: Symbol) = sym.is(Flags.PureInterface) || sym.is(Flags.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.typeSymbol)) + else minParents + + val ps = ensureClassAsFirstParent(validParents) + ps.foreach(boxedSig) + } + + def boxedSig(tp: Type): Unit = jsig(tp, primitiveOK = false) + + def boundsSig(bounds: List[Type]): Unit = { + val (isTrait, isClass) = bounds partition (_.typeSymbol.is(Flags.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) + } + } + + // TODO: Find how to encode names + def sanitizeName(name: Name): String = + name.toString.replaceAll("""\!""", """\$bang""") + .replaceAll("""=""", """\$eq""") + .replaceAll("""~""", """\$tilde""") + .replace("-", "$minus") + .replace("+", "$plus") + .replace(">", "$greater") + .replace("<", "$less") + .replace(":", "$colon") + .replace(";", "$u003B") + + // 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) + } + + @noinline + def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, 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) => + 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) + } + def classSig: Unit = { + markClassUsed(sym) + val preRebound = pre.baseType(sym.owner) // #2585 + if (needsJavaSig(preRebound, Nil)) { + val i = builder.length() + jsig(preRebound, existentiallyBound) + 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(Flags.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) + + if (args.nonEmpty) { + builder.append('<') + args foreach argSig + builder.append('>') + } + builder.append(';') + } + + // 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 (isTypeParameterInSig(sym, sym0)) { + assert(!sym.isAliasType, "Unexpected alias type: " + sym) + typeParamSig(sym.name.lastPart) + } + else if (sym == defn.AnyClass || sym == defn.AnyValClass || sym == defn.SingletonClass) + jsig(defn.ObjectType) + 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(sym.info.toTag) + } + else if (sym.isDerivedValueClass) { + val unboxed = sym.derivedValueClassUnbox.info.finalResultType + val unboxedSeen = (tp.memberInfo(sym.derivedValueClassUnbox)).finalResultType + if (unboxedSeen.isPrimitiveValueType && !primitiveOK) + classSig + else + jsig(unboxedSeen, existentiallyBound, toplevel, primitiveOK) + } + else if (tp.isPhantom) + jsig(defn.ErasedPhantomType) + else if (sym.isClass) + classSig + else + jsig(erasure(tp), existentiallyBound, 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 => + val params = mtpe.paramInfos + val restpe = mtpe.resultType + 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, existentiallyBound, toplevel, primitiveOK) + + case hktl: HKTypeLambda => + jsig(hktl.finalResultType) + + case _ => + val etp = erasure(tp) + if (etp eq tp) throw new UnknownSig + else jsig(etp) + } + } + 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 + } + + // TODO: Port from scalac + private object ThrownException { + def unapply(ann: Annotation): Option[Type] = None + } + + class UnknownSig extends Exception + + 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) + } + + // @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(Flags.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 _: TypeRef => + val sym = tp.typeSymbol + if (sym.isAbstractType && (!sym.owner.is(Flags.JavaDefined) || sym.is(Flags.Scala2Existential))) + tp + else + NoType + + case bounds: TypeBounds => + bounds + + case AppliedType(tp, _) => + val sym = tp.typeSymbol + if (sym.isAbstractType && (!sym.owner.is(Flags.JavaDefined) || sym.is(Flags.Scala2Existential))) + tp + else + NoType + + 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[(Int, Type)] = tp.widenDealias match { + case AppliedType(tp, arg :: Nil) => + val test = tp.typeSymbol == defn.ArrayClass + if (!test) return None + genericCore(arg) match { + case NoType => + unapply(arg) match { + case Some((level, core)) => Some((level + 1, core)) + case None => None + } + case core => + Some((1, core)) + } + 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 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(Flags.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 + } } diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6d2dc9ba8079..197d23ee7d90 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -192,6 +192,12 @@ class CompilationTests extends ParallelTesting { compileFilesInDir("../tests/run-no-optimise", defaultOptions) }.checkRuns() + // Generic java signatures tests --------------------------------------------- + + @Test def genericJavaSignatures: Unit = { + compileFilesInDir("../tests/generic-java-signatures", checkGenericJavaSignaturesOptions) + }.checkRuns() + // Pickling Tests ------------------------------------------------------------ // // Pickling tests are very memory intensive and as such need to be run with a diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 7c25cc56ea9f..4a180ec87eb7 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -58,4 +58,5 @@ object TestConfiguration { val scala2Mode = defaultOptions and "-language:Scala2" val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") + val checkGenericJavaSignaturesOptions = defaultOptions and "-Xverify-signatures" } diff --git a/tests/generic-java-signatures/arrayBound.check b/tests/generic-java-signatures/arrayBound.check new file mode 100644 index 000000000000..676a0e43b89d --- /dev/null +++ b/tests/generic-java-signatures/arrayBound.check @@ -0,0 +1,5 @@ +T <: java.lang.Object +U <: T[] +V <: java.util.List +W <: java.util.List +X <: java.util.HashMap> \ No newline at end of file diff --git a/tests/generic-java-signatures/arrayBound.scala b/tests/generic-java-signatures/arrayBound.scala new file mode 100644 index 000000000000..d4f24e7f7ca6 --- /dev/null +++ b/tests/generic-java-signatures/arrayBound.scala @@ -0,0 +1,9 @@ +class Foo[T <: Array[_], U <: Array[T], V <: java.util.List[Array[T]], W <: java.util.List[_ <: java.util.Date], X <: java.util.HashMap[Array[_], java.util.ArrayList[_ <: java.util.Date]]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _, _, _, _]].getTypeParameters() + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/boundParameters.check b/tests/generic-java-signatures/boundParameters.check new file mode 100644 index 000000000000..651cd5d7555b --- /dev/null +++ b/tests/generic-java-signatures/boundParameters.check @@ -0,0 +1,3 @@ +T <: java.util.List +U <: java.util.ArrayList +V <: java.util.ArrayList, java.util.Calendar>> diff --git a/tests/generic-java-signatures/boundParameters.scala b/tests/generic-java-signatures/boundParameters.scala new file mode 100644 index 000000000000..aa80e62d6cb2 --- /dev/null +++ b/tests/generic-java-signatures/boundParameters.scala @@ -0,0 +1,9 @@ +class Foo[T <: java.util.List[_], U <: java.util.ArrayList[java.util.Date], V <: java.util.ArrayList[java.util.HashMap[java.util.HashSet[_], java.util.Calendar]]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _, _]].getTypeParameters() + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/boundsInterfaces.check b/tests/generic-java-signatures/boundsInterfaces.check new file mode 100644 index 000000000000..b2ede085186f --- /dev/null +++ b/tests/generic-java-signatures/boundsInterfaces.check @@ -0,0 +1,2 @@ +T <: java.io.Serializable +U <: java.lang.Object \ No newline at end of file diff --git a/tests/generic-java-signatures/boundsInterfaces.scala b/tests/generic-java-signatures/boundsInterfaces.scala new file mode 100644 index 000000000000..2b0daaa5309a --- /dev/null +++ b/tests/generic-java-signatures/boundsInterfaces.scala @@ -0,0 +1,9 @@ +class Foo[T <: java.io.Serializable, U >: java.lang.Cloneable] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/higherKinded.check b/tests/generic-java-signatures/higherKinded.check new file mode 100644 index 000000000000..373b37e9e2b3 --- /dev/null +++ b/tests/generic-java-signatures/higherKinded.check @@ -0,0 +1,5 @@ +t <: java.lang.Object +u <: java.util.List +v <: java.lang.Object +b <: java.util.List +w <: java.util.HashMap \ No newline at end of file diff --git a/tests/generic-java-signatures/higherKinded.scala b/tests/generic-java-signatures/higherKinded.scala new file mode 100644 index 000000000000..624f122676fe --- /dev/null +++ b/tests/generic-java-signatures/higherKinded.scala @@ -0,0 +1,11 @@ +import scala.language.higherKinds + +class Foo[t[x], u[y] <: java.util.List[y], v[z[a] <: Array[a]], b[B] <: java.util.List[B], w[b[c]] <: java.util.HashMap[Array[Int], b[java.util.Date]]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _, _, _, _]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} diff --git a/tests/generic-java-signatures/lowerBoundClass.check b/tests/generic-java-signatures/lowerBoundClass.check new file mode 100644 index 000000000000..69bfc5eba370 --- /dev/null +++ b/tests/generic-java-signatures/lowerBoundClass.check @@ -0,0 +1,2 @@ +T <: java.util.Calendar +U <: java.lang.Object \ No newline at end of file diff --git a/tests/generic-java-signatures/lowerBoundClass.scala b/tests/generic-java-signatures/lowerBoundClass.scala new file mode 100644 index 000000000000..44a0e9cf7f97 --- /dev/null +++ b/tests/generic-java-signatures/lowerBoundClass.scala @@ -0,0 +1,9 @@ +class Foo[T <: java.util.Calendar, U >: java.util.Date] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} diff --git a/tests/generic-java-signatures/primitiveArrayBound.scala b/tests/generic-java-signatures/primitiveArrayBound.scala new file mode 100644 index 000000000000..21efdbb56c95 --- /dev/null +++ b/tests/generic-java-signatures/primitiveArrayBound.scala @@ -0,0 +1,9 @@ +class Foo[T <: Array[_ <: Int]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/primitiveArrayBounds.check b/tests/generic-java-signatures/primitiveArrayBounds.check new file mode 100644 index 000000000000..ae9f2ff447b2 --- /dev/null +++ b/tests/generic-java-signatures/primitiveArrayBounds.check @@ -0,0 +1 @@ +T <: java.lang.Object \ No newline at end of file diff --git a/tests/generic-java-signatures/primitives.check b/tests/generic-java-signatures/primitives.check new file mode 100644 index 000000000000..6f051b52a94a --- /dev/null +++ b/tests/generic-java-signatures/primitives.check @@ -0,0 +1,9 @@ +A <: byte[] +B <: char[] +C <: double[] +D <: float[] +E <: int[] +F <: long[] +G <: short[] +H <: boolean[] +I <: java.lang.Object \ No newline at end of file diff --git a/tests/generic-java-signatures/primitives.scala b/tests/generic-java-signatures/primitives.scala new file mode 100644 index 000000000000..8f75309096fc --- /dev/null +++ b/tests/generic-java-signatures/primitives.scala @@ -0,0 +1,17 @@ +class Foo[A <: Array[Byte], +B <: Array[Char], +C <: Array[Double], +D <: Array[Float], +E <: Array[Int], +F <: Array[Long], +G <: Array[Short], +H <: Array[Boolean], +I <: Array[_ <: Byte]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _, _, _, _, _, _, _, _]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds().map(_.getTypeName).mkString(", ")) + } + } +} diff --git a/tests/generic-java-signatures/simple.check b/tests/generic-java-signatures/simple.check new file mode 100644 index 000000000000..9efe27b7f53b --- /dev/null +++ b/tests/generic-java-signatures/simple.check @@ -0,0 +1,3 @@ +T +U +LongerName \ No newline at end of file diff --git a/tests/generic-java-signatures/simple.scala b/tests/generic-java-signatures/simple.scala new file mode 100644 index 000000000000..b8c3be1bffd9 --- /dev/null +++ b/tests/generic-java-signatures/simple.scala @@ -0,0 +1,7 @@ +class Foo[T, U, LongerName] +object Test { + def main(args: Array[String]): Unit = { + val typeParams = classOf[Foo[_, _, _]].getTypeParameters + typeParams.foreach(tp => println(tp.getName)) + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/simpleBoundParameters.check b/tests/generic-java-signatures/simpleBoundParameters.check new file mode 100644 index 000000000000..99479bc97fd4 --- /dev/null +++ b/tests/generic-java-signatures/simpleBoundParameters.check @@ -0,0 +1 @@ +T <: java.util.List \ No newline at end of file diff --git a/tests/generic-java-signatures/simpleBoundParameters.scala b/tests/generic-java-signatures/simpleBoundParameters.scala new file mode 100644 index 000000000000..beb99cba299a --- /dev/null +++ b/tests/generic-java-signatures/simpleBoundParameters.scala @@ -0,0 +1,9 @@ +class Foo[T <: java.util.List[_]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_]].getTypeParameters() + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/superClassParams.check b/tests/generic-java-signatures/superClassParams.check new file mode 100644 index 000000000000..83245dabac73 --- /dev/null +++ b/tests/generic-java-signatures/superClassParams.check @@ -0,0 +1,4 @@ +B: A +C: A +D: A +E: A \ No newline at end of file diff --git a/tests/generic-java-signatures/superClassParams.scala b/tests/generic-java-signatures/superClassParams.scala new file mode 100644 index 000000000000..730f3ab684a3 --- /dev/null +++ b/tests/generic-java-signatures/superClassParams.scala @@ -0,0 +1,13 @@ +class A[T] +class B extends A[Int] +class C extends A[String] +class D extends A[java.util.List[Int]] +class E extends A[java.util.List[String]] +object Test { + def main(args: Array[String]): Unit = { + println("B: " + classOf[B].getGenericSuperclass.getTypeName) + println("C: " + classOf[C].getGenericSuperclass.getTypeName) + println("D: " + classOf[B].getGenericSuperclass.getTypeName) + println("E: " + classOf[C].getGenericSuperclass.getTypeName) + } +} \ No newline at end of file diff --git a/tests/generic-java-signatures/valueClassBound.check b/tests/generic-java-signatures/valueClassBound.check new file mode 100644 index 000000000000..05f335c6fe38 --- /dev/null +++ b/tests/generic-java-signatures/valueClassBound.check @@ -0,0 +1 @@ +T <: VC \ No newline at end of file diff --git a/tests/generic-java-signatures/valueClassBound.scala b/tests/generic-java-signatures/valueClassBound.scala new file mode 100644 index 000000000000..76711dd500a9 --- /dev/null +++ b/tests/generic-java-signatures/valueClassBound.scala @@ -0,0 +1,10 @@ +class VC(val x: Int) extends AnyVal +class Foo[T <: VC] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} diff --git a/tests/generic-java-signatures/wildcards.check b/tests/generic-java-signatures/wildcards.check new file mode 100644 index 000000000000..c4857cf86088 --- /dev/null +++ b/tests/generic-java-signatures/wildcards.check @@ -0,0 +1,3 @@ +T <: java.util.List +U <: java.util.HashMap, java.util.Set> +V <: java.util.Queue \ No newline at end of file diff --git a/tests/generic-java-signatures/wildcards.scala b/tests/generic-java-signatures/wildcards.scala new file mode 100644 index 000000000000..ee7b71f5b612 --- /dev/null +++ b/tests/generic-java-signatures/wildcards.scala @@ -0,0 +1,9 @@ +class Foo[T <: java.util.List[_ <: java.util.Calendar], U <: java.util.HashMap[java.util.List[_ <: T], java.util.Set[_ >: T]], V <: java.util.Queue[Int]] +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_, _, _]].getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds().map(_.getTypeName).mkString(", ")) + } + } +} \ No newline at end of file From ced86e4c3ce952a61897ed95c8b8d6fcfacb31a0 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 29 Sep 2017 09:44:22 +0200 Subject: [PATCH 02/13] Add thrown exceptions to generic java signature --- .../dotty/tools/dotc/transform/Erasure.scala | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 65dbcf186c3a..240af6e00080 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -1035,11 +1035,23 @@ object Erasure { else None } - // TODO: Port from scalac - private object ThrownException { - def unapply(ann: Annotation): Option[Type] = None + /** 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. + */ + 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 + } + } } + class UnknownSig extends Exception private def needsJavaSig(tp: Type, throwsArgs: List[Type])(implicit ctx: Context): Boolean = !ctx.settings.YnoGenericSig.value && { From 2152bf5665c48e34a8b23755b19b0a61fee3a072 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Fri, 29 Sep 2017 15:52:51 +0200 Subject: [PATCH 03/13] Generate signature for forwarders --- .../tools/backend/jvm/DottyBackendInterface.scala | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index c4cff9349ba7..22f38b592208 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -549,6 +549,20 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma } } + def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = { + // scala/bug#3452 Static forwarder generation uses the same erased signature as the method if forwards to. + // By rights, it should use the signature as-seen-from the module class, and add suitable + // primitive and value-class boxing/unboxing. + // But for now, just like we did in mixin, we just avoid writing a wrong generic signature + // (one that doesn't erase to the actual signature). See run/t3452b for a test case. + + val memberTpe = ctx.atPhase(ctx.erasurePhase) { implicit ctx => moduleClass.denot.thisType.memberInfo(sym) } + val erasedMemberType = TypeErasure.erasure(memberTpe) + if (erasedMemberType =:= sym.denot.info) + getGenericSignature(sym, moduleClass, memberTpe).orNull + else null + } + private def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type)(implicit ctx: Context): Option[String] = if (needsGenericSignature(sym)) { val erasedTypeSym = sym.denot.info.typeSymbol @@ -570,7 +584,6 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma None } - def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = null // todo: implement def sourceFileFor(cu: CompilationUnit): String = cu.source.file.name From 96505c04d0b21de93b36ef08e00e9b82df4b1a3b Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 2 Oct 2017 08:43:08 +0200 Subject: [PATCH 04/13] Don't need to call `atPhase` here --- .../src/dotty/tools/backend/jvm/DottyBackendInterface.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 22f38b592208..5c116616ea04 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -569,11 +569,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma if (erasedTypeSym.isPrimitiveValueClass) { None } else { - val jsOpt = - ctx.atPhase(ctx.erasurePhase) { implicit ctx => - Erasure.javaSig(sym, memberTpe, _ => ()) - } - + val jsOpt = Erasure.javaSig(sym, memberTpe, _ => ()) if (ctx.settings.XverifySignatures.value) { jsOpt.foreach(verifySignature(sym, _)) } From ad952a6d968a7c143b665673e5bccb986a4fbd05 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 3 Oct 2017 09:42:18 +0200 Subject: [PATCH 05/13] Don't generate signatures for backticked-names --- .../dotty/tools/dotc/transform/Erasure.scala | 22 +++++++++---------- .../invalidNames.check | 0 .../invalidNames.scala | 10 +++++++++ .../mangledNames.check | 1 + .../mangledNames.scala | 10 +++++++++ 5 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 tests/generic-java-signatures/invalidNames.check create mode 100644 tests/generic-java-signatures/invalidNames.scala create mode 100644 tests/generic-java-signatures/mangledNames.check create mode 100644 tests/generic-java-signatures/mangledNames.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 240af6e00080..bb30e03283ae 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -839,17 +839,17 @@ object Erasure { } } - // TODO: Find how to encode names - def sanitizeName(name: Name): String = - name.toString.replaceAll("""\!""", """\$bang""") - .replaceAll("""=""", """\$eq""") - .replaceAll("""~""", """\$tilde""") - .replace("-", "$minus") - .replace("+", "$plus") - .replace(">", "$greater") - .replace("<", "$less") - .replace(":", "$colon") - .replace(";", "$u003B") + // 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 diff --git a/tests/generic-java-signatures/invalidNames.check b/tests/generic-java-signatures/invalidNames.check new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/generic-java-signatures/invalidNames.scala b/tests/generic-java-signatures/invalidNames.scala new file mode 100644 index 000000000000..9dbf9da3bdb6 --- /dev/null +++ b/tests/generic-java-signatures/invalidNames.scala @@ -0,0 +1,10 @@ +class Foo[`![]:;!!` <: java.util.Date] + +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_]].getTypeParameters() + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} diff --git a/tests/generic-java-signatures/mangledNames.check b/tests/generic-java-signatures/mangledNames.check new file mode 100644 index 000000000000..b8c936c4a983 --- /dev/null +++ b/tests/generic-java-signatures/mangledNames.check @@ -0,0 +1 @@ +$bang$bang$bang <: java.util.Date diff --git a/tests/generic-java-signatures/mangledNames.scala b/tests/generic-java-signatures/mangledNames.scala new file mode 100644 index 000000000000..b57aff3d4018 --- /dev/null +++ b/tests/generic-java-signatures/mangledNames.scala @@ -0,0 +1,10 @@ +class Foo[`!!!` <: java.util.Date] + +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo[_]].getTypeParameters() + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} From 797a9412a53f7a7eb9fd9eece32899eb2400e17e Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 3 Oct 2017 11:52:45 +0200 Subject: [PATCH 06/13] Exclude phantom parameters from methods signatures --- .../dotty/tools/dotc/transform/Erasure.scala | 3 ++- tests/generic-java-signatures/phantom.check | 3 +++ tests/generic-java-signatures/phantom.scala | 21 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/generic-java-signatures/phantom.check create mode 100644 tests/generic-java-signatures/phantom.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index bb30e03283ae..46bfcfedcba3 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -981,7 +981,8 @@ object Erasure { methodResultSig(restpe) case mtpe: MethodType => - val params = mtpe.paramInfos + // phantom method parameters do not make it to the bytecode. + val params = mtpe.paramInfos.filterNot(_.isPhantom) val restpe = mtpe.resultType builder.append('(') // TODO: Update once we support varargs diff --git a/tests/generic-java-signatures/phantom.check b/tests/generic-java-signatures/phantom.check new file mode 100644 index 000000000000..1efc1bc17904 --- /dev/null +++ b/tests/generic-java-signatures/phantom.check @@ -0,0 +1,3 @@ +public T MyOtherPhantom$.f1(int,int) +U <: java.lang.Object +T <: dotty.runtime.ErasedPhantom diff --git a/tests/generic-java-signatures/phantom.scala b/tests/generic-java-signatures/phantom.scala new file mode 100644 index 000000000000..0c5983253534 --- /dev/null +++ b/tests/generic-java-signatures/phantom.scala @@ -0,0 +1,21 @@ +object MyOtherPhantom extends Phantom { + type MyPhantom[V] <: this.Any + def myPhantom[X]: MyPhantom[X] = assume + + def f1[U, T <: MyPhantom[U]](a: Int, b: T, c: Int): T = b + + def f2 = { + f1(3, myPhantom[Int], 2) + } +} + +object Test { + def main(args: Array[String]): Unit = { + val f1 = MyOtherPhantom.getClass.getMethods.find(_.getName.endsWith("f1")).get + val tParams = f1.getTypeParameters + println(f1.toGenericString) + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} From 3f6af19108e94854c3852f090e282cc67aa83e65 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 3 Oct 2017 13:49:19 +0200 Subject: [PATCH 07/13] Remove unused parameters from scalac port --- .../backend/jvm/DottyBackendInterface.scala | 2 +- .../dotty/tools/dotc/transform/Erasure.scala | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 5c116616ea04..962d62afc1a0 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -569,7 +569,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma if (erasedTypeSym.isPrimitiveValueClass) { None } else { - val jsOpt = Erasure.javaSig(sym, memberTpe, _ => ()) + val jsOpt = Erasure.javaSig(sym, memberTpe) if (ctx.settings.XverifySignatures.value) { jsOpt.foreach(verifySignature(sym, _)) } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 46bfcfedcba3..23ae581e7d56 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -768,11 +768,11 @@ object Erasure { ) } - final def javaSig(sym0: Symbol, info: Type, markClassUsed: Symbol => Unit)(implicit ctx: Context): Option[String] = - ctx.atPhase(ctx.erasurePhase) { implicit ctx => javaSig0(sym0, info, markClassUsed) } + final def javaSig(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = + ctx.atPhase(ctx.erasurePhase) { implicit ctx => javaSig0(sym0, info) } @noinline - private final def javaSig0(sym0: Symbol, info: Type, markClassUsed: Symbol => Unit)(implicit ctx: Context): Option[String] = { + private final def javaSig0(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = { val builder = new StringBuilder(64) val isTraitSignature = sym0.enclosingClass.is(Flags.Trait) @@ -860,7 +860,7 @@ object Erasure { } @noinline - def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, toplevel: Boolean = false, primitiveOK: Boolean = true): Unit = { + def jsig(tp0: Type, toplevel: Boolean = false, primitiveOK: Boolean = true): Unit = { val tp = tp0.dealias tp match { @@ -890,11 +890,10 @@ object Erasure { boxedSig(tp) } def classSig: Unit = { - markClassUsed(sym) val preRebound = pre.baseType(sym.owner) // #2585 if (needsJavaSig(preRebound, Nil)) { val i = builder.length() - jsig(preRebound, existentiallyBound) + 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 @@ -952,14 +951,14 @@ object Erasure { if (unboxedSeen.isPrimitiveValueType && !primitiveOK) classSig else - jsig(unboxedSeen, existentiallyBound, toplevel, primitiveOK) + jsig(unboxedSeen, toplevel, primitiveOK) } else if (tp.isPhantom) jsig(defn.ErasedPhantomType) else if (sym.isClass) classSig else - jsig(erasure(tp), existentiallyBound, toplevel, primitiveOK) + jsig(erasure(tp), toplevel, primitiveOK) case ExprType(restpe) if toplevel => builder.append("()") @@ -1010,7 +1009,7 @@ object Erasure { superSig(ci.typeSymbol, ci.parents) case AnnotatedType(atp, _) => - jsig(atp, existentiallyBound, toplevel, primitiveOK) + jsig(atp, toplevel, primitiveOK) case hktl: HKTypeLambda => jsig(hktl.finalResultType) From 86e8779d28aecebbc2a4c3e6f8c0858c37320ce6 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 10 Oct 2017 09:40:47 +0200 Subject: [PATCH 08/13] Fix generic signatures for nested and types --- .../src/dotty/tools/dotc/transform/Erasure.scala | 2 +- tests/generic-java-signatures/andTypes.check | 1 + tests/generic-java-signatures/andTypes.scala | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 tests/generic-java-signatures/andTypes.check create mode 100644 tests/generic-java-signatures/andTypes.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 23ae581e7d56..1384084ce09c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -740,7 +740,7 @@ object Erasure { } private def hiBounds(bounds: TypeBounds)(implicit ctx: Context): List[Type] = bounds.hi.widenDealias match { - case AndType(tp1, tp2) => tp1 :: tp2 :: Nil + case AndType(tp1, tp2) => hiBounds(tp1.bounds) ::: hiBounds(tp2.bounds) case tp => tp :: Nil } diff --git a/tests/generic-java-signatures/andTypes.check b/tests/generic-java-signatures/andTypes.check new file mode 100644 index 000000000000..0533e202a51f --- /dev/null +++ b/tests/generic-java-signatures/andTypes.check @@ -0,0 +1 @@ +T <: C1, T1, T2 diff --git a/tests/generic-java-signatures/andTypes.scala b/tests/generic-java-signatures/andTypes.scala new file mode 100644 index 000000000000..b4557fd56b6f --- /dev/null +++ b/tests/generic-java-signatures/andTypes.scala @@ -0,0 +1,16 @@ +class C1 +trait T1 +trait T2 + +class Foo { + def foo[T <: (C1 & T1) & T2] = () +} + +object Test { + def main(args: Array[String]): Unit = { + val tParams = classOf[Foo].getDeclaredMethod("foo").getTypeParameters + tParams.foreach { tp => + println(tp.getName + " <: " + tp.getBounds.map(_.getTypeName).mkString(", ")) + } + } +} From 5fc95ddc6e1bf92f33a5f00945253c045cf2d41b Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 10 Oct 2017 09:45:44 +0200 Subject: [PATCH 09/13] Address most of review comments --- .../src/dotty/tools/dotc/core/Symbols.scala | 25 ------------------- .../dotty/tools/dotc/transform/Erasure.scala | 16 ++++++------ 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 6cffd6d75419..2e3661a52039 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -530,18 +530,6 @@ object Symbols { /** The current name of this symbol */ final def name(implicit ctx: Context): ThisName = denot.name.asInstanceOf[ThisName] - def isHigherOrderTypeParameter(implicit ctx: Context): Boolean = this.maybeOwner.isTypeParameterOrSkolem - def isTypeParameterOrSkolem(implicit ctx: Context): Boolean = this.isTypeParam - def enclClassChain(implicit ctx: Context): List[Symbol] = this.maybeOwner.enclClassChain - final def isDerivedValueClass(implicit ctx: Context): Boolean = - isClass && !denot.is(Flags.Package) && !denot.is(Flags.Trait) && - !ctx.phase.erasedTypes && denot.info.firstParent.typeSymbol == defn.AnyValClass && !denot.isPrimitiveValueClass - - /** If this is a derived value class, return its unbox method - * or NoSymbol if it does not exist. - */ - def derivedValueClassUnbox(implicit ctx: Context): Symbol = NoSymbol - /** The source or class file from which this class or * the class containing this symbol was generated, null if not applicable. * Overridden in ClassSymbol @@ -648,30 +636,17 @@ object Symbols { denot.asInstanceOf[ClassDenotation] override protected def prefixString = "ClassSymbol" - - override def enclClassChain(implicit ctx: Context): List[Symbol] = - if (this.is(Flags.PackageClass)) Nil - else this :: denot.owner.enclClassChain - - override def derivedValueClassUnbox(implicit ctx: Context): Symbol = - // (info.decl(nme.unbox)) orElse uncomment once we accept unbox methods - (denot.info.decls.find(_.denot.is(Flags.ParamAccessor | Flags.Method))) - } class ErrorSymbol(val underlying: Symbol, msg: => String)(implicit ctx: Context) extends Symbol(NoCoord, ctx.nextId) { type ThisName = underlying.ThisName denot = underlying.denot - - override def enclClassChain(implicit ctx: Context): List[Symbol] = Nil } @sharable object NoSymbol extends Symbol(NoCoord, 0) { denot = NoDenotation override def associatedFile(implicit ctx: Context): AbstractFile = NoSource.file override def recomputeDenot(lastd: SymDenotation)(implicit ctx: Context): SymDenotation = NoDenotation - - override def enclClassChain(implicit ctx: Context): List[Symbol] = Nil } implicit class Copier[N <: Name](sym: Symbol { type ThisName = N })(implicit ctx: Context) { diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 1384084ce09c..f807d87c0d04 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -751,7 +751,7 @@ object Erasure { case GenericArray(level, core) if !(core <:< defn.AnyRefType) => level case AndType(tp1, tp2) => - Math.max(unboundedGenericArrayLevel(tp1), unboundedGenericArrayLevel(tp2)) + unboundedGenericArrayLevel(tp1) max unboundedGenericArrayLevel(tp2) case _ => 0 } @@ -761,15 +761,15 @@ object Erasure { // * 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.isHigherOrderTypeParameter && - sym.isTypeParameterOrSkolem && ( - (initialSymbol.enclClassChain.exists(sym isContainedIn _)) || + !sym.maybeOwner.isTypeParam && + sym.isTypeParam && ( + sym.isContainedIn(initialSymbol.topLevelClass) || (initialSymbol.is(Flags.Method) && initialSymbol.typeParams.contains(sym)) ) } final def javaSig(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = - ctx.atPhase(ctx.erasurePhase) { implicit ctx => javaSig0(sym0, info) } + javaSig0(sym0, info)(ctx.withPhase(ctx.erasurePhase)) @noinline private final def javaSig0(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = { @@ -945,9 +945,9 @@ object Erasure { else if (sym == defn.UnitClass) jsig(defn.BoxedUnitType) else builder.append(sym.info.toTag) } - else if (sym.isDerivedValueClass) { - val unboxed = sym.derivedValueClassUnbox.info.finalResultType - val unboxedSeen = (tp.memberInfo(sym.derivedValueClassUnbox)).finalResultType + 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 else From d969d413c5fa32a6778a98bb6375b74e47af89c3 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 10 Oct 2017 10:30:16 +0200 Subject: [PATCH 10/13] Move signature generation to its own object --- .../backend/jvm/DottyBackendInterface.scala | 4 +- .../dotty/tools/dotc/transform/Erasure.scala | 497 ----------------- .../dotc/transform/GenericSignatures.scala | 520 ++++++++++++++++++ 3 files changed, 522 insertions(+), 499 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 962d62afc1a0..9f49d7fd09a7 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -5,7 +5,7 @@ import dotty.tools.dotc.ast.Trees import dotty.tools.dotc import dotty.tools.dotc.backend.jvm.DottyPrimitives import dotty.tools.dotc.core.Flags.FlagSet -import dotty.tools.dotc.transform.Erasure +import dotty.tools.dotc.transform.{Erasure, GenericSignatures} import dotty.tools.dotc.transform.SymUtils._ import java.io.{File => JFile} @@ -569,7 +569,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma if (erasedTypeSym.isPrimitiveValueClass) { None } else { - val jsOpt = Erasure.javaSig(sym, memberTpe) + val jsOpt = GenericSignatures.javaSig(sym, memberTpe) if (ctx.settings.XverifySignatures.value) { jsOpt.foreach(verifySignature(sym, _)) } diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index f807d87c0d04..ecb40b52a910 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -30,10 +30,6 @@ import ExplicitOuter._ import core.Mode import core.PhantomErasure import reporting.trace -import dotty.tools.dotc.core.Annotations.Annotation -import dotty.tools.dotc.core.classfile.ClassfileConstants -import dotty.tools.dotc.core.TypeApplications.TypeParamInfo -import java.lang.StringBuilder class Erasure extends Phase with DenotTransformer { @@ -680,497 +676,4 @@ object Erasure { private def wasPhantom(tp: Type)(implicit ctx: Context): Boolean = tp.widenDealias.classSymbol eq defn.ErasedPhantomClass - - /** 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.typeParams.head.paramInfo))) - } 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.typeSymbol - psym.ensureCompleted() - psym.isClass && !psym.is(Flags.Trait) && isUnshadowed(psym) - } - (if (cs.hasNext) cs else parents.iterator.filter(p => isUnshadowed(p.typeSymbol))).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(Flags.Trait) || ts.is(Flags.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(level, core) 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(Flags.Method) && initialSymbol.typeParams.contains(sym)) - ) - } - - final def javaSig(sym0: Symbol, info: Type)(implicit ctx: Context): Option[String] = - 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(Flags.Trait) - - def superSig(cls: Symbol, parents: List[Type]): Unit = { - def isInterfaceOrTrait(sym: Symbol) = sym.is(Flags.PureInterface) || sym.is(Flags.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.typeSymbol)) - else minParents - - val ps = ensureClassAsFirstParent(validParents) - ps.foreach(boxedSig) - } - - def boxedSig(tp: Type): Unit = jsig(tp, primitiveOK = false) - - def boundsSig(bounds: List[Type]): Unit = { - val (isTrait, isClass) = bounds partition (_.typeSymbol.is(Flags.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) - } - - @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) => - 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) - } - def classSig: Unit = { - 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(Flags.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) - - if (args.nonEmpty) { - builder.append('<') - args foreach argSig - builder.append('>') - } - builder.append(';') - } - - // 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 (isTypeParameterInSig(sym, sym0)) { - assert(!sym.isAliasType, "Unexpected alias type: " + sym) - typeParamSig(sym.name.lastPart) - } - else if (sym == defn.AnyClass || sym == defn.AnyValClass || sym == defn.SingletonClass) - jsig(defn.ObjectType) - 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(sym.info.toTag) - } - 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 - else - jsig(unboxedSeen, toplevel, primitiveOK) - } - else if (tp.isPhantom) - jsig(defn.ErasedPhantomType) - else if (sym.isClass) - classSig - 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 => - // phantom method parameters do not make it to the bytecode. - val params = mtpe.paramInfos.filterNot(_.isPhantom) - val restpe = mtpe.resultType - 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) - - case _ => - val etp = erasure(tp) - if (etp eq tp) throw new UnknownSig - else jsig(etp) - } - } - 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 - } - - /** 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. - */ - 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 - } - } - } - - - class UnknownSig extends Exception - - 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) - } - - // @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(Flags.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 _: TypeRef => - val sym = tp.typeSymbol - if (sym.isAbstractType && (!sym.owner.is(Flags.JavaDefined) || sym.is(Flags.Scala2Existential))) - tp - else - NoType - - case bounds: TypeBounds => - bounds - - case AppliedType(tp, _) => - val sym = tp.typeSymbol - if (sym.isAbstractType && (!sym.owner.is(Flags.JavaDefined) || sym.is(Flags.Scala2Existential))) - tp - else - NoType - - 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[(Int, Type)] = tp.widenDealias match { - case AppliedType(tp, arg :: Nil) => - val test = tp.typeSymbol == defn.ArrayClass - if (!test) return None - genericCore(arg) match { - case NoType => - unapply(arg) match { - case Some((level, core)) => Some((level + 1, core)) - case None => None - } - case core => - Some((1, core)) - } - 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 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(Flags.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 - } } diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala new file mode 100644 index 000000000000..846ac8faaae5 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -0,0 +1,520 @@ +package dotty.tools +package dotc +package transform + +import core.Annotations.Annotation +import core.Contexts.Context +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 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] = + 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.typeSymbol)) + else minParents + + val ps = ensureClassAsFirstParent(validParents) + ps.foreach(boxedSig) + } + + def boxedSig(tp: Type): Unit = jsig(tp, 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) + } + + @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) => + 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) + } + def classSig: Unit = { + 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) + + if (args.nonEmpty) { + builder.append('<') + args foreach argSig + builder.append('>') + } + builder.append(';') + } + + // 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 (isTypeParameterInSig(sym, sym0)) { + assert(!sym.isAliasType, "Unexpected alias type: " + sym) + typeParamSig(sym.name.lastPart) + } + else if (sym == defn.AnyClass || sym == defn.AnyValClass || sym == defn.SingletonClass) + jsig(defn.ObjectType) + 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(sym.info.toTag) + } + 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 + else + jsig(unboxedSeen, toplevel, primitiveOK) + } + else if (tp.isPhantom) + jsig(defn.ErasedPhantomType) + else if (sym.isClass) + classSig + 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 => + // phantom method parameters do not make it to the bytecode. + val params = mtpe.paramInfos.filterNot(_.isPhantom) + val restpe = mtpe.resultType + 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) + + case _ => + val etp = erasure(tp) + if (etp eq tp) throw new UnknownSig + else jsig(etp) + } + } + 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.typeParams.head.paramInfo))) + } 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.typeSymbol + psym.ensureCompleted() + psym.isClass && !psym.is(Trait) && isUnshadowed(psym) + } + (if (cs.hasNext) cs else parents.iterator.filter(p => isUnshadowed(p.typeSymbol))).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(level, core) 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 _: TypeRef => + val sym = tp.typeSymbol + if (sym.isAbstractType && (!sym.owner.is(JavaDefined) || sym.is(Scala2Existential))) + tp + else + NoType + + case bounds: TypeBounds => + bounds + + case AppliedType(tp, _) => + val sym = tp.typeSymbol + if (sym.isAbstractType && (!sym.owner.is(JavaDefined) || sym.is(Scala2Existential))) + tp + else + NoType + + 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[(Int, Type)] = tp.widenDealias match { + case AppliedType(tp, arg :: Nil) => + val test = tp.typeSymbol == defn.ArrayClass + if (!test) return None + genericCore(arg) match { + case NoType => + unapply(arg) match { + case Some((level, core)) => Some((level + 1, core)) + case None => None + } + case core => + Some((1, core)) + } + 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 + } +} From 94203bc816cd8e7b4796e814c71029bd9634fa05 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 10 Oct 2017 10:54:49 +0200 Subject: [PATCH 11/13] Move type to JVM tag conversion to Definitions --- .../dotty/tools/dotc/core/Definitions.scala | 25 +++++++++++-------- .../src/dotty/tools/dotc/core/NameOps.scala | 4 +-- .../src/dotty/tools/dotc/core/Types.scala | 18 ------------- .../dotc/transform/GenericSignatures.scala | 2 +- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 966d7e268270..5e03220467be 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -394,9 +394,9 @@ class Definitions { def ArrayModule(implicit ctx: Context) = ArrayModuleType.symbol.moduleClass.asClass - lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc) + lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void) def UnitClass(implicit ctx: Context) = UnitType.symbol.asClass - lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc) + lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc, nme.specializedTypeNames.Boolean) def BooleanClass(implicit ctx: Context) = BooleanType.symbol.asClass lazy val Boolean_notR = BooleanClass.requiredMethodRef(nme.UNARY_!) def Boolean_! = Boolean_notR.symbol @@ -415,13 +415,13 @@ class Definitions { }) def Boolean_!= = Boolean_neqeqR.symbol - lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc) + lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc, nme.specializedTypeNames.Byte) def ByteClass(implicit ctx: Context) = ByteType.symbol.asClass - lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc) + lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc, nme.specializedTypeNames.Short) def ShortClass(implicit ctx: Context) = ShortType.symbol.asClass - lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc) + lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc, nme.specializedTypeNames.Char) def CharClass(implicit ctx: Context) = CharType.symbol.asClass - lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc) + lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc, nme.specializedTypeNames.Int) def IntClass(implicit ctx: Context) = IntType.symbol.asClass lazy val Int_minusR = IntClass.requiredMethodRef(nme.MINUS, List(IntType)) def Int_- = Int_minusR.symbol @@ -437,7 +437,7 @@ class Definitions { def Int_>= = Int_geR.symbol lazy val Int_leR = IntClass.requiredMethodRef(nme.LE, List(IntType)) def Int_<= = Int_leR.symbol - lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc) + lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc, nme.specializedTypeNames.Long) def LongClass(implicit ctx: Context) = LongType.symbol.asClass lazy val Long_XOR_Long = LongType.member(nme.XOR).requiredSymbol( x => (x is Method) && (x.info.firstParamTypes.head isRef defn.LongClass) @@ -452,9 +452,9 @@ class Definitions { lazy val Long_divR = LongClass.requiredMethodRef(nme.DIV, List(LongType)) def Long_/ = Long_divR.symbol - lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc) + lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc, nme.specializedTypeNames.Float) def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass - lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc) + lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc, nme.specializedTypeNames.Double) def DoubleClass(implicit ctx: Context) = DoubleType.symbol.asClass lazy val BoxedUnitType: TypeRef = ctx.requiredClassRef("scala.runtime.BoxedUnit") @@ -976,15 +976,17 @@ class Definitions { private val boxedTypes = mutable.Map[TypeName, TypeRef]() private val valueTypeEnc = mutable.Map[TypeName, PrimitiveClassEnc]() + private val typeTags = mutable.Map[TypeName, Name]().withDefaultValue(nme.specializedTypeNames.Object) // private val unboxedTypeRef = mutable.Map[TypeName, TypeRef]() // private val javaTypeToValueTypeRef = mutable.Map[Class[_], TypeRef]() // private val valueTypeNameToJavaType = mutable.Map[TypeName, Class[_]]() - private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int): TypeRef = { + private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int, tag: Name): TypeRef = { val vcls = ctx.requiredClassRef(name) boxedTypes(vcls.name) = boxed valueTypeEnc(vcls.name) = enc + typeTags(vcls.name) = tag // unboxedTypeRef(boxed.name) = vcls // javaTypeToValueTypeRef(jtype) = vcls // valueTypeNameToJavaType(vcls.name) = jtype @@ -994,6 +996,9 @@ class Definitions { /** The type of the boxed class corresponding to primitive value type `tp`. */ def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp)) + /** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */ + def typeTag(tp: Type)(implicit ctx: Context): Name = typeTags(scalaClassName(tp)) + type PrimitiveClassEnc = Int val ByteEnc = 2 diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 9c9e40cf00ea..4e568861e401 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -229,8 +229,8 @@ object NameOps { def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = { - val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => x._1.toTag) - val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => x._1.toTag) + val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) + val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1)) name.likeSpaced(name ++ nme.specializedTypeNames.prefix ++ methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 996dda686c84..5509415d827d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -745,24 +745,6 @@ object Types { else ctx.asSeenFrom(this, pre, cls) } - /** The JVM tag for this type, if it's a primitive, `java.lang.Object` otherwise. */ - final def toTag(implicit ctx: Context): Name = { - classSymbol match { - case t if t eq defn.IntClass => nme.specializedTypeNames.Int - case t if t eq defn.BooleanClass => nme.specializedTypeNames.Boolean - case t if t eq defn.ByteClass => nme.specializedTypeNames.Byte - case t if t eq defn.LongClass => nme.specializedTypeNames.Long - case t if t eq defn.ShortClass => nme.specializedTypeNames.Short - case t if t eq defn.FloatClass => nme.specializedTypeNames.Float - case t if t eq defn.UnitClass => nme.specializedTypeNames.Void - case t if t eq defn.DoubleClass => nme.specializedTypeNames.Double - case t if t eq defn.CharClass => nme.specializedTypeNames.Char - case _ => nme.specializedTypeNames.Object - } - } - - - // ----- Subtype-related -------------------------------------------- /** Is this type a subtype of that type? */ diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 846ac8faaae5..ce55ab4cb3ea 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -202,7 +202,7 @@ object GenericSignatures { else if (sym.isPrimitiveValueClass) { if (!primitiveOK) jsig(defn.ObjectType) else if (sym == defn.UnitClass) jsig(defn.BoxedUnitType) - else builder.append(sym.info.toTag) + else builder.append(defn.typeTag(sym.info)) } else if (ValueClasses.isDerivedValueClass(sym)) { val unboxed = ValueClasses.valueClassUnbox(sym.asClass).info.finalResultType From 3f4be93d7d2fae9616e338310634a40583d02387 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 10 Oct 2017 11:24:52 +0200 Subject: [PATCH 12/13] Simplify `GenericArray` extractor --- .../dotc/transform/GenericSignatures.scala | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index ce55ab4cb3ea..d50f2e11ab38 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -363,7 +363,7 @@ object GenericSignatures { * e.g. with "tagged types" like Array[Int] with T. */ private def unboundedGenericArrayLevel(tp: Type)(implicit ctx: Context): Int = tp match { - case GenericArray(level, core) if !(core <:< defn.AnyRefType) => + case GenericArray(core, level) if !(core <:< defn.AnyRefType) => level case AndType(tp1, tp2) => unboundedGenericArrayLevel(tp1) max unboundedGenericArrayLevel(tp2) @@ -426,8 +426,7 @@ object GenericSignatures { * 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 _: TypeRef => - val sym = tp.typeSymbol + case RefOrAppliedType(sym, tp, _) => if (sym.isAbstractType && (!sym.owner.is(JavaDefined) || sym.is(Scala2Existential))) tp else @@ -436,13 +435,6 @@ object GenericSignatures { case bounds: TypeBounds => bounds - case AppliedType(tp, _) => - val sym = tp.typeSymbol - if (sym.isAbstractType && (!sym.owner.is(JavaDefined) || sym.is(Scala2Existential))) - tp - else - NoType - case _ => NoType } @@ -451,18 +443,16 @@ object GenericSignatures { * 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[(Int, Type)] = tp.widenDealias match { - case AppliedType(tp, arg :: Nil) => - val test = tp.typeSymbol == defn.ArrayClass - if (!test) return None + def unapply(tp: Type)(implicit ctx: Context): Option[(Type, Int)] = tp.widenDealias match { + case defn.ArrayOf(arg) => genericCore(arg) match { case NoType => - unapply(arg) match { - case Some((level, core)) => Some((level + 1, core)) - case None => None + arg match { + case GenericArray(core, level) => Some((core, level + 1)) + case _ => None } case core => - Some((1, core)) + Some((core, 1)) } case _ => None From 8adc33e2aeeb35b46a171165f385e2a2bbc457b9 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Mon, 23 Oct 2017 14:50:32 +0200 Subject: [PATCH 13/13] Run compilation tests with `-Xverify-signatures` --- compiler/test/dotty/tools/dotc/CompilationTests.scala | 2 +- compiler/test/dotty/tools/vulpix/TestConfiguration.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 197d23ee7d90..36fda9c8b417 100644 --- a/compiler/test/dotty/tools/dotc/CompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/CompilationTests.scala @@ -195,7 +195,7 @@ class CompilationTests extends ParallelTesting { // Generic java signatures tests --------------------------------------------- @Test def genericJavaSignatures: Unit = { - compileFilesInDir("../tests/generic-java-signatures", checkGenericJavaSignaturesOptions) + compileFilesInDir("../tests/generic-java-signatures", defaultOptions) }.checkRuns() // Pickling Tests ------------------------------------------------------------ diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 4a180ec87eb7..938808c27efe 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -12,7 +12,8 @@ object TestConfiguration { val checkOptions = Array( "-Yno-deep-subtypes", "-Yno-double-bindings", - "-Yforce-sbt-phases" + "-Yforce-sbt-phases", + "-Xverify-signatures" ) val classPath = mkClassPath(Jars.dottyTestDeps) @@ -58,5 +59,4 @@ object TestConfiguration { val scala2Mode = defaultOptions and "-language:Scala2" val explicitUTF8 = defaultOptions and ("-encoding", "UTF8") val explicitUTF16 = defaultOptions and ("-encoding", "UTF16") - val checkGenericJavaSignaturesOptions = defaultOptions and "-Xverify-signatures" }