-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Generate generic java signatures #3234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello, and thank you for opening this PR! 🎉
All contributors have signed the CLA, thank you! ❤️
Have an awesome day! ☀️
test performance please |
performance test scheduled: 1 job(s) in queue, 1 running. |
Performance test finished successfully: Visit http://dotty-bench.epfl.ch/3234/ to see the changes. Benchmarks is based on merging with master (432ceac) |
@@ -516,6 +516,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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just use isTypeParam
. isTypeParameterOrSkolem
would be confusing in a dotty context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept the original name to ease review. I don't really know what Skolem
refers to, and was afraid to lose the change in the port from scalac.
@@ -516,6 +516,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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just used once. I'd just use the RHS directly in Erasure. That would avoid having to look up what the term means.
private def isTypeParameterInSig(sym: Symbol, initialSymbol: Symbol)(implicit ctx: Context) = { | ||
!sym.isHigherOrderTypeParameter && | ||
sym.isTypeParameterOrSkolem && ( | ||
(initialSymbol.enclClassChain.exists(sym isContainedIn _)) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe equivalent, and faster would be:
sym.isContainedIn(initialSymbol.topLevelClass)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That also means we don't need enclClassChain
.
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) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to write denot
. It's used as an implicit everywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Symbols.scala, as most source files, imports all flags with a wildcard. So for consistency I would not use the Flags
prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can combine the two tests like this:
!this.is(Package | Trait)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But in the end, you should just use ValueClasses.isDerivedValueClass
. If the two definitions differ, we should find out which is the right one and use only that one.
/** 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use ValueClasses.valueClassUnbox
instead.
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 { thisTransformer => | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Erasure is already rather large as is, and therefore people are scared to touch it. We should try to move signature generation to a separate sourcefile. It could be integrated as an object in erasure similarly to how Bridges.scala moves out bridge generation from erasure.
} | ||
|
||
private def hiBounds(bounds: TypeBounds)(implicit ctx: Context): List[Type] = bounds.hi.widenDealias match { | ||
case AndType(tp1, tp2) => tp1 :: tp2 :: Nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens with nested AndTypes? Don't you need a recursive call here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I thought at first that it was not necessary, because all the parents would be picked up in jsig
and the signature would be correct, but further investigation showed that I was wrong, and it's actually a bug in scalac:
Considering the following definitions:
class C1 { def c1 = 0 }
trait T1 { def t1: Int }
trait T2 { def t2: Int }
class Foo[T <: (C1 with T1) with T2] { // Note the parentheses
def foo(t: T): Int = t.c1 + t.t1 + t.t2
}
The signature of Foo
that scalac generates is: <T:LC1;:LT2;>Ljava/lang/Object
There's no mention of T1
! This means that javac won't complain if someone passes an instance of C1 with T2
to def foo
, resulting in a runtime crash.
With the recursive call that you suggest, dotty generates the expected signature: <T:LC1;:LT1;:LT2;>Ljava/lang/Object;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/cc @retronym
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, noted as scala/bug#10543
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm, looks like another instance of too aggressively replacing normalize (which flattens refined types) with dealias in
private def hiBounds(bounds: TypeBounds): List[Type] = bounds.hi.dealiasWiden match {
case RefinedType(parents, _) => parents map (_.dealiasWiden)
case tp => tp :: Nil
}
case GenericArray(level, core) if !(core <:< defn.AnyRefType) => | ||
level | ||
case AndType(tp1, tp2) => | ||
Math.max(unboundedGenericArrayLevel(tp1), unboundedGenericArrayLevel(tp2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use infix max
instead.
} | ||
|
||
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) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simpler:
javaSig0(sym0, info, markClassUsed)(ctx.withPhase(ctx.erasurePhase))
if (owner.is(Flags.PackageClass) || owner.isTerm) pre else cls.owner.info /* .tpe_* */ | ||
} | ||
|
||
object GenericArray { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to base this on the Definitions.MultiArrayOf
extractor. This would avoid duplication and simplify the code that needs to be added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simplified GenericArray
as much as I could, but I cannot use MultiArrayOf
directly, since it'll resolve the upper bound when encountering type bounds. This means that types such as
class Foo[T <: Array[Int]]
would get a wrong signature (T <: java.lang.Object
instead of T <: int[]
)
c2f78da
to
a919e9a
Compare
This is ready for another round of review 😄 |
@@ -0,0 +1,510 @@ | |||
package dotty.tools | |||
package dotc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably be in backend/jvm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debatable where it should go. But since most of it is part of erasure I'd leave it in transform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is only called from backend/jvm/DottyBackendInterface.scala
. So my reasoning was that this code is platform specific.
But I don't have a strong opinion about where it should be.
/** Helper object to generate generic java signatures, as defined in | ||
* the Java Virtual Machine Specification, §4.3.4 | ||
*/ | ||
object GenericSignatures { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GenericJavaSignatures
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not go through GenericSignatures in detail, since I trust that this was ported correctly from scalac. The integration with the rest of the compiler is now as clean and minimal as it can be, I think. Nice work!
@@ -745,6 +745,7 @@ object Types { | |||
else ctx.asSeenFrom(this, pre, cls) | |||
} | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spurious empty line?
a919e9a
to
420706d
Compare
Rebased and added |
The code has been ported from scalac.
420706d
to
8adc33e
Compare
The code has been ported from scalac.
Two flags are added:
-Yno-generic-signatures
disables the generation of generic signatures-Xverify-signatures
runs further verifications of the signatures, and emits a warning if the signature is not valid.As discussed, the signatures are not generated if they contain symbols that we cannot encode for the JVM.