From 10a061d425857c9e7bf4fa9aba9923b90467e24e Mon Sep 17 00:00:00 2001 From: James Iry Date: Mon, 22 Jul 2013 17:45:36 -0700 Subject: 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. --- .../scala/tools/nsc/settings/ScalaSettings.scala | 2 + .../scala/tools/nsc/transform/UnCurry.scala | 127 +++++++++++++++++---- .../run/delambdafy_uncurry_byname_inline.check | 21 ++++ .../run/delambdafy_uncurry_byname_inline.scala | 20 ++++ .../run/delambdafy_uncurry_byname_method.check | 15 +++ .../run/delambdafy_uncurry_byname_method.scala | 20 ++++ test/files/run/delambdafy_uncurry_inline.check | 23 ++++ test/files/run/delambdafy_uncurry_inline.scala | 20 ++++ test/files/run/delambdafy_uncurry_method.check | 17 +++ test/files/run/delambdafy_uncurry_method.scala | 20 ++++ 10 files changed, 264 insertions(+), 21 deletions(-) create mode 100644 test/files/run/delambdafy_uncurry_byname_inline.check create mode 100644 test/files/run/delambdafy_uncurry_byname_inline.scala create mode 100644 test/files/run/delambdafy_uncurry_byname_method.check create mode 100644 test/files/run/delambdafy_uncurry_byname_method.scala create mode 100644 test/files/run/delambdafy_uncurry_inline.check create mode 100644 test/files/run/delambdafy_uncurry_inline.scala create mode 100644 test/files/run/delambdafy_uncurry_method.check create mode 100644 test/files/run/delambdafy_uncurry_method.scala 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 { + class Foo extends Object { + def (): Foo = { + Foo.super.(); + () + }; + def bar(x: () => Int): Int = x.apply(); + def foo(): Int = Foo.this.bar({ + @SerialVersionUID(0) final class $anonfun extends scala.runtime.AbstractFunction0[Int] with Serializable { + def (): <$anon: () => Int> = { + $anonfun.super.(); + () + }; + 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 { + class Foo extends Object { + def (): Foo = { + Foo.super.(); + () + }; + def bar(x: () => Int): Int = x.apply(); + def foo(): Int = Foo.this.bar({ + final 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 { + class Foo extends Object { + def (): Foo = { + Foo.super.(); + () + }; + def bar(): Unit = { + val f: Int => Int = { + @SerialVersionUID(0) final class $anonfun extends scala.runtime.AbstractFunction1[Int,Int] with Serializable { + def (): <$anon: Int => Int> = { + $anonfun.super.(); + () + }; + 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 { + class Foo extends Object { + def (): Foo = { + Foo.super.(); + () + }; + def bar(): Unit = { + val f: Int => Int = { + final 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() + } + } +} -- cgit v1.2.3