8000 SI-10071 Separate compilation for varargs methods by dragos · Pull Request #5556 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

SI-10071 Separate compilation for varargs methods #5556

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 3 commits into from
Nov 25, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
SI-10071 Separate compilation for varargs methods
Make sure that methods annotated with varargs are properly mixed-in. This commit
splits the transformation into an info transformer (that works on all symbols, whether
they come from source or binary) and a tree transformer.

The gist of this is that the symbol-creation part of the code was moved to the UnCurry
info transformer, while tree operations remained in the tree transformer. The newly
created symbol is attached to the original method so that the tree transformer can still
retrieve the symbol.

A few fall outs:

- I removed a local map that was identical to TypeParamsVarargsAttachment
- moved the said attachment to StdAttachments so it’s visible between reflect.internal
  and nsc.transform
- a couple more comments in UnCurry to honour the boy-scout rule
  • Loading branch information
dragos committed Nov 25, 2016
8000 commit 7602f2ebc0fbd0e1b51aa8d9d9a9e71607a06dd6
1 change: 0 additions & 1 deletion src/compiler/scala/tools/nsc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1278,5 +1278,4 @@ abstract class Erasure extends InfoTransform
}

private class TypeRefAttachment(val tpe: TypeRef)
class TypeParamVarargsAttachment(val typeParamRef: Type)
}
95 changes: 34 additions & 61 deletions src/compiler/scala/tools/nsc/transform/UnCurry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import scala.reflect.internal.util.ListOfNil
* - for every repeated Scala parameter `x: T*' --> x: Seq[T].
* - for every repeated Java parameter `x: T...' --> x: Array[T], except:
* if T is an unbounded abstract type, replace --> x: Array[Object]
* - for every method defining repeated parameters annotated with @varargs, generate
* a synthetic Java-style vararg method
* - for every argument list that corresponds to a repeated Scala parameter
* (a_1, ..., a_n) => (Seq(a_1, ..., a_n))
* - for every argument list that corresponds to a repeated Java parameter
Expand All @@ -44,6 +46,8 @@ import scala.reflect.internal.util.ListOfNil
* def liftedTry$1 = try { x_i } catch { .. }
* meth(x_1, .., liftedTry$1(), .. )
* }
* - remove calls to elidable methods and replace their bodies with NOPs when elide-below
* requires it
*/
/*</export> */
abstract class UnCurry extends InfoTransform
Expand Down Expand Up @@ -577,7 +581,13 @@ abstract class UnCurry extends InfoTransform
case None => literalRhsIfConst
}
)
addJavaVarargsForwarders(dd, flatdd)
// Only class members can reasonably be called from Java due to name mangling.
// Additionally, the Uncurry info transformer only adds a forwarder symbol to class members,
// since the other symbols are not part of the ClassInfoType (see reflect.internal.transform.UnCurry)
if (dd.symbol.owner.isClass)
addJavaVarargsForwarders(dd, flatdd)
else
flatdd

case tree: Try =>
if (tree.catches exists (cd => !treeInfo.isCatchCase(cd)))
Expand Down Expand Up @@ -739,68 +749,32 @@ abstract class UnCurry extends InfoTransform
if (!hasRepeated) reporter.error(dd.symbol.pos, "A method without repeated parameters cannot be annotated with the `varargs` annotation.")
}

/* Called during post transform, after the method argument lists have been flattened.
* It looks for the method in the `repeatedParams` map, and generates a Java-style
/**
* Called during post transform, after the method argument lists have been flattened.
* It looks for the forwarder symbol in the symbol attachments and generates a Java-style
* varargs forwarder.
*
* @note The Java-style varargs method symbol is generated in the Uncurry info transformer. If the
* symbol can't be found this method reports a warning and carries on.
* @see [[scala.reflect.internal.transform.UnCurry]]
*/
private def addJavaVarargsForwarders(dd: DefDef, flatdd: DefDef): DefDef = {
Copy link
Contributor Author
@dragos dragos Nov 24, 2016

Choose a reason for hiding this comment

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

This method signature is confusing, but I chose to keep things simple and I didn't touch it.

flatdd is not required since almost all the logic is based on Symbols and the two trees always have the same symbol. Additionally, the only legitimate use of a tree is in the call to newTyper(dd,..), though I'm not sure that there's no other way. If you have any suggestions here I'm happy to at least remove the extra parameter.

if (!dd.symbol.hasAnnotation(VarargsClass) || !enteringUncurry(mexists(dd.symbol.paramss)(sym => definitions.isRepeatedParamType(sym.tpe))))
return flatdd

val forwSym = currentClass.newMethod(dd.name.toTermName, dd.pos, VARARGS | SYNTHETIC | flatdd.symbol.flags & ~DEFERRED)

val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe)))

val oldPs = flatdd.symbol.paramss.head

// see comment in method toArrayType below
val arrayTypesMappedToObject = mutable.Map.empty[Symbol, Type]

val forwTpe = {
val (oldTps, tps) = dd.symbol.tpe match {
case PolyType(oldTps, _) =>
val newTps = oldTps.map(_.cloneSymbol(forwSym))
(oldTps, newTps)

case _ => (Nil, Nil)
}

def toArrayType(tp: Type, newParam: Symbol): Type = {
val arg = elementType(SeqClass, tp)
val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) {
// To prevent generation of an `Object` parameter from `Array[T]` parameter later
// as this would crash the Java compiler which expects an `Object[]` array for varargs
// e.g. def foo[T](a: Int, b: T*)
// becomes def foo[T](a: Int, b: Array[Object])
// instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object)
//
// In order for the forwarder method to type check we need to insert a cast:
// def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']])
// The target element type for that cast (T') is stored in the `arrayTypesMappedToObject` map.
val originalArg = arg.substSym(oldTps, tps)
arrayTypesMappedToObject(newParam) = originalArg
// Store the type parameter that was replaced by Object to emit the correct generic signature
newParam.updateAttachment(new erasure.TypeParamVarargsAttachment(originalArg))
ObjectTpe
} else
arg
arrayType(elem)
val forwSym: Symbol = {
currentClass.info // make sure the info is up to date, so the varargs forwarder symbol has been generated
flatdd.symbol.attachments.get[VarargsSymbolAttachment] match {
case Some(VarargsSymbolAttachment(sym)) => sym
case None =>
reporter.warning(dd.pos, s"Could not generate Java varargs forwarder for ${flatdd.symbol}. Please file a bug.")
return flatdd
}

val ps = map2(oldPs, isRepeated)((oldParam, isRep) => {
val newParam = oldParam.cloneSymbol(forwSym)
val tp = if (isRep) toArrayType(oldParam.tpe, newParam) else oldParam.tpe
newParam.setInfo(tp)
})

val resTp = dd.symbol.tpe_*.finalResultType.substSym(oldPs, ps)
val mt = MethodType(ps, resTp)
val r = if (tps.isEmpty) mt else PolyType(tps, mt)
r.substSym(oldTps, tps)
}

forwSym.setInfo(forwTpe)
val newPs = forwTpe.params
val newPs = forwSym.tpe.params
val isRepeated = enteringUncurry(dd.symbol.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe)))
val oldPs = flatdd.symbol.paramss.head

