diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index c8a587b3b71d..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} @@ -489,10 +489,97 @@ 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 + } + } - def getGenericSignature(sym: Symbol, owner: Symbol): String = null // todo: implement + 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 + if (erasedTypeSym.isPrimitiveValueClass) { + None + } else { + val jsOpt = GenericSignatures.javaSig(sym, memberTpe) + if (ctx.settings.XverifySignatures.value) { + jsOpt.foreach(verifySignature(sym, _)) + } + + jsOpt + } + } else { + None + } - def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = null // todo: implement def sourceFileFor(cu: CompilationUnit): String = cu.source.file.name 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..5e03220467be 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 @@ -392,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 @@ -413,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 @@ -435,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) @@ -450,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") @@ -974,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 @@ -992,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 5041c003bb92..4e568861e401 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 => 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/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala new file mode 100644 index 000000000000..d50f2e11ab38 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -0,0 +1,510 @@ +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(defn.typeTag(sym.info)) + } + else if (ValueClasses.isDerivedValueClass(sym)) { + val unboxed = ValueClasses.valueClassUnbox(sym.asClass).info.finalResultType + val unboxedSeen = tp.memberInfo(ValueClasses.valueClassUnbox(sym.asClass)).finalResultType + if (unboxedSeen.isPrimitiveValueType && !primitiveOK) + classSig + 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(core, level) if !(core <:< defn.AnyRefType) => + level + case AndType(tp1, tp2) => + unboundedGenericArrayLevel(tp1) max unboundedGenericArrayLevel(tp2) + case _ => + 0 + } + + // only refer to type params that will actually make it into the sig, this excludes: + // * higher-order type parameters + // * type parameters appearing in method parameters + // * type members not visible in an enclosing template + private def isTypeParameterInSig(sym: Symbol, initialSymbol: Symbol)(implicit ctx: Context) = { + !sym.maybeOwner.isTypeParam && + sym.isTypeParam && ( + sym.isContainedIn(initialSymbol.topLevelClass) || + (initialSymbol.is(Method) && initialSymbol.typeParams.contains(sym)) + ) + } + + /** Extracts the type of the thrown exception from an AnnotationInfo. + * + * Supports both “old-style” `@throws(classOf[Exception])` + * as well as “new-style” `@throws[Exception]("cause")` annotations. + */ + private object ThrownException { + def unapply(ann: Annotation)(implicit ctx: Context): Option[Type] = { + ann.tree match { + case Apply(TypeApply(fun, List(tpe)), _) if tpe.isType && fun.symbol.owner == defn.ThrowsAnnot && fun.symbol.isConstructor => + Some(tpe.typeOpt) + case _ => + None + } + } + } + + // @M #2585 when generating a java generic signature that includes + // a selection of an inner class p.I, (p = `pre`, I = `cls`) must + // rewrite to p'.I, where p' refers to the class that directly defines + // the nested class I. + // + // See also #2585 marker in javaSig: there, type arguments must be + // included (use pre.baseType(cls.owner)). + // + // This requires that cls.isClass. + private def rebindInnerClass(pre: Type, cls: Symbol)(implicit ctx: Context): Type = { + val owner = cls.owner + if (owner.is(PackageClass) || owner.isTerm) pre else cls.owner.info /* .tpe_* */ + } + + object GenericArray { + + /** Is `tp` an unbounded generic type (i.e. which could be instantiated + * with primitive as well as class types)?. + */ + private def genericCore(tp: Type)(implicit ctx: Context): Type = tp.widenDealias match { + /* A Java Array is erased to Array[Object] (T can only be a reference type), where as a Scala Array[T] is + * erased to Object. However, there is only symbol for the Array class. So to make the distinction between + * a Java and a Scala array, we check if the owner of T comes from a Java class. + * This however caused issue scala/bug#5654. The additional test for EXISTENTIAL fixes it, see the ticket comments. + * In short, members of an existential type (e.g. `T` in `forSome { type T }`) can have pretty arbitrary + * owners (e.g. when computing lubs, is used). All packageClass symbols have `isJavaDefined == true`. + */ + case RefOrAppliedType(sym, tp, _) => + if (sym.isAbstractType && (!sym.owner.is(JavaDefined) || sym.is(Scala2Existential))) + tp + else + NoType + + case bounds: TypeBounds => + bounds + + case _ => + NoType + } + + /** If `tp` is of the form Array[...Array[T]...] where `T` is an abstract type + * then Some((N, T)) where N is the number of Array constructors enclosing `T`, + * otherwise None. Existentials on any level are ignored. + */ + def unapply(tp: Type)(implicit ctx: Context): Option[(Type, Int)] = tp.widenDealias match { + case defn.ArrayOf(arg) => + genericCore(arg) match { + case NoType => + arg match { + case GenericArray(core, level) => Some((core, level + 1)) + case _ => None + } + case core => + Some((core, 1)) + } + case _ => + None + } + + } + + private object RefOrAppliedType { + def unapply(tp: Type)(implicit ctx: Context): Option[(Symbol, Type, List[Type])] = tp match { + case TypeParamRef(_, _) => + Some((tp.typeSymbol, tp, Nil)) + case TermParamRef(_, _) => + Some((tp.termSymbol, tp, Nil)) + case TypeRef(pre, _) if !tp.typeSymbol.isAliasType => + val sym = tp.typeSymbol + Some((sym, pre, Nil)) + case AppliedType(pre, args) => + Some((pre.typeSymbol, pre, args)) + case _ => + None + } + } + + private def needsJavaSig(tp: Type, throwsArgs: List[Type])(implicit ctx: Context): Boolean = !ctx.settings.YnoGenericSig.value && { + def needs(tp: Type) = (new NeedsSigCollector).apply(false, tp) + needs(tp) || throwsArgs.exists(needs) + } + + private class NeedsSigCollector(implicit ctx: Context) extends TypeAccumulator[Boolean] { + override def apply(x: Boolean, tp: Type): Boolean = + if (!x) { + tp match { + case RefinedType(parent, refinedName, refinedInfo) => + val sym = parent.typeSymbol + if (sym == defn.ArrayClass) foldOver(x, refinedInfo) + else true + case tref @ TypeRef(pre, name) => + val sym = tref.typeSymbol + if (sym.is(TypeParam) || sym.typeParams.nonEmpty) true + else if (sym.isClass) foldOver(x, rebindInnerClass(pre, sym)) // #2585 + else foldOver(x, pre) + case PolyType(_, _) => + true + case ClassInfo(_, _, parents, _, _) => + foldOver(tp.typeParams.nonEmpty, parents) + case AnnotatedType(tpe, _) => + foldOver(x, tpe) + case proxy: TypeProxy => + foldOver(x, proxy) + case _ => + foldOver(x, tp) + } + } else x + } +} diff --git a/compiler/test/dotty/tools/dotc/CompilationTests.scala b/compiler/test/dotty/tools/dotc/CompilationTests.scala index 6d2dc9ba8079..36fda9c8b417 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", defaultOptions) + }.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..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) 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(", ")) + } + } +} 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/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/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/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(", ")) + } + } +} 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(", ")) + } + } +} 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