Skip to content

Commit eca989b

Browse files
authored
Merge pull request #3234 from Duhemm/wip/java-generic-sig
Generate generic java signatures
2 parents f44438e + 8adc33e commit eca989b

39 files changed

+851
-31
lines changed

compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import dotty.tools.dotc.ast.Trees
55
import dotty.tools.dotc
66
import dotty.tools.dotc.backend.jvm.DottyPrimitives
77
import dotty.tools.dotc.core.Flags.FlagSet
8-
import dotty.tools.dotc.transform.Erasure
8+
import dotty.tools.dotc.transform.{Erasure, GenericSignatures}
99
import dotty.tools.dotc.transform.SymUtils._
1010
import java.io.{File => JFile}
1111

@@ -489,10 +489,97 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
489489

490490
def getSingleOutput: Option[AbstractFile] = None // todo: implement
491491

492+
// @M don't generate java generics sigs for (members of) implementation
493+
// classes, as they are monomorphic (TODO: ok?)
494+
private final def needsGenericSignature(sym: Symbol): Boolean = !(
495+
// pp: this condition used to include sym.hasexpandedname, but this leads
496+
// to the total loss of generic information if a private member is
497+
// accessed from a closure: both the field and the accessor were generated
498+
// without it. This is particularly bad because the availability of
499+
// generic information could disappear as a consequence of a seemingly
500+
// unrelated change.
501+
ctx.base.settings.YnoGenericSig.value
502+
|| sym.is(Flags.Artifact)
503+
|| sym.is(Flags.allOf(Flags.Method, Flags.Lifted))
504+
|| sym.is(Flags.Bridge)
505+
)
506+
507+
private def verifySignature(sym: Symbol, sig: String)(implicit ctx: Context): Unit = {
508+
import scala.tools.asm.util.CheckClassAdapter
509+
def wrap(body: => Unit): Boolean =
510+
try { body; true }
511+
catch { case ex: Throwable => println(ex.getMessage); false }
512+
513+
val valid = wrap {
514+
if (sym.is(Flags.Method)) {
515+
CheckClassAdapter.checkMethodSignature(sig)
516+
}
517+
else if (sym.isTerm) {
518+
CheckClassAdapter.checkFieldSignature(sig)
519+
}
520+
else {
521+
CheckClassAdapter.checkClassSignature(sig)
522+
}
523+
}
524+
525+
if(!valid) {
526+
ctx.warning(
527+
i"""|compiler bug: created invalid generic signature for $sym in ${sym.denot.owner.showFullName}
528+
|signature: $sig
529+
|if this is reproducible, please report bug at https://github.com/lampepfl/dotty/issues
530+
""".trim, sym.pos)
531+
}
532+
}
533+
534+
/**
535+
* Generates the generic signature for `sym` before erasure.
536+
*
537+
* @param sym The symbol for which to generate a signature.
538+
* @param owner The owner of `sym`.
539+
* @return The generic signature of `sym` before erasure, as specified in the Java Virtual
540+
* Machine Specification, §4.3.4, or `null` if `sym` doesn't need a generic signature.
541+
* @see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4
542+
*/
543+
def getGenericSignature(sym: Symbol, owner: Symbol): String = {
544+
ctx.atPhase(ctx.erasurePhase) { implicit ctx =>
545+
val memberTpe =
546+
if (sym.is(Flags.Method)) sym.denot.info
547+
else owner.denot.thisType.memberInfo(sym)
548+
getGenericSignature(sym, owner, memberTpe).orNull
549+
}
550+
}
492551

493-
def getGenericSignature(sym: Symbol, owner: Symbol): String = null // todo: implement
552+
def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = {
553+
// scala/bug#3452 Static forwarder generation uses the same erased signature as the method if forwards to.
554+
// By rights, it should use the signature as-seen-from the module class, and add suitable
555+
// primitive and value-class boxing/unboxing.
556+
// But for now, just like we did in mixin, we just avoid writing a wrong generic signature
557+
// (one that doesn't erase to the actual signature). See run/t3452b for a test case.
558+
559+
val memberTpe = ctx.atPhase(ctx.erasurePhase) { implicit ctx => moduleClass.denot.thisType.memberInfo(sym) }
560+
val erasedMemberType = TypeErasure.erasure(memberTpe)
561+
if (erasedMemberType =:= sym.denot.info)
562+
getGenericSignature(sym, moduleClass, memberTpe).orNull
563+
else null
564+
}
565+
566+
private def getGenericSignature(sym: Symbol, owner: Symbol, memberTpe: Type)(implicit ctx: Context): Option[String] =
567+
if (needsGenericSignature(sym)) {
568+
val erasedTypeSym = sym.denot.info.typeSymbol
569+
if (erasedTypeSym.isPrimitiveValueClass) {
570+
None
571+
} else {
572+
val jsOpt = GenericSignatures.javaSig(sym, memberTpe)
573+
if (ctx.settings.XverifySignatures.value) {
574+
jsOpt.foreach(verifySignature(sym, _))
575+
}
576+
577+
jsOpt
578+
}
579+
} else {
580+
None
581+
}
494582

