Skip to content

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

Merged
merged 13 commits into from
Oct 30, 2017
Merged

Conversation

Duhemm
Copy link
Contributor

@Duhemm Duhemm commented Oct 3, 2017

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.

Copy link
Member

@dottybot dottybot left a 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! ☀️

@smarter
Copy link
Member

smarter commented Oct 3, 2017

test performance please

@dottybot
Copy link
Member

dottybot commented Oct 3, 2017

performance test scheduled: 1 job(s) in queue, 1 running.

@dottybot
Copy link
Member

dottybot commented Oct 3, 2017

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
Copy link
Contributor

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.

Copy link
Contributor Author

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
Copy link
Contributor

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 _)) ||
Copy link
Contributor

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)

Copy link
Contributor

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) &&
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

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)

Copy link
Contributor

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
Copy link
Contributor

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 =>

Copy link
Contributor

@odersky odersky Oct 7, 2017

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
Copy link
Contributor

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?

Copy link
Contributor Author

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;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @retronym

Copy link
Member

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

Copy link
Contributor

@adriaanm adriaanm Oct 9, 2017

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))
Copy link
Contributor

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) }
Copy link
Contributor

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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[])

@Duhemm
Copy link
Contributor Author

Duhemm commented Oct 10, 2017

This is ready for another round of review 😄

@@ -0,0 +1,510 @@
package dotty.tools
package dotc
Copy link
Contributor

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

Copy link
Contributor

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.

Copy link
Contributor

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenericJavaSignatures?

Copy link
Contributor

@odersky odersky left a 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)
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spurious empty line?

@allanrenucci allanrenucci added this to the 0.5 Tech Preview milestone Oct 17, 2017
@Duhemm Duhemm force-pushed the wip/java-generic-sig branch from a919e9a to 420706d Compare October 23, 2017 12:51
@Duhemm
Copy link
Contributor Author

Duhemm commented Oct 23, 2017

Rebased and added -Xverify-signatures to the default test options.

@Duhemm Duhemm force-pushed the wip/java-generic-sig branch from 420706d to 8adc33e Compare October 27, 2017 13:08
@allanrenucci allanrenucci merged commit eca989b into scala:master Oct 30, 2017
@xuwei-k
Copy link
Contributor

xuwei-k commented Nov 25, 2020

scala/bug#12246

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants