diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2013-12-13 10:37:44 +0100 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2013-12-15 22:18:10 +0100 |
commit | 6a4947c45c0b5fac3297da320b9627069a7b5ac4 (patch) | |
tree | 28f6e71a1dbf9043a5fd4bb69f87f2f383c618da | |
parent | fcf1adad75553d25ea4e6afe37c971902b11f35e (diff) | |
download | scala-6a4947c45c0b5fac3297da320b9627069a7b5ac4.tar.gz scala-6a4947c45c0b5fac3297da320b9627069a7b5ac4.tar.bz2 scala-6a4947c45c0b5fac3297da320b9627069a7b5ac4.zip |
SI-8017 Value class awareness for -Ydelamdafy:method
The delambdafy creates a bridge method which requires adaptation
of the result type to the generic `Object`, which is the erased
return type of FunctionN.
This bridge building reused some code from erasure, now refactored
into TypeAdaptingTransformer.
But, it was running into problems with:
class C(a: Int) extends AnyVal
(x: Any) => new C(0)
It created (forgive the pseudo quasiquote syntax):
class anonfun$ extends Function1[Any, C] {
def apply#1(a: Object): Int = 0
<bridge> def apply#2(a: Object): Object = {
val result: Int = apply#1(a)
${adapt(Ident("result"), ObjectType)}
}
}
This resulted in primitive boxing, rather than value class boxing.
Instead, we need the call to the main apply method to be typed
as `ErasedValueClass(C, Int)`, which `adapt` takes as a trigger
to perform value class boxing.
Finally, we have to run the post-erasure transformer over the adapted
tree to eliminate remnants of `ErasedValueClass` from the types of
trees.
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 22 | ||||
-rw-r--r-- | test/files/run/t8017.flags | 1 | ||||
-rw-r--r-- | test/files/run/t8017/value-class-lambda.scala | 40 | ||||
-rw-r--r-- | test/files/run/t8017/value-class.scala | 3 |
4 files changed, 64 insertions, 2 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index c546c21d48..933a2f70a1 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -340,7 +340,19 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre else (true, adaptToType(tree, expectedTpe)) } + def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = { + val (needsAdapt, adaptedTree) = adapt(tree, pt) + val trans = postErasure.newTransformer(unit) + val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 elimnates ErasedValueTypes + (needsAdapt, postErasedTree) + } + enteringPhase(currentRun.posterasurePhase) { + // e.g, in: + // class C(val a: Int) extends AnyVal; (x: Int) => new C(x) + // + // This type is: + // (x: Int)ErasedValueType(class C, Int) val liftedBodyDefTpe: MethodType = { val liftedBodySymbol = { val Apply(method, _) = originalFunction.body @@ -349,8 +361,14 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre liftedBodySymbol.info.asInstanceOf[MethodType] } val (paramNeedsAdaptation, adaptedParams) = (bridgeSyms zip liftedBodyDefTpe.params map {case (bridgeSym, param) => adapt(Ident(bridgeSym) setType bridgeSym.tpe, param.tpe)}).unzip - val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType applyMethod.symbol.tpe.resultType - val (needsReturnAdaptation, adaptedBody) = adapt(typer.typed(body), ObjectTpe) + // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`. + // But that symbol doesn't have a type history that goes back before `delambdafy`, + // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`. + // This triggered primitive boxing, rather than value class boxing. + val resTp = liftedBodyDefTpe.finalResultType + val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp + val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe) + val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation if (needsBridge) { val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody) diff --git a/test/files/run/t8017.flags b/test/files/run/t8017.flags new file mode 100644 index 0000000000..48b438ddf8 --- /dev/null +++ b/test/files/run/t8017.flags @@ -0,0 +1 @@ +-Ydelambdafy:method diff --git a/test/files/run/t8017/value-class-lambda.scala b/test/files/run/t8017/value-class-lambda.scala new file mode 100644 index 0000000000..370023b194 --- /dev/null +++ b/test/files/run/t8017/value-class-lambda.scala @@ -0,0 +1,40 @@ +object Test { + def testC { + val f1 = (c: C) => c.value + val f2 = (x: Int) => new C(x) + val f3 = (c1: C) => (c2: C) => (c1, c2) + val r1 = f2(2) + val r2 = f2(2) + val r3 = f3(r1)(r2) + val result = f1(r3._2) + assert(result == 2) + } + + def testD { + val f1 = (c: D) => c.value + val f2 = (x: String) => new D(x) + val f3 = (c1: D) => (c2: D) => (c1, c2) + val r1 = f2("2") + val r2 = f2("2") + val r3 = f3(r1)(r2) + val result = f1(r3._2) + assert(result == "2") + } + + def testE { + val f1 = (c: E[Int]) => c.value + val f2 = (x: Int) => new E(x) + val f3 = (c1: E[Int]) => (c2: E[Int]) => (c1, c2) + val r1 = f2(2) + val r2 = f2(2) + val r3 = f3(r1)(r2) + val result = f1(r3._2) + assert(result == 2) + } + + def main(args: Array[String]) { + testC + testD + testE + } +} diff --git a/test/files/run/t8017/value-class.scala b/test/files/run/t8017/value-class.scala new file mode 100644 index 0000000000..821239305f --- /dev/null +++ b/test/files/run/t8017/value-class.scala @@ -0,0 +1,3 @@ +class C(val value: Int) extends AnyVal +class D(val value: String) extends AnyVal +class E[A](val value: A) extends AnyVal |