495-
def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = null // todo: implement
496583

497584

498585
def sourceFileFor(cu: CompilationUnit): String = cu.source.file.name

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class ScalaSettings extends Settings.SettingGroup {
6161
val XnoValueClasses = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.")
6262
val XreplLineWidth = IntSetting("-Xrepl-line-width", "Maximial number of columns per line for REPL output", 390)
6363
val XfatalWarnings = BooleanSetting("-Xfatal-warnings", "Fail the compilation if there are any warnings.")
64+
val XverifySignatures = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.")
6465

6566
/** -Y "Private" settings */
6667
val overrideVars = BooleanSetting("-Yoverride-vars", "Allow vars to be overridden.")
@@ -79,6 +80,7 @@ class ScalaSettings extends Settings.SettingGroup {
7980
val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.")
8081

8182
val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.")
83+
val YnoGenericSig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.")
8284
val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.")
8385
val Yskip = PhasesSetting("-Yskip", "Skip")
8486
val Ydumpclasses = StringSetting("-Ydump-classes", "dir", "Dump the generated bytecode to .class files (useful for reflective compilation that utilizes in-memory classloaders).", "")

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,11 @@ class Definitions {
300300
lazy val NothingClass: ClassSymbol = enterCompleteClassSymbol(
301301
ScalaPackageClass, tpnme.Nothing, AbstractFinal, List(AnyClass.typeRef))
302302
def NothingType = NothingClass.typeRef
303+
lazy val RuntimeNothingModuleRef = ctx.requiredModuleRef("scala.runtime.Nothing")
303304
lazy val NullClass: ClassSymbol = enterCompleteClassSymbol(
304305
ScalaPackageClass, tpnme.Null, AbstractFinal, List(ObjectClass.typeRef))
305306
def NullType = NullClass.typeRef
307+
lazy val RuntimeNullModuleRef = ctx.requiredModuleRef("scala.runtime.Null")
306308

307309
lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef")
308310
def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol
@@ -392,9 +394,9 @@ class Definitions {
392394
def ArrayModule(implicit ctx: Context) = ArrayModuleType.symbol.moduleClass.asClass
393395

394396

395-
lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc)
397+
lazy val UnitType: TypeRef = valueTypeRef("scala.Unit", BoxedUnitType, java.lang.Void.TYPE, UnitEnc, nme.specializedTypeNames.Void)
396398
def UnitClass(implicit ctx: Context) = UnitType.symbol.asClass
397-
lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc)
399+
lazy val BooleanType = valueTypeRef("scala.Boolean", BoxedBooleanType, java.lang.Boolean.TYPE, BooleanEnc, nme.specializedTypeNames.Boolean)
398400
def BooleanClass(implicit ctx: Context) = BooleanType.symbol.asClass
399401
lazy val Boolean_notR = BooleanClass.requiredMethodRef(nme.UNARY_!)
400402
def Boolean_! = Boolean_notR.symbol
@@ -413,13 +415,13 @@ class Definitions {
413415
})
414416
def Boolean_!= = Boolean_neqeqR.symbol
415417

416-
lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc)
418+
lazy val ByteType: TypeRef = valueTypeRef("scala.Byte", BoxedByteType, java.lang.Byte.TYPE, ByteEnc, nme.specializedTypeNames.Byte)
417419
def ByteClass(implicit ctx: Context) = ByteType.symbol.asClass
418-
lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc)
420+
lazy val ShortType: TypeRef = valueTypeRef("scala.Short", BoxedShortType, java.lang.Short.TYPE, ShortEnc, nme.specializedTypeNames.Short)
419421
def ShortClass(implicit ctx: Context) = ShortType.symbol.asClass
420-
lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc)
422+
lazy val CharType: TypeRef = valueTypeRef("scala.Char", BoxedCharType, java.lang.Character.TYPE, CharEnc, nme.specializedTypeNames.Char)
421423
def CharClass(implicit ctx: Context) = CharType.symbol.asClass
422-
lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc)
424+
lazy val IntType: TypeRef = valueTypeRef("scala.Int", BoxedIntType, java.lang.Integer.TYPE, IntEnc, nme.specializedTypeNames.Int)
423425
def IntClass(implicit ctx: Context) = IntType.symbol.asClass
424426
lazy val Int_minusR = IntClass.requiredMethodRef(nme.MINUS, List(IntType))
425427
def Int_- = Int_minusR.symbol
@@ -435,7 +437,7 @@ class Definitions {
435437
def Int_>= = Int_geR.symbol
436438
lazy val Int_leR = IntClass.requiredMethodRef(nme.LE, List(IntType))
437439
def Int_<= = Int_leR.symbol
438-
lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc)
440+
lazy val LongType: TypeRef = valueTypeRef("scala.Long", BoxedLongType, java.lang.Long.TYPE, LongEnc, nme.specializedTypeNames.Long)
439441
def LongClass(implicit ctx: Context) = LongType.symbol.asClass
440442
lazy val Long_XOR_Long = LongType.member(nme.XOR).requiredSymbol(
441443
x => (x is Method) && (x.info.firstParamTypes.head isRef defn.LongClass)
@@ -450,9 +452,9 @@ class Definitions {
450452
lazy val Long_divR = LongClass.requiredMethodRef(nme.DIV, List(LongType))
451453
def Long_/ = Long_divR.symbol
452454

453-
lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc)
455+
lazy val FloatType: TypeRef = valueTypeRef("scala.Float", BoxedFloatType, java.lang.Float.TYPE, FloatEnc, nme.specializedTypeNames.Float)
454456
def FloatClass(implicit ctx: Context) = FloatType.symbol.asClass
455-
lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc)
457+
lazy val DoubleType: TypeRef = valueTypeRef("scala.Double", BoxedDoubleType, java.lang.Double.TYPE, DoubleEnc, nme.specializedTypeNames.Double)
456458
def DoubleClass(implicit ctx: Context) = DoubleType.symbol.asClass
457459

458460
lazy val BoxedUnitType: TypeRef = ctx.requiredClassRef("scala.runtime.BoxedUnit")
@@ -974,15 +976,17 @@ class Definitions {
974976

975977
private val boxedTypes = mutable.Map[TypeName, TypeRef]()
976978
private val valueTypeEnc = mutable.Map[TypeName, PrimitiveClassEnc]()
979+
private val typeTags = mutable.Map[TypeName, Name]().withDefaultValue(nme.specializedTypeNames.Object)
977980

978981
// private val unboxedTypeRef = mutable.Map[TypeName, TypeRef]()
979982
// private val javaTypeToValueTypeRef = mutable.Map[Class[_], TypeRef]()
980983
// private val valueTypeNameToJavaType = mutable.Map[TypeName, Class[_]]()
981984

982-
private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int): TypeRef = {
985+
private def valueTypeRef(name: String, boxed: TypeRef, jtype: Class[_], enc: Int, tag: Name): TypeRef = {
983986
val vcls = ctx.requiredClassRef(name)
984987
boxedTypes(vcls.name) = boxed
985988
valueTypeEnc(vcls.name) = enc
989+
typeTags(vcls.name) = tag
986990
// unboxedTypeRef(boxed.name) = vcls
987991
// javaTypeToValueTypeRef(jtype) = vcls
988992
// valueTypeNameToJavaType(vcls.name) = jtype
@@ -992,6 +996,9 @@ class Definitions {
992996
/** The type of the boxed class corresponding to primitive value type `tp`. */
993997
def boxedType(tp: Type)(implicit ctx: Context): TypeRef = boxedTypes(scalaClassName(tp))
994998

999+
/** The JVM tag for `tp` if it's a primitive, `java.lang.Object` otherwise. */
1000+
def typeTag(tp: Type)(implicit ctx: Context): Name = typeTags(scalaClassName(tp))
1001+
9951002
type PrimitiveClassEnc = Int
9961003

9971004
val ByteEnc = 2

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -229,23 +229,8 @@ object NameOps {
229229

230230
def specializedFor(classTargs: List[Types.Type], classTargsNames: List[Name], methodTargs: List[Types.Type], methodTarsNames: List[Name])(implicit ctx: Context): name.ThisName = {
231231

232-
def typeToTag(tp: Types.Type): Name = {
233-
tp.classSymbol match {
234-
case t if t eq defn.IntClass => nme.specializedTypeNames.Int
235-
case t if t eq defn.BooleanClass => nme.specializedTypeNames.Boolean
236-
case t if t eq defn.ByteClass => nme.specializedTypeNames.Byte
237-
case t if t eq defn.LongClass => nme.specializedTypeNames.Long
238-
case t if t eq defn.ShortClass => nme.specializedTypeNames.Short
239-
case t if t eq defn.FloatClass => nme.specializedTypeNames.Float
240-
case t if t eq defn.UnitClass => nme.specializedTypeNames.Void
241-
case t if t eq defn.DoubleClass => nme.specializedTypeNames.Double
242-
case t if t eq defn.CharClass => nme.specializedTypeNames.Char
243-
case _ => nme.specializedTypeNames.Object
244-
}
245-
}
246-
247-
val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => typeToTag(x._1))
248-
val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => typeToTag(x._1))
232+
val methodTags: Seq[Name] = (methodTargs zip methodTarsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
233+
val classTags: Seq[Name] = (classTargs zip classTargsNames).sortBy(_._2).map(x => defn.typeTag(x._1))
249234

250235
name.likeSpaced(name ++ nme.specializedTypeNames.prefix ++
251236
methodTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.separator ++

0 commit comments

Comments
 (0)