diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-11-07 10:56:47 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2013-11-07 10:56:47 -0800 |
commit | e72c11e18062b4e281cc38fb7727b3bd144c8594 (patch) | |
tree | c73549ee81ec9aacab999fc68673133940d5ae26 | |
parent | bb02fb350f7ec4aefcc830fec36a964ae45cfdd4 (diff) | |
parent | ef995ac6b4030e3cd10e487d414c941e5794666f (diff) | |
download | scala-e72c11e18062b4e281cc38fb7727b3bd144c8594.tar.gz scala-e72c11e18062b4e281cc38fb7727b3bd144c8594.tar.bz2 scala-e72c11e18062b4e281cc38fb7727b3bd144c8594.zip |
Merge pull request #3081 from JamesIry/wip_delayed_delambdafy_cleanup
Delay delambdafication and put the lambda's body into the containing class
64 files changed, 1261 insertions, 253 deletions
diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index b2cedf6338..1747405f03 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -91,7 +91,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { val values = List("namer", "typer", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", "erasure", "lazyvals", "lambdalift", "constructors", - "flatten", "mixin", "cleanup", "icode", "inliner", + "flatten", "mixin", "delambdafy", "cleanup", "icode", "inliner", "closelim", "dce", "jvm", "terminal") } diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index e765c9165a..1852e670e4 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -575,6 +575,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = None } with CleanUp + // phaseName = "delambdafy" + object delambdafy extends { + val global: Global.this.type = Global.this + val runsAfter = List("cleanup") + val runsRightAfter = None + } with Delambdafy + // phaseName = "icode" object genicode extends { val global: Global.this.type = Global.this @@ -695,6 +702,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) lambdaLift -> "move nested functions to top level", constructors -> "move field definitions into constructors", mixer -> "mixin composition", + delambdafy -> "remove lambdas", cleanup -> "platform-specific cleanups, generate reflective calls", genicode -> "generate portable intermediate code", inliner -> "optimization: do inlining", @@ -1068,6 +1076,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) @inline final def exitingExplicitOuter[T](op: => T): T = exitingPhase(currentRun.explicitouterPhase)(op) @inline final def exitingFlatten[T](op: => T): T = exitingPhase(currentRun.flattenPhase)(op) @inline final def exitingMixin[T](op: => T): T = exitingPhase(currentRun.mixinPhase)(op) + @inline final def exitingDelambdafy[T](op: => T): T = exitingPhase(currentRun.delambdafyPhase)(op) @inline final def exitingPickler[T](op: => T): T = exitingPhase(currentRun.picklerPhase)(op) @inline final def exitingRefchecks[T](op: => T): T = exitingPhase(currentRun.refchecksPhase)(op) @inline final def exitingSpecialize[T](op: => T): T = exitingPhase(currentRun.specializePhase)(op) @@ -1078,6 +1087,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) @inline final def enteringFlatten[T](op: => T): T = enteringPhase(currentRun.flattenPhase)(op) @inline final def enteringIcode[T](op: => T): T = enteringPhase(currentRun.icodePhase)(op) @inline final def enteringMixin[T](op: => T): T = enteringPhase(currentRun.mixinPhase)(op) + @inline final def enteringDelambdafy[T](op: => T): T = enteringPhase(currentRun.delambdafyPhase)(op) @inline final def enteringPickler[T](op: => T): T = enteringPhase(currentRun.picklerPhase)(op) @inline final def enteringSpecialize[T](op: => T): T = enteringPhase(currentRun.specializePhase)(op) @inline final def enteringTyper[T](op: => T): T = enteringPhase(currentRun.typerPhase)(op) @@ -1415,6 +1425,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") val mixinPhase = phaseNamed("mixin") + val delambdafyPhase = phaseNamed("delambdafy") val cleanupPhase = phaseNamed("cleanup") val icodePhase = phaseNamed("icode") val inlinerPhase = phaseNamed("inliner") diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 2f6f9620a8..8bbc382251 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -2370,7 +2370,8 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { case LOAD_MODULE(module) => // assert(module.isModule, "Expected module: " + module) debuglog("generating LOAD_MODULE for: " + module + " flags: " + module.flagString) - if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString) { + def inStaticMethod = this.method != null && this.method.symbol.isStaticMember + if (clasz.symbol == module.moduleClass && jMethodName != nme.readResolve.toString && !inStaticMethod) { jmethod.visitVarInsn(Opcodes.ALOAD, 0) } else { jmethod.visitFieldInsn( 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/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala new file mode 100644 index 0000000000..c546c21d48 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -0,0 +1,449 @@ +package scala.tools.nsc +package transform + +import symtab._ +import Flags._ +import scala.collection._ +import scala.language.postfixOps +import scala.reflect.internal.Symbols +import scala.collection.mutable.LinkedHashMap + +/** + * This transformer is responisble for turning lambdas into anonymous classes. + * The main assumption it makes is that a lambda {args => body} has been turned into + * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda. + * Currently Uncurry is responsible for that transformation. + * + * From a lambda, Delambdafy will create + * 1) a static forwarder at the top level of the class that contained the lambda + * 2) a new top level class that + a) has fields and a constructor taking the captured environment (including possbily the "this" + * reference) + * b) an apply method that calls the static forwarder + * c) if needed a bridge method for the apply method + * 3) an instantiation of the newly created class which replaces the lambda + * + * TODO the main work left to be done is to plug into specialization. Primarily that means choosing a + * specialized FunctionN trait instead of the generic FunctionN trait as a parent and creating the + * appropriately named applysp method + */ +abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer { + import global._ + import definitions._ + import CODE._ + + val analyzer: global.analyzer.type = global.analyzer + + /** the following two members override abstract members in Transform */ + val phaseName: String = "delambdafy" + + protected def newTransformer(unit: CompilationUnit): Transformer = + new DelambdafyTransformer(unit) + + class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter { + private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil + + + val typer = localTyper + + // we need to know which methods refer to the 'this' reference so that we can determine + // which lambdas need access to it + val thisReferringMethods: Set[Symbol] = { + val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser() + thisReferringMethodsTraverser traverse unit.body + val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences + val referrers = thisReferringMethodsTraverser.thisReferringMethods + // recursively find methods that refer to 'this' directly or indirectly via references to other methods + // for each method found add it to the referrers set + def refersToThis(symbol: Symbol): Boolean = { + if (referrers contains symbol) true + else if (methodReferringMap(symbol) exists refersToThis) { + // add it early to memoize + debuglog(s"$symbol indirectly refers to 'this'") + referrers += symbol + true + } else false + } + methodReferringMap.keys foreach refersToThis + referrers + } + + val accessorMethods = mutable.ArrayBuffer[Tree]() + + // the result of the transformFunction method. A class definition for the lambda, an expression + // insantiating the lambda class, and an accessor method for the lambda class to be able to + // call the implementation + case class TransformedFunction(lambdaClassDef: ClassDef, newExpr: Tree, accessorMethod: Tree) + + // here's the main entry point of the transform + override def transform(tree: Tree): Tree = tree match { + // the main thing we care about is lambdas + case fun @ Function(_, _) => + // a lambda beccomes a new class, an instantiation expression, and an + // accessor method + val TransformedFunction(lambdaClassDef, newExpr, accessorMethod) = transformFunction(fun) + // we'll add accessor methods to the current template later + accessorMethods += accessorMethod + val pkg = lambdaClassDef.symbol.owner + + // we'll add the lambda class to the package later + lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg) + + super.transform(newExpr) + // when we encounter a template (basically the thing that holds body of a class/trait) + // we need to updated it to include newly created accesor methods after transforming it + case Template(_, _, _) => + try { + // during this call accessorMethods will be populated from the Function case + val Template(parents, self, body) = super.transform(tree) + Template(parents, self, body ++ accessorMethods) + } finally accessorMethods.clear() + case _ => super.transform(tree) + } + + // this entry point is aimed at the statements in the compilation unit. + // after working on the entire compilation until we'll have a set of + // new class definitions to add to the top level + override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { + super.transformStats(stats, exprOwner) ++ lambdaClassDefs(exprOwner) + } + + private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None + + // turns a lambda into a new class def, a New expression instantiating that class, and an + // accessor method fo the body of the lambda + private def transformFunction(originalFunction: Function): TransformedFunction = { + val functionTpe = originalFunction.tpe + val targs = functionTpe.typeArgs + val formals :+ restpe = targs + val oldClass = originalFunction.symbol.enclClass + + // find which variables are free in the lambda because those are captures that need to be + // passed into the constructor of the anonymous function class + val captures = FreeVarTraverser.freeVarsOf(originalFunction) + + /** + * Creates the apply method for the anonymous subclass of FunctionN + */ + def createAccessorMethod(thisProxy: Symbol, fun: Function): DefDef = { + val target = targetMethod(fun) + if (!thisProxy.exists) { + target setFlag STATIC + } + val params = ((optionSymbol(thisProxy) map {proxy:Symbol => ValDef(proxy)}) ++ (target.paramss.flatten map ValDef)).toList + + val methSym = oldClass.newMethod(unit.freshTermName(nme.accessor.toString()), target.pos, FINAL | BRIDGE | SYNTHETIC | PROTECTED | STATIC) + + val paramSyms = params map {param => methSym.newSyntheticValueParam(param.symbol.tpe, param.name) } + + params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym } + params foreach (_.symbol.owner = methSym) + + val methodType = MethodType(paramSyms, restpe) + methSym setInfo methodType + + oldClass.info.decls enter methSym + + val body = localTyper.typed { + val newTarget = Select(if (thisProxy.exists) gen.mkAttributedRef(paramSyms(0)) else gen.mkAttributedThis(oldClass), target) + val newParams = paramSyms drop (if (thisProxy.exists) 1 else 0) map Ident + Apply(newTarget, newParams) + } setPos fun.pos + val methDef = DefDef(methSym, List(params), body) + + // Have to repack the type to avoid mismatches when existentials + // appear in the result - see SI-4869. + // TODO probably don't need packedType + methDef.tpt setType localTyper.packedType(body, methSym) + methDef + } + + /** + * Creates the apply method for the anonymous subclass of FunctionN + */ + def createApplyMethod(newClass: Symbol, fun: Function, accessor: DefDef, thisProxy: Symbol): DefDef = { + val methSym = newClass.newMethod(nme.apply, fun.pos, FINAL | SYNTHETIC) + val params = fun.vparams map (_.duplicate) + + val paramSyms = map2(formals, params) { + (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) + } + params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym } + params foreach (_.symbol.owner = methSym) + + val methodType = MethodType(paramSyms, restpe) + methSym setInfo methodType + + newClass.info.decls enter methSym + + val Apply(_, oldParams) = fun.body + + val body = localTyper typed Apply(Select(gen.mkAttributedThis(oldClass), accessor.symbol), (optionSymbol(thisProxy) map {tp => Select(gen.mkAttributedThis(newClass), tp)}).toList ++ oldParams) + body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol)) + body changeOwner (fun.symbol -> methSym) + + val methDef = DefDef(methSym, List(params), body) + + // Have to repack the type to avoid mismatches when existentials + // appear in the result - see SI-4869. + // TODO probably don't need packedType + methDef.tpt setType localTyper.packedType(body, methSym) + methDef + } + + /** + * Creates the constructor on the newly created class. It will handle + * initialization of members that represent the captured environment + */ + def createConstructor(newClass: Symbol, members: List[ValDef]): DefDef = { + val constrSym = newClass.newConstructor(originalFunction.pos, SYNTHETIC) + + val (paramSymbols, params, assigns) = (members map {member => + val paramSymbol = newClass.newVariable(member.symbol.name.toTermName, newClass.pos, 0) + paramSymbol.setInfo(member.symbol.info) + val paramVal = ValDef(paramSymbol) + val paramIdent = Ident(paramSymbol) + val assign = Assign(Select(gen.mkAttributedThis(newClass), member.symbol), paramIdent) + + (paramSymbol, paramVal, assign) + }).unzip3 + + val constrType = MethodType(paramSymbols, newClass.thisType) + constrSym setInfoAndEnter constrType + + val body = + Block( + List( + Apply(Select(Super(gen.mkAttributedThis(newClass), tpnme.EMPTY) setPos newClass.pos, nme.CONSTRUCTOR) setPos newClass.pos, Nil) setPos newClass.pos + ) ++ assigns, + Literal(Constant(())): Tree + ) setPos newClass.pos + + (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef] + } + + val pkg = oldClass.owner + + // Parent for anonymous class def + val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe + + // anonymous subclass of FunctionN with an apply method + def makeAnonymousClass = { + val parents = addSerializable(abstractFunctionErasedType) + val funOwner = originalFunction.symbol.owner + + val suffix = "$lambda$" + ( + if (funOwner.isPrimaryConstructor) "" + else "$" + funOwner.name + ) + val name = unit.freshTypeName(s"${oldClass.name.decode}$suffix") + + val anonClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation + anonClass setInfo ClassInfoType(parents, newScope, anonClass) + + val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol] + captures foreach {capture => + val sym = anonClass.newVariable(capture.name.toTermName, capture.pos, SYNTHETIC) + sym setInfo capture.info + captureProxies2 += ((capture, sym)) + } + + // the Optional proxy that will hold a reference to the 'this' + // object used by the lambda, if any. NoSymbol if there is no this proxy + val thisProxy = { + val target = targetMethod(originalFunction) + if (thisReferringMethods contains target) { + val sym = anonClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) + sym.info = oldClass.tpe + sym + } else NoSymbol + } + + val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, anonClass, originalFunction.symbol.pos, thisProxy) + + val accessorMethod = createAccessorMethod(thisProxy, originalFunction) + + val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] + + val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => + anonClass.info.decls enter member + ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos + } + + // constructor + val constr = createConstructor(anonClass, members) + + // apply method with same arguments and return type as original lambda. + val applyMethodDef = createApplyMethod(anonClass, decapturedFunction, accessorMethod, thisProxy) + + val bridgeMethod = createBridgeMethod(anonClass, originalFunction, applyMethodDef) + + def fulldef(sym: Symbol) = + if (sym == NoSymbol) sym.toString + else s"$sym: ${sym.tpe} in ${sym.owner}" + + def clashError(bm: Symbol) = { + unit.error( + applyMethodDef.symbol.pos, + sm"""bridge generated for member ${fulldef(applyMethodDef.symbol)} + |which overrides ${fulldef(getMember(abstractFunctionErasedType.typeSymbol, nme.apply))} + |clashes with definition of the member itself; + |both have erased type ${exitingPostErasure(bm.tpe)}""") + } + + bridgeMethod foreach (bm => + if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe) + clashError(bm.symbol) + ) + + val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod + + // TODO if member fields are private this complains that they're not accessible + (localTyper.typedPos(decapturedFunction.pos)(ClassDef(anonClass, body)).asInstanceOf[ClassDef], thisProxy, accessorMethod) + } + + val (anonymousClassDef, thisProxy, accessorMethod) = makeAnonymousClass + + pkg.info.decls enter anonymousClassDef.symbol + + val thisArg = optionSymbol(thisProxy) map (_ => gen.mkAttributedThis(oldClass) setPos originalFunction.pos) + val captureArgs = captures map (capture => Ident(capture) setPos originalFunction.pos) + + val newStat = + Typed(New(anonymousClassDef.symbol, (thisArg.toList ++ captureArgs): _*), TypeTree(abstractFunctionErasedType)) + + val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) + + TransformedFunction(anonymousClassDef, typedNewStat, accessorMethod) + } + + /** + * Creates a bridge method if needed. The bridge method forwards from apply(x1: Object, x2: Object...xn: Object): Object to + * apply(x1: T1, x2: T2...xn: Tn): T0 using type adaptation on each input and output. The only time a bridge isn't needed + * is when the original lambda is already erased to type Object, Object, Object... => Object + */ + def createBridgeMethod(newClass:Symbol, originalFunction: Function, applyMethod: DefDef): Option[DefDef] = { + val bridgeMethSym = newClass.newMethod(nme.apply, applyMethod.pos, FINAL | SYNTHETIC | BRIDGE) + val originalParams = applyMethod.vparamss(0) + val bridgeParams = originalParams map { originalParam => + val bridgeSym = bridgeMethSym.newSyntheticValueParam(ObjectTpe, originalParam.name) + ValDef(bridgeSym) + } + + val bridgeSyms = bridgeParams map (_.symbol) + + val methodType = MethodType(bridgeSyms, ObjectTpe) + bridgeMethSym setInfo methodType + + def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = { + if (tree.tpe =:= expectedTpe) (false, tree) + else (true, adaptToType(tree, expectedTpe)) + } + + enteringPhase(currentRun.posterasurePhase) { + val liftedBodyDefTpe: MethodType = { + val liftedBodySymbol = { + val Apply(method, _) = originalFunction.body + method.symbol + } + 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) + val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation + if (needsBridge) { + val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody) + newClass.info.decls enter bridgeMethSym + Some((localTyper typed methDef).asInstanceOf[DefDef]) + } else None + } + } + } // DelambdafyTransformer + + // A traverser that finds symbols used but not defined in the given Tree + // TODO freeVarTraverser in LambdaLift does a very similar task. With some + // analysis this could probably be unified with it + class FreeVarTraverser extends Traverser { + val freeVars = mutable.LinkedHashSet[Symbol]() + val declared = mutable.LinkedHashSet[Symbol]() + + override def traverse(tree: Tree) = { + tree match { + case Function(args, _) => + args foreach {arg => declared += arg.symbol} + case ValDef(_, _, _, _) => + declared += tree.symbol + case _: Bind => + declared += tree.symbol + case Ident(_) => + val sym = tree.symbol + if ((sym != NoSymbol) && sym.isLocal && sym.isTerm && !sym.isMethod && !declared.contains(sym)) freeVars += sym + case _ => + } + super.traverse(tree) + } + } + + object FreeVarTraverser { + def freeVarsOf(function: Function) = { + val freeVarsTraverser = new FreeVarTraverser + freeVarsTraverser.traverse(function) + freeVarsTraverser.freeVars + } + } + + // A transformer that converts specified captured symbols into other symbols + // TODO this transform could look more like ThisSubstituter and TreeSymSubstituter. It's not clear that it needs that level of sophistication since the types + // at this point are always very simple flattened/erased types, but it would probably be more robust if it tried to take more complicated types into account + class DeCapturifyTransformer(captureProxies: Map[Symbol, TermSymbol], unit: CompilationUnit, oldClass: Symbol, newClass:Symbol, pos: Position, thisProxy: Symbol) extends TypingTransformer(unit) { + override def transform(tree: Tree) = tree match { + case tree@This(encl) if tree.symbol == oldClass && thisProxy.exists => + gen mkAttributedSelect (gen mkAttributedThis newClass, thisProxy) + case Ident(name) if (captureProxies contains tree.symbol) => + gen mkAttributedSelect (gen mkAttributedThis newClass, captureProxies(tree.symbol)) + case _ => super.transform(tree) + } + } + + /** + * Get the symbol of the target lifted lambad body method from a function. I.e. if + * the function is {args => anonfun(args)} then this method returns anonfun's symbol + */ + private def targetMethod(fun: Function): Symbol = fun match { + case Function(_, Apply(target, _)) => + target.symbol + case _ => + // any other shape of Function is unexpected at this point + abort(s"could not understand function with tree $fun") + } + + // finds all methods that reference 'this' + class ThisReferringMethodsTraverser() extends Traverser { + private var currentMethod: Symbol = NoSymbol + // the set of methods that refer to this + val thisReferringMethods = mutable.Set[Symbol]() + // the set of lifted lambda body methods that each method refers to + val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + override def traverse(tree: Tree) = tree match { + case DefDef(_, _, _, _, _, _) => + // we don't expect defs within defs. At this phase trees should be very flat + if (currentMethod.exists) devWarning("Found a def within a def at a phase where defs are expected to be flattened out.") + currentMethod = tree.symbol + super.traverse(tree) + currentMethod = NoSymbol + case fun@Function(_, _) => + // we don't drill into functions because at the beginning of this phase they will always refer to 'this'. + // They'll be of the form {(args...) => this.anonfun(args...)} + // but we do need to make note of the lifted body method in case it refers to 'this' + if (currentMethod.exists) liftedMethodReferences(currentMethod) += targetMethod(fun) + case This(_) => + if (currentMethod.exists && tree.symbol == currentMethod.enclClass) { + debuglog(s"$currentMethod directly refers to 'this'") + thisReferringMethods add currentMethod + } + case _ => + super.traverse(tree) + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index f0d3db1296..68f1c81c59 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -17,11 +17,15 @@ abstract class Erasure extends AddInterfaces with typechecker.Analyzer with TypingTransformers with ast.TreeDSL + with TypeAdaptingTransformer { import global._ import definitions._ import CODE._ + val analyzer: typechecker.Analyzer { val global: Erasure.this.global.type } = + this.asInstanceOf[typechecker.Analyzer { val global: Erasure.this.global.type }] + val phaseName: String = "erasure" def newTransformer(unit: CompilationUnit): Transformer = @@ -352,13 +356,6 @@ abstract class Erasure extends AddInterfaces override def newTyper(context: Context) = new Eraser(context) - private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { - isUnbox(fn.symbol) && { - val cls = arg.tpe.typeSymbol - (cls == definitions.NullClass) || isBoxedValueClass(cls) - } - } - class ComputeBridges(unit: CompilationUnit, root: Symbol) { assert(phase == currentRun.erasurePhase, phase) @@ -522,158 +519,8 @@ abstract class Erasure extends AddInterfaces } /** The modifier typer which retypes with erased types. */ - class Eraser(_context: Context) extends Typer(_context) { - - private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) - - private def isDifferentErasedValueType(tpe: Type, other: Type) = - isErasedValueType(tpe) && (tpe ne other) - - private def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) - - @inline private def box(tree: Tree, target: => String): Tree = { - val result = box1(tree) - if (tree.tpe =:= UnitTpe) () - else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") - result - } - - /** Box `tree` of unboxed type */ - private def box1(tree: Tree): Tree = tree match { - case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(box1) - ldef setType ldef.rhs.tpe - case _ => - val tree1 = tree.tpe match { - case ErasedValueType(clazz, _) => - New(clazz, cast(tree, underlyingOfValueClass(clazz))) - case _ => - tree.tpe.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) - else BLOCK(tree, REF(BoxedUnit_UNIT)) - case NothingClass => tree // a non-terminating expression doesn't need boxing - case x => - assert(x != ArrayClass) - tree match { - /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x - * may lead to throwing an exception. - * - * This is important for specialization: calls to the super constructor should not box/unbox specialized - * fields (see TupleX). (ID) - */ - case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => - log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") - arg - case _ => - (REF(boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe - } - } - } - typedPos(tree.pos)(tree1) - } - - private def unbox(tree: Tree, pt: Type): Tree = { - val result = unbox1(tree, pt) - log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") - result - } - - /** Unbox `tree` of boxed type to expected type `pt`. - * - * @param tree the given tree - * @param pt the expected type. - * @return the unboxed tree - */ - private def unbox1(tree: Tree, pt: Type): Tree = tree match { -/* - case Boxed(unboxed) => - println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. - adaptToType(unboxed, pt) - */ - case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(unbox(_, pt)) - ldef setType ldef.rhs.tpe - case _ => - val tree1 = pt match { - case ErasedValueType(clazz, underlying) => - val tree0 = - if (tree.tpe.typeSymbol == NullClass && - isPrimitiveValueClass(underlying.typeSymbol)) { - // convert `null` directly to underlying type, as going - // via the unboxed type would yield a NPE (see SI-5866) - unbox1(tree, underlying) - } else - Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) - cast(tree0, pt) - case _ => - pt.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) UNIT - else BLOCK(tree, UNIT) - case x => - assert(x != ArrayClass) - // don't `setType pt` the Apply tree, as the Apply's fun won't be typechecked if the Apply tree already has a type - Apply(unboxMethod(pt.typeSymbol), tree) - } - } - typedPos(tree.pos)(tree1) - } - - /** Generate a synthetic cast operation from tree.tpe to pt. - * @pre pt eq pt.normalize - */ - private def cast(tree: Tree, pt: Type): Tree = { - if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { - def word = ( - if (tree.tpe <:< pt) "upcast" - else if (pt <:< tree.tpe) "downcast" - else if (pt weak_<:< tree.tpe) "coerce" - else if (tree.tpe weak_<:< pt) "widen" - else "cast" - ) - log(s"erasure ${word}s from ${tree.tpe} to $pt") - } - if (pt =:= UnitTpe) { - // See SI-4731 for one example of how this occurs. - log("Attempted to cast to Unit: " + tree) - tree.duplicate setType pt - } else if (tree.tpe != null && tree.tpe.typeSymbol == ArrayClass && pt.typeSymbol == ArrayClass) { - // See SI-2386 for one example of when this might be necessary. - val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head) - val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree - gen.mkAttributedCast(tree1, pt) - } else gen.mkAttributedCast(tree, pt) - } - - /** Adapt `tree` to expected type `pt`. - * - * @param tree the given tree - * @param pt the expected type - * @return the adapted tree - */ - private def adaptToType(tree: Tree, pt: Type): Tree = { - if (settings.debug && pt != WildcardType) - log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug - if (tree.tpe <:< pt) - tree - else if (isDifferentErasedValueType(tree.tpe, pt)) - adaptToType(box(tree, pt.toString), pt) - else if (isDifferentErasedValueType(pt, tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { - adaptToType(box(tree, pt.toString), pt) - } else if (isMethodTypeWithEmptyParams(tree.tpe)) { - // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val - //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) - adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) -// } else if (pt <:< tree.tpe) -// cast(tree, pt) - } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else - cast(tree, pt) - } + class Eraser(_context: Context) extends Typer(_context) with TypeAdapter { + val typer = this.asInstanceOf[analyzer.Typer] /** Replace member references as follows: * @@ -834,11 +681,6 @@ abstract class Erasure extends AddInterfaces tree1 } } - - private def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { - case MethodType(Nil, _) => true - case _ => false - } } /** The erasure transformer */ diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala index 9e8cbe6c03..2235a93ca4 100644 --- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala +++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala @@ -129,7 +129,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers { if (seen contains clazz) unit.error(pos, "value class may not unbox to itself") else { - val unboxed = erasure.underlyingOfValueClass(clazz).typeSymbol + val unboxed = definitions.underlyingOfValueClass(clazz).typeSymbol if (unboxed.isDerivedValueClass) checkNonCyclic(pos, seen + clazz, unboxed) } diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 2d4269a3bc..4cf3bef939 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -55,6 +55,8 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { import definitions._ import Flags._ + private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline" + /** the name of the phase: */ val phaseName: String = "specialize" @@ -1318,7 +1320,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } private def isAccessible(sym: Symbol): Boolean = - (currentClass == sym.owner.enclClass) && (currentClass != targetClass) + if (currentOwner.isAnonymousFunction) { + if (inlineFunctionExpansion) devWarning("anonymous function made it to specialization even though inline expansion is set.") + false + } + else (currentClass == sym.owner.enclClass) && (currentClass != targetClass) private def shouldMakePublic(sym: Symbol): Boolean = sym.hasFlag(PRIVATE | PROTECTED) && (addressFields || !nme.isLocalName(sym.name)) diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala new file mode 100644 index 0000000000..41b8461c46 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -0,0 +1,187 @@ +package scala.tools.nsc +package transform + +import scala.reflect.internal._ +import scala.tools.nsc.ast.TreeDSL +import scala.tools.nsc.Global + +/** + * A trait usable by transforms that need to adapt trees of one type to another type + */ +trait TypeAdaptingTransformer { + self: TreeDSL => + + val analyzer: typechecker.Analyzer { val global: self.global.type } + + trait TypeAdapter { + val typer: analyzer.Typer + import global._ + import definitions._ + import CODE._ + + def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { + case MethodType(Nil, _) => true + case _ => false + } + + private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { + isUnbox(fn.symbol) && { + val cls = arg.tpe.typeSymbol + (cls == definitions.NullClass) || isBoxedValueClass(cls) + } + } + + private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) + + private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType] + + private def isDifferentErasedValueType(tpe: Type, other: Type) = + isErasedValueType(tpe) && (tpe ne other) + + def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) + + @inline def box(tree: Tree, target: => String): Tree = { + val result = box1(tree) + if (tree.tpe =:= UnitTpe) () + else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") + result + } + + /** Box `tree` of unboxed type */ + private def box1(tree: Tree): Tree = tree match { + case LabelDef(_, _, _) => + val ldef = deriveLabelDef(tree)(box1) + ldef setType ldef.rhs.tpe + case _ => + val tree1 = tree.tpe match { + case ErasedValueType(clazz, _) => + New(clazz, cast(tree, underlyingOfValueClass(clazz))) + case _ => + tree.tpe.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) + else BLOCK(tree, REF(BoxedUnit_UNIT)) + case NothingClass => tree // a non-terminating expression doesn't need boxing + case x => + assert(x != ArrayClass) + tree match { + /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x + * may lead to throwing an exception. + * + * This is important for specialization: calls to the super constructor should not box/unbox specialized + * fields (see TupleX). (ID) + */ + case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => + log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") + arg + case _ => + (REF(boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe + } + } + } + typer.typedPos(tree.pos)(tree1) + } + + def unbox(tree: Tree, pt: Type): Tree = { + val result = unbox1(tree, pt) + log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") + result + } + + /** Unbox `tree` of boxed type to expected type `pt`. + * + * @param tree the given tree + * @param pt the expected type. + * @return the unboxed tree + */ + private def unbox1(tree: Tree, pt: Type): Tree = tree match { +/* + case Boxed(unboxed) => + println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. + adaptToType(unboxed, pt) + */ + case LabelDef(_, _, _) => + val ldef = deriveLabelDef(tree)(unbox(_, pt)) + ldef setType ldef.rhs.tpe + case _ => + val tree1 = pt match { + case ErasedValueType(clazz, underlying) => + val tree0 = + if (tree.tpe.typeSymbol == NullClass && + isPrimitiveValueClass(underlying.typeSymbol)) { + // convert `null` directly to underlying type, as going + // via the unboxed type would yield a NPE (see SI-5866) + unbox1(tree, underlying) + } else + Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) + cast(tree0, pt) + case _ => + pt.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) UNIT + else BLOCK(tree, UNIT) + case x => + assert(x != ArrayClass) + // don't `setType pt` the Apply tree, as the Apply's fun won't be typechecked if the Apply tree already has a type + Apply(unboxMethod(pt.typeSymbol), tree) + } + } + typer.typedPos(tree.pos)(tree1) + } + + /** Generate a synthetic cast operation from tree.tpe to pt. + * @pre pt eq pt.normalize + */ + def cast(tree: Tree, pt: Type): Tree = { + if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { + def word = ( + if (tree.tpe <:< pt) "upcast" + else if (pt <:< tree.tpe) "downcast" + else if (pt weak_<:< tree.tpe) "coerce" + else if (tree.tpe weak_<:< pt) "widen" + else "cast" + ) + log(s"erasure ${word}s from ${tree.tpe} to $pt") + } + if (pt =:= UnitTpe) { + // See SI-4731 for one example of how this occurs. + log("Attempted to cast to Unit: " + tree) + tree.duplicate setType pt + } else if (tree.tpe != null && tree.tpe.typeSymbol == ArrayClass && pt.typeSymbol == ArrayClass) { + // See SI-2386 for one example of when this might be necessary. + val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head) + val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree + gen.mkAttributedCast(tree1, pt) + } else gen.mkAttributedCast(tree, pt) + } + + /** Adapt `tree` to expected type `pt`. + * + * @param tree the given tree + * @param pt the expected type + * @return the adapted tree + */ + def adaptToType(tree: Tree, pt: Type): Tree = { + if (settings.debug && pt != WildcardType) + log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug + if (tree.tpe <:< pt) + tree + else if (isDifferentErasedValueType(tree.tpe, pt)) + adaptToType(box(tree, pt.toString), pt) + else if (isDifferentErasedValueType(pt, tree.tpe)) + adaptToType(unbox(tree, pt), pt) + else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { + adaptToType(box(tree, pt.toString), pt) + } else if (isMethodTypeWithEmptyParams(tree.tpe)) { + // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val + //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) + adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) +// } else if (pt <:< tree.tpe) +// cast(tree, pt) + } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) + adaptToType(unbox(tree, pt), pt) + else + cast(tree, pt) + } + } +}
\ No newline at end of file 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/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 396f3407f3..69ae6ec0c8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -136,8 +136,8 @@ abstract class Duplicators extends Analyzer { sym private def invalidate(tree: Tree, owner: Symbol = NoSymbol) { - debuglog("attempting to invalidate " + tree.symbol) - if (tree.isDef && tree.symbol != NoSymbol) { + debuglog(s"attempting to invalidate symbol = ${tree.symbol}") + if ((tree.isDef || tree.isInstanceOf[Function]) && tree.symbol != NoSymbol) { debuglog("invalid " + tree.symbol) invalidSyms(tree.symbol) = tree @@ -166,6 +166,11 @@ abstract class Duplicators extends Analyzer { invalidateAll(tparams ::: vparamss.flatten) tree.symbol = NoSymbol + case Function(vparams, _) => + // invalidate parameters + invalidateAll(vparams) + tree.symbol = NoSymbol + case _ => tree.symbol = NoSymbol } @@ -226,6 +231,10 @@ abstract class Duplicators extends Analyzer { ddef.tpt modifyType fixType super.typed(ddef.clearType(), mode, pt) + case fun: Function => + debuglog("Clearing the type and retyping Function: " + fun) + super.typed(fun.clearType, mode, pt) + case vdef @ ValDef(mods, name, tpt, rhs) => // log("vdef fixing tpe: " + tree.tpe + " with sym: " + tree.tpe.typeSymbol + " and " + invalidSyms) //if (mods.hasFlag(Flags.LAZY)) vdef.symbol.resetFlag(Flags.MUTABLE) // Martin to Iulian: lazy vars can now appear because they are no longer boxed; Please check that deleting this statement is OK. diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7b88514429..ea680867da 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -153,6 +153,10 @@ trait Definitions extends api.StandardDefinitions { DoubleClass ) def ScalaPrimitiveValueClasses: List[ClassSymbol] = ScalaValueClasses + + def underlyingOfValueClass(clazz: Symbol): Type = + clazz.derivedValueClassUnbox.tpe.resultType + } abstract class DefinitionsClass extends DefinitionsApi with ValueClassDefinitions { diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 9f56e78059..02f22a16f6 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -609,6 +609,7 @@ trait StdNames { val TypeRef: NameType = "TypeRef" val TypeTree: NameType = "TypeTree" val UNIT : NameType = "UNIT" + val accessor: NameType = "accessor" val add_ : NameType = "add" val annotation: NameType = "annotation" val anyValClass: NameType = "anyValClass" diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 9adddeed50..743c674eea 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1001,7 +1001,8 @@ trait Trees extends api.Trees { // ---- values and creators --------------------------------------- /** @param sym the class symbol - * @return the implementation template + * @param impl the implementation template + * @return the class definition */ def ClassDef(sym: Symbol, impl: Template): ClassDef = atPos(sym.pos) { @@ -1011,6 +1012,25 @@ trait Trees extends api.Trees { impl) setSymbol sym } + /** @param sym the class symbol + * @param body trees that constitute the body of the class + * @return the class definition + */ + def ClassDef(sym: Symbol, body: List[Tree]): ClassDef = + ClassDef(sym, Template(sym, body)) + + /** @param sym the template's symbol + * @param body trees that constitute the body of the template + * @return the template + */ + def Template(sym: Symbol, body: List[Tree]): Template = { + atPos(sym.pos) { + Template(sym.info.parents map TypeTree, + if (sym.thisSym == sym) noSelfType else ValDef(sym), + body) + } + } + /** * @param sym the class symbol * @param impl the implementation template diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 4aefc105e3..3eb3a4cdf4 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -77,9 +77,6 @@ trait Erasure { if (cls.owner.isClass) cls.owner.tpe_* else pre // why not cls.isNestedClass? } - def underlyingOfValueClass(clazz: Symbol): Type = - clazz.derivedValueClassUnbox.tpe.resultType - /** The type of the argument of a value class reference after erasure * This method needs to be called at a phase no later than erasurephase */ diff --git a/src/repl/scala/tools/nsc/interpreter/Phased.scala b/src/repl/scala/tools/nsc/interpreter/Phased.scala index f625124e70..1cdbd65949 100644 --- a/src/repl/scala/tools/nsc/interpreter/Phased.scala +++ b/src/repl/scala/tools/nsc/interpreter/Phased.scala @@ -91,7 +91,7 @@ trait Phased { Parser, Namer, Packageobjects, Typer, Superaccessors, Pickler, Refchecks, Selectiveanf, Liftcode, Selectivecps, Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Lazyvals, Lambdalift, Constructors, Flatten, Mixin, - Cleanup, Icode, Inliner, Closelim, Dce, Jvm, Terminal + Cleanup, Delambdafy, Icode, Inliner, Closelim, Dce, Jvm, Terminal ) lazy val nameMap = all.map(x => x.name -> x).toMap withDefaultValue NoPhaseName multi = all @@ -127,6 +127,7 @@ trait Phased { case object Flatten extends PhaseName case object Mixin extends PhaseName case object Cleanup extends PhaseName + case object Delambdafy extends PhaseName case object Icode extends PhaseName case object Inliner extends PhaseName case object Closelim extends PhaseName diff --git a/test/files/instrumented/inline-in-constructors.flags b/test/files/instrumented/inline-in-constructors.flags index c9b68d70dc..068318e8ac 100644 --- a/test/files/instrumented/inline-in-constructors.flags +++ b/test/files/instrumented/inline-in-constructors.flags @@ -1 +1 @@ --optimise +-optimise -Ydelambdafy:inline diff --git a/test/files/jvm/future-spec/FutureTests.scala b/test/files/jvm/future-spec/FutureTests.scala index 5d213691df..1595b2c862 100644 --- a/test/files/jvm/future-spec/FutureTests.scala +++ b/test/files/jvm/future-spec/FutureTests.scala @@ -10,7 +10,7 @@ import scala.util.{Try,Success,Failure} -object FutureTests extends MinimalScalaTest { +class FutureTests extends MinimalScalaTest { /* some utils */ diff --git a/test/files/jvm/future-spec/PromiseTests.scala b/test/files/jvm/future-spec/PromiseTests.scala index 6e613bf3ec..49350586b8 100644 --- a/test/files/jvm/future-spec/PromiseTests.scala +++ b/test/files/jvm/future-spec/PromiseTests.scala @@ -9,7 +9,7 @@ import scala.runtime.NonLocalReturnControl import scala.util.{Try,Success,Failure} -object PromiseTests extends MinimalScalaTest { +class PromiseTests extends MinimalScalaTest { import ExecutionContext.Implicits._ val defaultTimeout = Inf diff --git a/test/files/jvm/future-spec/TryTests.scala b/test/files/jvm/future-spec/TryTests.scala index 5d1b9b84b4..01bb3c9d36 100644 --- a/test/files/jvm/future-spec/TryTests.scala +++ b/test/files/jvm/future-spec/TryTests.scala @@ -5,7 +5,7 @@ import scala.util.{Try,Success,Failure} -object TryTests extends MinimalScalaTest { +class TryTests extends MinimalScalaTest { class MyException extends Exception val e = new Exception("this is an exception") diff --git a/test/files/jvm/future-spec/main.scala b/test/files/jvm/future-spec/main.scala index 132263e2e8..697d0fe91f 100644 --- a/test/files/jvm/future-spec/main.scala +++ b/test/files/jvm/future-spec/main.scala @@ -10,9 +10,9 @@ import java.util.concurrent.{ TimeoutException, CountDownLatch, TimeUnit } object Test { def main(args: Array[String]) { - FutureTests.check() - PromiseTests.check() - TryTests.check() + (new FutureTests).check() + (new PromiseTests).check() + (new TryTests).check() } } diff --git a/test/files/jvm/t7006.check b/test/files/jvm/t7006.check index 7c99eba30c..6294b14d62 100644 --- a/test/files/jvm/t7006.check +++ b/test/files/jvm/t7006.check @@ -19,6 +19,7 @@ [running phase flatten on Foo_1.scala] [running phase mixin on Foo_1.scala] [running phase cleanup on Foo_1.scala] +[running phase delambdafy on Foo_1.scala] [running phase icode on Foo_1.scala] [running phase inliner on Foo_1.scala] [running phase inlinehandlers on Foo_1.scala] diff --git a/test/files/neg/delambdafy_t6260_method.check b/test/files/neg/delambdafy_t6260_method.check new file mode 100644 index 0000000000..f5cd6947d1 --- /dev/null +++ b/test/files/neg/delambdafy_t6260_method.check @@ -0,0 +1,13 @@ +delambdafy_t6260_method.scala:3: error: bridge generated for member method apply: (bx: Object)Object in class map$extension1 +which overrides method apply: (v1: Object)Object in trait Function1 +clashes with definition of the member itself; +both have erased type (bx: Object)Object + ((bx: Box[X]) => new Box(f(bx.x)))(this) + ^ +delambdafy_t6260_method.scala:8: error: bridge generated for member method apply: (bx: Object)Object in class map21 +which overrides method apply: (v1: Object)Object in trait Function1 +clashes with definition of the member itself; +both have erased type (bx: Object)Object + ((bx: Box[X]) => new Box(f(bx.x)))(self) + ^ +two errors found diff --git a/test/files/neg/delambdafy_t6260_method.flags b/test/files/neg/delambdafy_t6260_method.flags new file mode 100644 index 0000000000..48b438ddf8 --- /dev/null +++ b/test/files/neg/delambdafy_t6260_method.flags @@ -0,0 +1 @@ +-Ydelambdafy:method diff --git a/test/files/neg/delambdafy_t6260_method.scala b/test/files/neg/delambdafy_t6260_method.scala new file mode 100644 index 0000000000..93b5448227 --- /dev/null +++ b/test/files/neg/delambdafy_t6260_method.scala @@ -0,0 +1,17 @@ +class Box[X](val x: X) extends AnyVal { + def map[Y](f: X => Y): Box[Y] = + ((bx: Box[X]) => new Box(f(bx.x)))(this) +} + +object Test { + def map2[X, Y](self: Box[X], f: X => Y): Box[Y] = + ((bx: Box[X]) => new Box(f(bx.x)))(self) + + def main(args: Array[String]) { + val f = (x: Int) => x + 1 + val g = (x: String) => x + x + + map2(new Box(42), f) + new Box("abc") map g + } +} diff --git a/test/files/neg/t6231.flags b/test/files/neg/t6231.flags new file mode 100644 index 0000000000..ac96850b69 --- /dev/null +++ b/test/files/neg/t6231.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline
\ No newline at end of file diff --git a/test/files/neg/t6260.flags b/test/files/neg/t6260.flags new file mode 100644 index 0000000000..2349d8294d --- /dev/null +++ b/test/files/neg/t6260.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline diff --git a/test/files/neg/t6446-additional.check b/test/files/neg/t6446-additional.check index c91333830a..a87af2f1e5 100755 --- a/test/files/neg/t6446-additional.check +++ b/test/files/neg/t6446-additional.check @@ -21,18 +21,19 @@ superaccessors 6 add super accessors in traits and nested classes flatten 19 eliminate inner classes mixin 20 mixin composition cleanup 21 platform-specific cleanups, generate reflective calls - icode 22 generate portable intermediate code + delambdafy 22 remove lambdas + icode 23 generate portable intermediate code #partest -optimise - inliner 23 optimization: do inlining -inlinehandlers 24 optimization: inline exception handlers - closelim 25 optimization: eliminate uncalled closures - constopt 26 optimization: optimize null and other constants - dce 27 optimization: eliminate dead code - jvm 28 generate JVM bytecode - ploogin 29 A sample phase that does so many things it's kind of hard... - terminal 30 the last phase during a compilation run + inliner 24 optimization: do inlining +inlinehandlers 25 optimization: inline exception handlers + closelim 26 optimization: eliminate uncalled closures + constopt 27 optimization: optimize null and other constants + dce 28 optimization: eliminate dead code + jvm 29 generate JVM bytecode + ploogin 30 A sample phase that does so many things it's kind of hard... + terminal 31 the last phase during a compilation run #partest !-optimise - jvm 23 generate JVM bytecode - ploogin 24 A sample phase that does so many things it's kind of hard... - terminal 25 the last phase during a compilation run + jvm 24 generate JVM bytecode + ploogin 25 A sample phase that does so many things it's kind of hard... + terminal 26 the last phase during a compilation run #partest diff --git a/test/files/neg/t6446-missing.check b/test/files/neg/t6446-missing.check index b2d5ddd686..cd867289c3 100755 --- a/test/files/neg/t6446-missing.check +++ b/test/files/neg/t6446-missing.check @@ -22,16 +22,17 @@ superaccessors 6 add super accessors in traits and nested classes flatten 19 eliminate inner classes mixin 20 mixin composition cleanup 21 platform-specific cleanups, generate reflective calls - icode 22 generate portable intermediate code + delambdafy 22 remove lambdas + icode 23 generate portable intermediate code #partest !-optimise - jvm 23 generate JVM bytecode - terminal 24 the last phase during a compilation run + jvm 24 generate JVM bytecode + terminal 25 the last phase during a compilation run #partest -optimise - inliner 23 optimization: do inlining -inlinehandlers 24 optimization: inline exception handlers - closelim 25 optimization: eliminate uncalled closures - constopt 26 optimization: optimize null and other constants - dce 27 optimization: eliminate dead code - jvm 28 generate JVM bytecode - terminal 29 the last phase during a compilation run + inliner 24 optimization: do inlining +inlinehandlers 25 optimization: inline exception handlers + closelim 26 optimization: eliminate uncalled closures + constopt 27 optimization: optimize null and other constants + dce 28 optimization: eliminate dead code + jvm 29 generate JVM bytecode + terminal 30 the last phase during a compilation run #partest diff --git a/test/files/neg/t6446-show-phases.check b/test/files/neg/t6446-show-phases.check index 48d4f37b3e..3ae3f96ef2 100644 --- a/test/files/neg/t6446-show-phases.check +++ b/test/files/neg/t6446-show-phases.check @@ -21,16 +21,17 @@ superaccessors 6 add super accessors in traits and nested classes flatten 19 eliminate inner classes mixin 20 mixin composition cleanup 21 platform-specific cleanups, generate reflective calls - icode 22 generate portable intermediate code + delambdafy 22 remove lambdas + icode 23 generate portable intermediate code #partest !-optimise - jvm 23 generate JVM bytecode - terminal 24 the last phase during a compilation run + jvm 24 generate JVM bytecode + terminal 25 the last phase during a compilation run #partest -optimise - inliner 23 optimization: do inlining -inlinehandlers 24 optimization: inline exception handlers - closelim 25 optimization: eliminate uncalled closures - constopt 26 optimization: optimize null and other constants - dce 27 optimization: eliminate dead code - jvm 28 generate JVM bytecode - terminal 29 the last phase during a compilation run + inliner 24 optimization: do inlining +inlinehandlers 25 optimization: inline exception handlers + closelim 26 optimization: eliminate uncalled closures + constopt 27 optimization: optimize null and other constants + dce 28 optimization: eliminate dead code + jvm 29 generate JVM bytecode + terminal 30 the last phase during a compilation run #partest diff --git a/test/files/neg/t6666.flags b/test/files/neg/t6666.flags new file mode 100644 index 0000000000..2349d8294d --- /dev/null +++ b/test/files/neg/t6666.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline diff --git a/test/files/neg/t6666c.flags b/test/files/neg/t6666c.flags new file mode 100644 index 0000000000..2349d8294d --- /dev/null +++ b/test/files/neg/t6666c.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline diff --git a/test/files/neg/t7494-no-options.check b/test/files/neg/t7494-no-options.check index b5dc0e3d4f..e3316f590a 100644 --- a/test/files/neg/t7494-no-options.check +++ b/test/files/neg/t7494-no-options.check @@ -22,18 +22,19 @@ superaccessors 6 add super accessors in traits and nested classes flatten 19 eliminate inner classes mixin 20 mixin composition cleanup 21 platform-specific cleanups, generate reflective calls - icode 22 generate portable intermediate code + delambdafy 22 remove lambdas + icode 23 generate portable intermediate code #partest !-optimise - jvm 23 generate JVM bytecode - ploogin 24 A sample phase that does so many things it's kind of hard... - terminal 25 the last phase during a compilation run + jvm 24 generate JVM bytecode + ploogin 25 A sample phase that does so many things it's kind of hard... + terminal 26 the last phase during a compilation run #partest -optimise - inliner 23 optimization: do inlining -inlinehandlers 24 optimization: inline exception handlers - closelim 25 optimization: eliminate uncalled closures - constopt 26 optimization: optimize null and other constants - dce 27 optimization: eliminate dead code - jvm 28 generate JVM bytecode - ploogin 29 A sample phase that does so many things it's kind of hard... - terminal 30 the last phase during a compilation run + inliner 24 optimization: do inlining +inlinehandlers 25 optimization: inline exception handlers + closelim 26 optimization: eliminate uncalled closures + constopt 27 optimization: optimize null and other constants + dce 28 optimization: eliminate dead code + jvm 29 generate JVM bytecode + ploogin 30 A sample phase that does so many things it's kind of hard... + terminal 31 the last phase during a compilation run #partest diff --git a/test/files/pos/delambdafy-lambdalift.scala b/test/files/pos/delambdafy-lambdalift.scala new file mode 100644 index 0000000000..e9da24ef37 --- /dev/null +++ b/test/files/pos/delambdafy-lambdalift.scala @@ -0,0 +1,8 @@ +class LambdaLift { + + def enclosingMethod(capturedArg: Int): Unit = { + def innerMethod(x: Int): Int = x + capturedArg + val f = (y: Int) => innerMethod(y) + } + +} diff --git a/test/files/pos/delambdafy-patterns.scala b/test/files/pos/delambdafy-patterns.scala new file mode 100644 index 0000000000..95d498629b --- /dev/null +++ b/test/files/pos/delambdafy-patterns.scala @@ -0,0 +1,15 @@ +class DelambdafyPatterns { + def bar: Unit = () + def wildcardPatternInTryCatch: Unit => Unit = (x: Unit) => + // patterns in try..catch are preserved so we need to be + // careful when it comes to free variable detction + // in particular a is _not_ free variable, also the + // `_` identifier has no symbol attached to it + try bar catch { + case a@(_:java.lang.reflect.InvocationTargetException) => + // refer to a so we trigger a bug where a is considered + // to be a free variable for enclosing lambda + val b = a + () + } +} diff --git a/test/files/run/delambdafy-nested-by-name.check b/test/files/run/delambdafy-nested-by-name.check new file mode 100644 index 0000000000..94954abda4 --- /dev/null +++ b/test/files/run/delambdafy-nested-by-name.check @@ -0,0 +1,2 @@ +hello +world diff --git a/test/files/run/delambdafy-nested-by-name.scala b/test/files/run/delambdafy-nested-by-name.scala new file mode 100644 index 0000000000..4498b3308d --- /dev/null +++ b/test/files/run/delambdafy-nested-by-name.scala @@ -0,0 +1,11 @@ +// during development of delayed delambdafication I created a bug where calling a by-name method with a by-name argument that +// itself contained a by-name argument would cause a class cast exception. That bug wasn't found in the existing test suite +// so this test covers that case +object Test { + def meth1(arg1: => String) = arg1 + def meth2(arg2: => String) = meth1({println("hello"); arg2}) + + def main(args: Array[String]) { + println(meth2("world")) + } +}
\ No newline at end of file diff --git a/test/files/run/delambdafy-two-lambdas.check b/test/files/run/delambdafy-two-lambdas.check new file mode 100644 index 0000000000..ed9ea404dd --- /dev/null +++ b/test/files/run/delambdafy-two-lambdas.check @@ -0,0 +1,2 @@ +13 +24 diff --git a/test/files/run/delambdafy-two-lambdas.scala b/test/files/run/delambdafy-two-lambdas.scala new file mode 100644 index 0000000000..decede74a4 --- /dev/null +++ b/test/files/run/delambdafy-two-lambdas.scala @@ -0,0 +1,12 @@ +/* + * Tests if two lambdas defined in the same class do not lead to + * name clashes. + */ +object Test { + def takeLambda(f: Int => Int ): Int = f(12) + + def main(args: Array[String]): Unit = { + println(takeLambda(x => x+1)) + println(takeLambda(x => x*2)) + } +} diff --git a/test/files/run/delambdafy_t6028.check b/test/files/run/delambdafy_t6028.check new file mode 100644 index 0000000000..92cfbaefb6 --- /dev/null +++ b/test/files/run/delambdafy_t6028.check @@ -0,0 +1,57 @@ +[[syntax trees at end of lambdalift]] // newSource1.scala +package <empty> { + class T extends Object { + <paramaccessor> private[this] val classParam: Int = _; + def <init>(classParam: Int): T = { + T.super.<init>(); + () + }; + private[this] val field: Int = 0; + <stable> <accessor> def field(): Int = T.this.field; + def foo(methodParam: Int): Function0 = { + val methodLocal: Int = 0; + { + (() => T.this.$anonfun$1(methodParam, methodLocal)).$asInstanceOf[Function0]() + } + }; + def bar(barParam: Int): Object = { + @volatile var MethodLocalObject$module: runtime.VolatileObjectRef = scala.runtime.VolatileObjectRef.zero(); + T.this.MethodLocalObject$1(barParam, MethodLocalObject$module) + }; + def tryy(tryyParam: Int): Function0 = { + var tryyLocal: runtime.IntRef = scala.runtime.IntRef.create(0); + { + (() => T.this.$anonfun$2(tryyParam, tryyLocal)).$asInstanceOf[Function0]() + } + }; + final <artifact> private[this] def $anonfun$1(methodParam$1: Int, methodLocal$1: Int): Int = T.this.classParam.+(T.this.field()).+(methodParam$1).+(methodLocal$1); + abstract trait MethodLocalTrait$1 extends Object { + <synthetic> <stable> <artifact> def $outer(): T + }; + object MethodLocalObject$2 extends Object with T#MethodLocalTrait$1 { + def <init>($outer: T, barParam$1: Int): T#MethodLocalObject$2.type = { + MethodLocalObject$2.super.<init>(); + MethodLocalObject$2.this.$asInstanceOf[T#MethodLocalTrait$1$class]()./*MethodLocalTrait$1$class*/$init$(barParam$1); + () + }; + <synthetic> <paramaccessor> <artifact> private[this] val $outer: T = _; + <synthetic> <stable> <artifact> def $outer(): T = MethodLocalObject$2.this.$outer; + <synthetic> <stable> <artifact> def $outer(): T = MethodLocalObject$2.this.$outer + }; + final <stable> private[this] def MethodLocalObject$1(barParam$1: Int, MethodLocalObject$module$1: runtime.VolatileObjectRef): T#MethodLocalObject$2.type = { + MethodLocalObject$module$1.elem = new T#MethodLocalObject$2.type(T.this, barParam$1); + MethodLocalObject$module$1.elem.$asInstanceOf[T#MethodLocalObject$2.type]() + }; + abstract trait MethodLocalTrait$1$class extends Object with T#MethodLocalTrait$1 { + def /*MethodLocalTrait$1$class*/$init$(barParam$1: Int): Unit = { + () + }; + scala.this.Predef.print(scala.Int.box(barParam$1)) + }; + final <artifact> private[this] def $anonfun$2(tryyParam$1: Int, tryyLocal$1: runtime.IntRef): Unit = try { + tryyLocal$1.elem = tryyParam$1 + } finally () + } +} + +warning: there were 1 feature warning(s); re-run with -feature for details diff --git a/test/files/run/delambdafy_t6028.scala b/test/files/run/delambdafy_t6028.scala new file mode 100644 index 0000000000..0b7ef48c3d --- /dev/null +++ b/test/files/run/delambdafy_t6028.scala @@ -0,0 +1,21 @@ +import scala.tools.partest._ +import java.io.{Console => _, _} + +object Test extends DirectTest { + + override def extraSettings: String = "-usejavacp -Ydelambdafy:method -Xprint:lambdalift -d " + testOutput.path + + override def code = """class T(classParam: Int) { + | val field: Int = 0 + | def foo(methodParam: Int) = {val methodLocal = 0 ; () => classParam + field + methodParam + methodLocal } + | def bar(barParam: Int) = { trait MethodLocalTrait { print(barParam) }; object MethodLocalObject extends MethodLocalTrait; MethodLocalObject } + | def tryy(tryyParam: Int) = { var tryyLocal = 0; () => try { tryyLocal = tryyParam } finally () } + |} + |""".stripMargin.trim + + override def show(): Unit = { + Console.withErr(System.out) { + compile() + } + } +} diff --git a/test/files/run/delambdafy_t6555.check b/test/files/run/delambdafy_t6555.check new file mode 100644 index 0000000000..6b174c0d2a --- /dev/null +++ b/test/files/run/delambdafy_t6555.check @@ -0,0 +1,15 @@ +[[syntax trees at end of specialize]] // newSource1.scala +package <empty> { + class Foo extends Object { + def <init>(): Foo = { + Foo.super.<init>(); + () + }; + private[this] val f: Int => Int = { + final <artifact> def $anonfun(param: Int): Int = param; + ((param: Int) => $anonfun(param)) + }; + <stable> <accessor> def f(): Int => Int = Foo.this.f + } +} + diff --git a/test/files/run/delambdafy_t6555.scala b/test/files/run/delambdafy_t6555.scala new file mode 100644 index 0000000000..a1dcfe790c --- /dev/null +++ b/test/files/run/delambdafy_t6555.scala @@ -0,0 +1,15 @@ +import scala.tools.partest._ +import java.io.{Console => _, _} + +object Test extends DirectTest { + + override def extraSettings: String = "-usejavacp -Xprint:specialize -Ydelambdafy:method -d " + testOutput.path + + override def code = "class Foo { val f = (param: Int) => param } " + + override def show(): Unit = { + Console.withErr(System.out) { + compile() + } + } +} 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() + } + } +} diff --git a/test/files/run/origins.flags b/test/files/run/origins.flags index a7e64e4f0c..690753d807 100644 --- a/test/files/run/origins.flags +++ b/test/files/run/origins.flags @@ -1 +1 @@ --no-specialization
\ No newline at end of file +-no-specialization -Ydelambdafy:inline
\ No newline at end of file diff --git a/test/files/run/primitive-sigs-2-new.flags b/test/files/run/primitive-sigs-2-new.flags new file mode 100644 index 0000000000..2349d8294d --- /dev/null +++ b/test/files/run/primitive-sigs-2-new.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline diff --git a/test/files/run/primitive-sigs-2-old.flags b/test/files/run/primitive-sigs-2-old.flags new file mode 100644 index 0000000000..ac96850b69 --- /dev/null +++ b/test/files/run/primitive-sigs-2-old.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline
\ No newline at end of file diff --git a/test/files/run/programmatic-main.check b/test/files/run/programmatic-main.check index cfa3ed3fb4..1cd94ccb45 100644 --- a/test/files/run/programmatic-main.check +++ b/test/files/run/programmatic-main.check @@ -21,6 +21,7 @@ superaccessors 6 add super accessors in traits and nested classes flatten 19 eliminate inner classes mixin 20 mixin composition cleanup 21 platform-specific cleanups, generate reflective calls - icode 22 generate portable intermediate code - jvm 23 generate JVM bytecode - terminal 24 the last phase during a compilation run + delambdafy 22 remove lambdas + icode 23 generate portable intermediate code + jvm 24 generate JVM bytecode + terminal 25 the last phase during a compilation run diff --git a/test/files/run/static-module-method.check b/test/files/run/static-module-method.check new file mode 100644 index 0000000000..ce01362503 --- /dev/null +++ b/test/files/run/static-module-method.check @@ -0,0 +1 @@ +hello diff --git a/test/files/run/static-module-method.scala b/test/files/run/static-module-method.scala new file mode 100644 index 0000000000..a8691300de --- /dev/null +++ b/test/files/run/static-module-method.scala @@ -0,0 +1,14 @@ +// During development of delayed delambdafy there was a problem where +// GenASM would eliminate a loadmodule for all methods defined within that module +// even if those methods were static. This test would thus fail +// with a verify error under -Ydelambdafy:method + +object Test { + def moduleMethod(x: String) = x + + def map(x: String, f: String => String) = f(x) + + def main(args: Array[String]) { + println(map("hello", Test.moduleMethod)) + } +}
\ No newline at end of file diff --git a/test/files/run/t1167.flags b/test/files/run/t1167.flags new file mode 100644 index 0000000000..ac96850b69 --- /dev/null +++ b/test/files/run/t1167.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline
\ No newline at end of file diff --git a/test/files/run/t3897.flags b/test/files/run/t3897.flags new file mode 100644 index 0000000000..ac96850b69 --- /dev/null +++ b/test/files/run/t3897.flags @@ -0,0 +1 @@ +-Ydelambdafy:inline
\ No newline at end of file diff --git a/test/files/run/t6028.scala b/test/files/run/t6028.scala index cab17535fc..a6f920c5bb 100644 --- a/test/files/run/t6028.scala +++ b/test/files/run/t6028.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:lambdalift -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Ydelambdafy:inline -Xprint:lambdalift -d " + testOutput.path override def code = """class T(classParam: Int) { | val field: Int = 0 diff --git a/test/files/run/t6102.check b/test/files/run/t6102.check index 4e8efa7b6d..aa3e6cc9e2 100644 --- a/test/files/run/t6102.check +++ b/test/files/run/t6102.check @@ -19,6 +19,7 @@ [running phase flatten on t6102.scala] [running phase mixin on t6102.scala] [running phase cleanup on t6102.scala] +[running phase delambdafy on t6102.scala] [running phase icode on t6102.scala] #partest -optimise [running phase inliner on t6102.scala] diff --git a/test/files/run/t6555.scala b/test/files/run/t6555.scala index b1a6137786..cc0e4d1bfa 100644 --- a/test/files/run/t6555.scala +++ b/test/files/run/t6555.scala @@ -3,7 +3,7 @@ import java.io.{Console => _, _} object Test extends DirectTest { - override def extraSettings: String = "-usejavacp -Xprint:specialize -d " + testOutput.path + override def extraSettings: String = "-usejavacp -Xprint:specialize -Ydelambdafy:inline -d " + testOutput.path override def code = "class Foo { val f = (param: Int) => param } " diff --git a/test/files/specialized/constant_lambda.check b/test/files/specialized/constant_lambda.check new file mode 100644 index 0000000000..4b095fd0ff --- /dev/null +++ b/test/files/specialized/constant_lambda.check @@ -0,0 +1,2 @@ +false +false diff --git a/test/files/specialized/constant_lambda.scala b/test/files/specialized/constant_lambda.scala new file mode 100644 index 0000000000..bb9a97403e --- /dev/null +++ b/test/files/specialized/constant_lambda.scala @@ -0,0 +1,16 @@ +// during development of late delmabdafying there was a problem where +// specialization would undo some of the work done in uncurry if the body of the +// lambda had a constant type. That would result in a compiler crash as +// when the delambdafy phase got a tree shape it didn't understand +class X[@specialized(Int) A] { + val f = { x: A => false } +} + +object Test { + def main(args: Array[String]) { + val xInt = new X[Int] + println(xInt.f(42)) + val xString = new X[String] + println(xString.f("hello")) + } +}
\ No newline at end of file |