Skip to content
Browse files

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.
  • Loading branch information...
1 parent fcf1ada commit 6a4947c45c0b5fac3297da320b9627069a7b5ac4 @retronym retronym committed Dec 13, 2013
View
22 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)
View
1 test/files/run/t8017.flags
@@ -0,0 +1 @@
+-Ydelambdafy:method
View
40 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
+ }
+}
View
3 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

0 comments on commit 6a4947c

Please sign in to comment.
Something went wrong with that request. Please try again.