summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Iry <james.iry@typesafe.com>2013-07-22 17:45:36 -0700
committerJames Iry <james.iry@typesafe.com>2013-11-06 12:28:04 -0800
commit10a061d425857c9e7bf4fa9aba9923b90467e24e (patch)
treea619f5e66a25a05da71cbbf24a282c4c17d4fb3c
parent9136e76ca1a9827e1a8c90fa4f7f63c2967cb019 (diff)
downloadscala-10a061d425857c9e7bf4fa9aba9923b90467e24e.tar.gz
scala-10a061d425857c9e7bf4fa9aba9923b90467e24e.tar.bz2
scala-10a061d425857c9e7bf4fa9aba9923b90467e24e.zip
Adds a setting to delay delambdafication. If set then uncurry lifts
the body of a lambda into a local def. Tests are included to show the different tree shapes.
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala127
-rw-r--r--test/files/run/delambdafy_uncurry_byname_inline.check21
-rw-r--r--test/files/run/delambdafy_uncurry_byname_inline.scala20
-rw-r--r--test/files/run/delambdafy_uncurry_byname_method.check15
-rw-r--r--test/files/run/delambdafy_uncurry_byname_method.scala20
-rw-r--r--test/files/run/delambdafy_uncurry_inline.check23
-rw-r--r--test/files/run/delambdafy_uncurry_inline.scala20
-rw-r--r--test/files/run/delambdafy_uncurry_method.check17
-rw-r--r--test/files/run/delambdafy_uncurry_method.scala20
10 files changed, 264 insertions, 21 deletions
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 01d5791f60..b8ca4adc14 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -184,6 +184,8 @@ trait ScalaSettings extends AbsScalaSettings
val YnoLoadImplClass = BooleanSetting ("-Yno-load-impl-class", "Do not load $class.class files.")
val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
+ // the current standard is "inline" but we are moving towards "method"
+ val Ydelambdafy = ChoiceSetting ("-Ydelambdafy", "strategy", "Strategy used for translating lambdas into JVM code.", List("inline", "method"), "inline")
private def removalIn212 = "This flag is scheduled for removal in 2.12. If you have a case where you need this flag then please report a bug."
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index ccf2266540..3d648ccbac 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -63,6 +63,7 @@ abstract class UnCurry extends InfoTransform
// uncurry and uncurryType expand type aliases
class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
+ private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline"
private var needTryLift = false
private var inConstructorFlag = 0L
private val byNameArgs = mutable.HashSet[Tree]()
@@ -223,34 +224,110 @@ abstract class UnCurry extends InfoTransform
val targs = fun.tpe.typeArgs
val (formals, restpe) = (targs.init, targs.last)
- val applyMethodDef = {
- val methSym = anonClass.newMethod(nme.apply, fun.pos, FINAL)
- val paramSyms = map2(formals, fun.vparams) {
- (tp, param) => methSym.newSyntheticValueParam(tp, param.name)
+ if (inlineFunctionExpansion) {
+ val applyMethodDef = {
+ val methSym = anonClass.newMethod(nme.apply, fun.pos, FINAL)
+ val paramSyms = map2(formals, fun.vparams) {
+ (tp, param) => methSym.newSyntheticValueParam(tp, param.name)
+ }
+ methSym setInfoAndEnter MethodType(paramSyms, restpe)
+
+ fun.vparams foreach (_.symbol.owner = methSym)
+ fun.body changeOwner (fun.symbol -> methSym)
+
+ val body = localTyper.typedPos(fun.pos)(fun.body)
+ val methDef = DefDef(methSym, List(fun.vparams), body)
+
+ // Have to repack the type to avoid mismatches when existentials
+ // appear in the result - see SI-4869.
+ methDef.tpt setType localTyper.packedType(body, methSym)
+ methDef
}
- methSym setInfoAndEnter MethodType(paramSyms, restpe)
- fun.vparams foreach (_.symbol.owner = methSym)
- fun.body changeOwner (fun.symbol -> methSym)
+ localTyper.typedPos(fun.pos) {
+ Block(
+ List(ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos)),
+ Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
+ }
+ } else {
+ /**
+ * Abstracts away the common functionality required to create both
+ * the lifted function and the apply method on the anonymous class
+ * It creates a method definition with value params cloned from the
+ * original lambda. Then it calls a supplied function to create
+ * the body and types the result. Finally
+ * everything is wrapped up in a MethodDef
+ *
+ * TODO it is intended that this common functionality be used
+ * whether inlineFunctionExpansion is true or not. However, it
+ * seems to introduce subtle ownwership changes that produce
+ * binary incompatible changes and so it is completely
+ * hidden behind the inlineFunctionExpansion for now.
+ *
+ * @param owner The owner for the new method
+ * @param name name for the new method
+ * @param additionalFlags flags to be put on the method in addition to FINAL
+ * @bodyF function that turns the method symbol and list of value params
+ * into a body for the method
+ */
+ def createMethod(owner: Symbol, name: TermName, additionalFlags: Long)(bodyF: (Symbol, List[ValDef]) => Tree) = {
+ val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
+ val vparams = fun.vparams map (_.duplicate)
+
+ val paramSyms = map2(formals, vparams) {
+ (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name)
+ }
+ foreach2(vparams, paramSyms){(valdef, sym) => valdef.symbol = sym}
+ vparams foreach (_.symbol.owner = methSym)
- val body = localTyper.typedPos(fun.pos)(fun.body)
- val methDef = DefDef(methSym, List(fun.vparams), body)
+ val methodType = MethodType(paramSyms, restpe.deconst)
+ methSym setInfo methodType
- // Have to repack the type to avoid mismatches when existentials
- // appear in the result - see SI-4869.
- methDef.tpt setType localTyper.packedType(body, methSym)
- methDef
- }
+ // TODO this is probably cleaner if bodyF only works with symbols rather than parameter ValDefs
+ val tempBody = bodyF(methSym, vparams)
+ val body = localTyper.typedPos(fun.pos)(tempBody)
+ val methDef = DefDef(methSym, List(vparams), body)
- localTyper.typedPos(fun.pos) {
- Block(
- List(ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos)),
- Typed(New(anonClass.tpe), TypeTree(fun.tpe)))
- }
+ // Have to repack the type to avoid mismatches when existentials
+ // appear in the result - see SI-4869.
+ methDef.tpt setType localTyper.packedType(body, methSym).deconst
+ methDef
+ }
- }
+ val methodFlags = ARTIFACT
+ // method definition with the same arguments, return type, and body as the original lambda
+ val liftedMethod = createMethod(fun.symbol.owner, tpnme.ANON_FUN_NAME.toTermName, methodFlags){
+ case(methSym, vparams) =>
+ fun.body.substituteSymbols(fun.vparams map (_.symbol), vparams map (_.symbol))
+ fun.body changeOwner (fun.symbol -> methSym)
+ }
+
+ // callsite for the lifted method
+ val args = fun.vparams map { vparam =>
+ val ident = Ident(vparam.symbol)
+ // if -Yeta-expand-keeps-star is turned on then T* types can get through. In order
+ // to forward them we need to forward x: T* ascribed as "x:_*"
+ if (settings.etaExpandKeepsStar && definitions.isRepeatedParamType(vparam.tpt.tpe))
+ gen.wildcardStar(ident)
+ else
+ ident
+ }
+
+ val funTyper = localTyper.typedPos(fun.pos) _
+
+ val liftedMethodCall = funTyper(Apply(liftedMethod.symbol, args:_*))
+
+ // new function whose body is just a call to the lifted method
+ val newFun = treeCopy.Function(fun, fun.vparams, liftedMethodCall)
+ funTyper(Block(
+ List(funTyper(liftedMethod)),
+ super.transform(newFun)
+ ))
+ }
+ }
}
+
def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = {
val isJava = fun.isJavaDefined
def transformVarargs(varargsElemType: Type) = {
@@ -381,7 +458,7 @@ abstract class UnCurry extends InfoTransform
deriveDefDef(dd)(_ => body)
case _ => tree
}
- def isNonLocalReturn(ret: Return) = ret.symbol != currentOwner.enclMethod || currentOwner.isLazy
+ def isNonLocalReturn(ret: Return) = ret.symbol != currentOwner.enclMethod || currentOwner.isLazy || currentOwner.isAnonymousFunction
// ------ The tree transformers --------------------------------------------------------
@@ -413,6 +490,10 @@ abstract class UnCurry extends InfoTransform
}
val sym = tree.symbol
+
+ // true if the taget is a lambda body that's been lifted into a method
+ def isLiftedLambdaBody(target: Tree) = target.symbol.isLocal && target.symbol.isArtifact && target.symbol.name.containsName(nme.ANON_FUN_NAME)
+
val result = (
// TODO - settings.noassertions.value temporarily retained to avoid
// breakage until a reasonable interface is settled upon.
@@ -494,6 +575,10 @@ abstract class UnCurry extends InfoTransform
val pat1 = transform(pat)
treeCopy.CaseDef(tree, pat1, transform(guard), transform(body))
+ // if a lambda is already the right shape we don't need to transform it again
+ case fun @ Function(_, Apply(target, _)) if (!inlineFunctionExpansion) && isLiftedLambdaBody(target) =>
+ super.transform(fun)
+
case fun @ Function(_, _) =>
mainTransform(transformFunction(fun))
diff --git a/test/files/run/delambdafy_uncurry_byname_inline.check b/test/files/run/delambdafy_uncurry_byname_inline.check
new file mode 100644
index 0000000000..0dc69b379a
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_byname_inline.check
@@ -0,0 +1,21 @@
+[[syntax trees at end of uncurry]] // newSource1.scala
+package <empty> {
+ class Foo extends Object {
+ def <init>(): Foo = {
+ Foo.super.<init>();
+ ()
+ };
+ def bar(x: () => Int): Int = x.apply();
+ def foo(): Int = Foo.this.bar({
+ @SerialVersionUID(0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction0[Int] with Serializable {
+ def <init>(): <$anon: () => Int> = {
+ $anonfun.super.<init>();
+ ()
+ };
+ final def apply(): Int = 1
+ };
+ (new <$anon: () => Int>(): () => Int)
+ })
+ }
+}
+
diff --git a/test/files/run/delambdafy_uncurry_byname_inline.scala b/test/files/run/delambdafy_uncurry_byname_inline.scala
new file mode 100644
index 0000000000..8f480fa804
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_byname_inline.scala
@@ -0,0 +1,20 @@
+import scala.tools.partest._
+import java.io.{Console => _, _}
+
+object Test extends DirectTest {
+
+ override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:inline -d " + testOutput.path
+
+ override def code = """class Foo {
+ | def bar(x: => Int) = x
+ |
+ | def foo = bar(1)
+ |}
+ |""".stripMargin.trim
+
+ override def show(): Unit = {
+ Console.withErr(System.out) {
+ compile()
+ }
+ }
+}
diff --git a/test/files/run/delambdafy_uncurry_byname_method.check b/test/files/run/delambdafy_uncurry_byname_method.check
new file mode 100644
index 0000000000..cd3edc7d6f
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_byname_method.check
@@ -0,0 +1,15 @@
+[[syntax trees at end of uncurry]] // newSource1.scala
+package <empty> {
+ class Foo extends Object {
+ def <init>(): Foo = {
+ Foo.super.<init>();
+ ()
+ };
+ def bar(x: () => Int): Int = x.apply();
+ def foo(): Int = Foo.this.bar({
+ final <artifact> def $anonfun(): Int = 1;
+ (() => $anonfun())
+ })
+ }
+}
+
diff --git a/test/files/run/delambdafy_uncurry_byname_method.scala b/test/files/run/delambdafy_uncurry_byname_method.scala
new file mode 100644
index 0000000000..1adeec8433
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_byname_method.scala
@@ -0,0 +1,20 @@
+import scala.tools.partest._
+import java.io.{Console => _, _}
+
+object Test extends DirectTest {
+
+ override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:method -Ystop-after:uncurry -d " + testOutput.path
+
+ override def code = """class Foo {
+ | def bar(x: => Int) = x
+ |
+ | def foo = bar(1)
+ |}
+ |""".stripMargin.trim
+
+ override def show(): Unit = {
+ Console.withErr(System.out) {
+ compile()
+ }
+ }
+}
diff --git a/test/files/run/delambdafy_uncurry_inline.check b/test/files/run/delambdafy_uncurry_inline.check
new file mode 100644
index 0000000000..e2b024b462
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_inline.check
@@ -0,0 +1,23 @@
+[[syntax trees at end of uncurry]] // newSource1.scala
+package <empty> {
+ class Foo extends Object {
+ def <init>(): Foo = {
+ Foo.super.<init>();
+ ()
+ };
+ def bar(): Unit = {
+ val f: Int => Int = {
+ @SerialVersionUID(0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1[Int,Int] with Serializable {
+ def <init>(): <$anon: Int => Int> = {
+ $anonfun.super.<init>();
+ ()
+ };
+ final def apply(x: Int): Int = x.+(1)
+ };
+ (new <$anon: Int => Int>(): Int => Int)
+ };
+ ()
+ }
+ }
+}
+
diff --git a/test/files/run/delambdafy_uncurry_inline.scala b/test/files/run/delambdafy_uncurry_inline.scala
new file mode 100644
index 0000000000..b42b65f5bb
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_inline.scala
@@ -0,0 +1,20 @@
+import scala.tools.partest._
+import java.io.{Console => _, _}
+
+object Test extends DirectTest {
+
+ override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:inline -d " + testOutput.path
+
+ override def code = """class Foo {
+ | def bar = {
+ | val f = {x: Int => x + 1}
+ | }
+ |}
+ |""".stripMargin.trim
+
+ override def show(): Unit = {
+ Console.withErr(System.out) {
+ compile()
+ }
+ }
+}
diff --git a/test/files/run/delambdafy_uncurry_method.check b/test/files/run/delambdafy_uncurry_method.check
new file mode 100644
index 0000000000..5ee3d174b3
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_method.check
@@ -0,0 +1,17 @@
+[[syntax trees at end of uncurry]] // newSource1.scala
+package <empty> {
+ class Foo extends Object {
+ def <init>(): Foo = {
+ Foo.super.<init>();
+ ()
+ };
+ def bar(): Unit = {
+ val f: Int => Int = {
+ final <artifact> def $anonfun(x: Int): Int = x.+(1);
+ ((x: Int) => $anonfun(x))
+ };
+ ()
+ }
+ }
+}
+
diff --git a/test/files/run/delambdafy_uncurry_method.scala b/test/files/run/delambdafy_uncurry_method.scala
new file mode 100644
index 0000000000..a988fb2ee7
--- /dev/null
+++ b/test/files/run/delambdafy_uncurry_method.scala
@@ -0,0 +1,20 @@
+import scala.tools.partest._
+import java.io.{Console => _, _}
+
+object Test extends DirectTest {
+
+ override def extraSettings: String = "-usejavacp -Xprint:uncurry -Ydelambdafy:method -Ystop-after:uncurry -d " + testOutput.path
+
+ override def code = """class Foo {
+ | def bar = {
+ | val f = {x: Int => x + 1}
+ | }
+ |}
+ |""".stripMargin.trim
+
+ override def show(): Unit = {
+ Console.withErr(System.out) {
+ compile()
+ }
+ }
+}