val theTyper = typer.atOwner(dd, currentClass)
val forwTree = theTyper.typedPos(dd.pos) {
Expand All @@ -809,8 +783,8 @@ abstract class UnCurry extends InfoTransform
else {
val parTp = elementType(ArrayClass, param.tpe)
val wrap = gen.mkWrapArray(Ident(param), parTp)
arrayTypesMappedToObject.get(param) match {
case Some(tp) => gen.mkCast(wrap, seqType(tp))
param.attachments.get[TypeParamVarargsAttachment] match {
case Some(TypeParamVarargsAttachment(tp)) => gen.mkCast(wrap, seqType(tp))
case _ => wrap
}
}
Expand All @@ -821,13 +795,12 @@ abstract class UnCurry extends InfoTransform
}

// check if the method with that name and those arguments already exists in the template
currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe)) match {
case Some(s) => reporter.error(dd.symbol.pos,
"A method with a varargs annotation produces a forwarder method with the same signature "
+ s.tpe + " as an existing method.")
enteringUncurry(currentClass.info.member(forwSym.name).alternatives.find(s => s != forwSym && s.tpe.matches(forwSym.tpe))) match {
case Some(s) =>
reporter.error(dd.symbol.pos,
s"A method with a varargs annotation produces a forwarder method with the same signature ${s.tpe} as an existing method.")
case None =>
// enter symbol into scope
currentClass.info.decls enter forwSym
addNewMember(forwTree)
}

Expand Down
3 changes: 3 additions & 0 deletions src/reflect/scala/reflect/internal/StdAttachments.scala
67E6
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,7 @@ trait StdAttachments {
case object OuterArgCanBeElided extends PlainAttachment

case object UseInvokeSpecial extends PlainAttachment

/** An attachment carrying information between uncurry and erasure */
case class TypeParamVarargsAttachment(val typeParamRef: Type)
}
82 changes: 80 additions & 2 deletions src/reflect/scala/reflect/internal/transform/UnCurry.scala
10000
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ package internal
package transform

import Flags._
import scala.collection.mutable

trait UnCurry {

val global: SymbolTable
import global._
import definitions._

/**
* The synthetic Java vararg method symbol corresponding to a Scala vararg method
* annotated with @varargs.
*/
case class VarargsSymbolAttachment(varargMethod: Symbol)

/** Note: changing tp.normalize to tp.dealias in this method leads to a single
* test failure: run/t5688.scala, where instead of the expected output
* Vector(ta, tb, tab)
Expand Down Expand Up @@ -67,8 +74,25 @@ trait UnCurry {
tp match {
case ClassInfoType(parents, decls, clazz) =>
val parents1 = parents mapConserve uncurry
Copy link
Member

Choose a reason for hiding this comment

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

move this declaration into the else branch below?

if (parents1 eq parents) tp
else ClassInfoType(parents1, decls, clazz) // @MAT normalize in decls??
val varargOverloads = mutable.ListBuffer.empty[Symbol]
Copy link
Member
@retronym retronym Nov 24, 2016

Choose a reason for hiding this comment

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

You could skip the new logic here if clazz.isJavaDefined.

Copy link
Member

Choose a reason for hiding this comment

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

In fact, I think we could skip the rest of the info transform in that case. Worth an experiment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I'll give it a go.


// Not using `hasAnnotation` here because of dreaded cyclic reference errors:
// it may happen that VarargsClass has not been initialized yet and we get here
// while processing one of its superclasses (such as java.lang.Object). Since we
// don't need the more precise `matches` semantics, we only check the symbol, which
// is anyway faster and safer
for (decl <- decls if decl.annotations.exists(_.symbol == VarargsClass)) {
if (mexists(decl.paramss)(sym => definitions.isRepeatedParamType(sym.tpe))) {
val forwarderSym = varargForwarderSym(clazz, decl, exitingPhase(phase)(decl.info))
varargOverloads += forwarderSym
}
Copy link
Member

Choose a reason for hiding this comment

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

could use exitingUncurry to be more explicit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately there is no such thing in scala.reflect.internal, only in the compiler cake.

Copy link
Member

Choose a reason for hiding this comment

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

ah yes :)

}
if ((parents1 eq parents) && varargOverloads.isEmpty) tp
else {
val newDecls = decls.cloneScope
varargOverloads.foreach(newDecls.enter)
ClassInfoType(parents1, newDecls, clazz)
} // @MAT normalize in decls??
case PolyType(_, _) =>
mapOver(tp)
case _ =>
Expand All @@ -77,6 +101,60 @@ trait UnCurry {
}
}

private def varargForwarderSym(currentClass: Symbol, origSym: Symbol, newInfo: Type): Symbol = {
val forwSym = currentClass.newMethod(origSym.name.toTermName, origSym.pos, VARARGS | SYNTHETIC | origSym.flags & ~DEFERRED)

// we are using `origSym.info`, which contains the type *before* the transformation
// so we still see repeated parameter types (uncurry replaces them with Seq)
val isRepeated = origSym.info.paramss.flatten.map(sym => definitions.isRepeatedParamType(sym.tpe))
val oldPs = newInfo.paramss.head

val forwTpe = {
val (oldTps, tps) = newInfo match {
case PolyType(oldTps, _) =>
val newTps = oldTps.map(_.cloneSymbol(forwSym))
(oldTps, newTps)

case _ => (Nil, Nil)
}

def toArrayType(tp: Type, newParam: Symbol): Type = {
val arg = elementType(SeqClass, tp)
val elem = if (arg.typeSymbol.isTypeParameterOrSkolem && !(arg <:< AnyRefTpe)) {
// To prevent generation of an `Object` parameter from `Array[T]` parameter later
// as this would crash the Java compiler which expects an `Object[]` array for varargs
// e.g. def foo[T](a: Int, b: T*)
// becomes def foo[T](a: Int, b: Array[Object])
// instead of def foo[T](a: Int, b: Array[T]) ===> def foo[T](a: Int, b: Object)
//
// In order for the forwarder method to type check we need to insert a cast:
// def foo'[T'](a: Int, b: Array[Object]) = foo[T'](a, wrapRefArray(b).asInstanceOf[Seq[T']])
// The target element type for that cast (T') is stored in the TypeParamVarargsAttachment
val originalArg = arg.substSym(oldTps, tps)
// Store the type parameter that was replaced by Object to emit the correct generic signature
newParam.updateAttachment(new TypeParamVarargsAttachment(originalArg))
ObjectTpe
} else
arg
arrayType(elem)
}

val ps = map2(oldPs, isRepeated)((oldParam, isRep) => {
val newParam = oldParam.cloneSymbol(forwSym)
val tp = if (isRep) toArrayType(oldParam.tpe, newParam) else oldParam.tpe
newParam.setInfo(tp)
})

val resTp = newInfo.finalResultType.substSym(oldPs, ps)
val mt = MethodType(ps, resTp)
val r = if (tps.isEmpty) mt else PolyType(tps, mt)
r.substSym(oldTps, tps)
}
Copy link
Member

Choose a reason for hiding this comment

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

This is old code, but it is using very low level means to build up the new method symbol. I believe we can just clone the original symbol to get this for free.

https://github.com/retronym/scala/compare/1a21cc667a6f33a576900ea024c3bb21df84638e...retronym:review/5556?expand=11a21cc667a6f33a576900ea024c3bb21df84638e

Passes at least sbt partest --grep varargs.

@lrytz Do you remember any gotchas in this code that justify the current approach?

Copy link
Member

Choose a reason for hiding this comment

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

no, your patch LGTM. @dragos you want to include it in this PR, maybe as a separate commit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'll add it as another commit and let the bot tell us what's going on


origSym.updateAttachment(VarargsSymbolAttachment(forwSym))
forwSym.setInfo(forwTpe)
}

/** - return symbol's transformed type,
* - if symbol is a def parameter with transformed type T, return () => T
*
Expand Down
2 changes: 2 additions & 0 deletions src/reflect/scala/reflect/runtime/JavaUniverseForce.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
this.InlineCallsiteAttachment
this.OuterArgCanBeElided
this.UseInvokeSpecial
this.TypeParamVarargsAttachment
this.noPrint
this.typeDebug
this.Range
Expand Down Expand Up @@ -458,6 +459,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
definitions.ScalaValueClassesNoUnit
definitions.ScalaValueClasses

uncurry.VarargsSymbolAttachment
uncurry.DesugaredParameterType
erasure.GenericArray
erasure.scalaErasure
Expand Down
1 change: 1 addition & 0 deletions test/files/jvm/varargs-separate-bytecode.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Found vararg overload for method create
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package foo

import scala.annotation.varargs

t F438 rait AbstractProps {
@varargs
def create(x: String, y: Int*): AbstractProps = null
}
3 changes: 3 additions & 0 deletions test/files/jvm/varargs-separate-bytecode/Props_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import foo.AbstractProps

class Props extends AbstractProps
15 changes: 15 additions & 0 deletions test/files/jvm/varargs-separate-bytecode/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import scala.collection.JavaConverters._
import scala.tools.asm
import scala.tools.asm.Opcodes
import scala.tools.partest.BytecodeTest

object Test extends BytecodeTest {
def show: Unit = {
val classNode = loadClassNode("Props")
val methods = classNode.methods.iterator().asScala.filter( m => m.name == "create")

for (m <- methods if (m.access & Opcodes.ACC_VARARGS) > 0) {
println(s"Found vararg overload for method ${m.name}")
}
}
}
3 changes: 3 additions & 0 deletions test/files/run/t5125b.check
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ public void C2.f(scala.collection.Seq)
public void C2$C3.f(java.lang.String[])
public void C2$C3.f(scala.collection.Seq)
public void C4.f(scala.collection.Seq)
private void C5.f(int,int[])
private void C5.f(int,scala.collection.Seq)
public void C5.f(scala.collection.Seq)
12 changes: 12 additions & 0 deletions test/files/run/t5125b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ class C4 {
}
}

class C5 {
def f(values: String*) = println("Calling C5.f(): " + values)
@scala.annotation.varargs
private def f(v: Int, values: Int*) = println("Calling C5.f(): " + values)

def method(): Unit = {
@scala.annotation.varargs
def f(values: String*) = println("Calling C5.<locally>.f(): " + values)
}
}

object Test extends App {
def check(c: Class[_]) {
val methodName = "f"
Expand All @@ -34,4 +45,5 @@ object Test extends App {
check(classOf[C2])
check(classOf[C2#C3])
check(classOf[C4])
check(classOf[C5])
}
0