diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/Delambdafy.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 690 |
1 files changed, 228 insertions, 462 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 67e3f67f2f..76c84bd428 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -7,30 +7,19 @@ import scala.collection._ import scala.collection.mutable.LinkedHashMap /** - * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes - * or to a tree that will be converted to invokedynamic by the JVM 1.8+ backend. - * - * 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: - * - * Under GenASM - * - * 1) a new top level class that - a) has fields and a constructor taking the captured environment (including possibly the "this" - * reference) - * b) an apply method that calls the target method - * c) if needed a bridge method for the apply method - * 2) an instantiation of the newly created class which replaces the lambda - * - * Under GenBCode: - * - * 1) An application of the captured arguments to a fictional symbol representing the lambda factory. - * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. - * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. - */ + * This transformer is responsible for preparing Function nodes for runtime, + * by translating to a tree that will be converted to an invokedynamic by the backend. + * + * The main assumption it makes is that a Function {args => body} has been turned into + * {args => liftedBody()} where lifted body is a top level method that implements the body of the function. + * Currently Uncurry is responsible for that transformation. + * + * From this shape of Function, Delambdafy will create: + * + * An application of the captured arguments to a fictional symbol representing the lambda factory. + * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. + * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. + */ abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer { import global._ import definitions._ @@ -40,6 +29,19 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre /** the following two members override abstract members in Transform */ val phaseName: String = "delambdafy" + final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol) + + /** + * Get the symbol of the target lifted lambda 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") + } + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { if (settings.Ydelambdafy.value == "method") new Phase(prev) else new SkipPhase(prev) @@ -52,432 +54,217 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre 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 + class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it + // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes? + private[this] lazy val methodReferencesThis: Set[Symbol] = + (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body) + + private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = { + val pos = fun.pos + val allCapturedArgRefs = { + // 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 captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture => + gen.mkAttributedRef(capture) setPos pos + ).toList + + if (target hasFlag STATIC) captureArgs // no `this` reference needed + else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs + } + // Create a symbol representing a fictional lambda factory method that accepts the captured + // arguments and returns the SAM type. + val msym = { + val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, pos, ARTIFACT) + val capturedParams = meth.newSyntheticValueParams(allCapturedArgRefs.map(_.tpe)) + meth.setInfo(MethodType(capturedParams, fun.tpe)) + } - val typer = localTyper + // We then apply this symbol to the captures. + val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs)) - // 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 + // TODO: this is a bit gross + val sam = samUserDefined orElse { + if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get + else functionalInterface.info.member(nme.apply) } - methodReferringMap.keys foreach refersToThis - referrers + + // no need for adaptation when the implemented sam is of a specialized built-in function type + val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface, sam) + + // The backend needs to know the target of the lambda and the functional interface in order + // to emit the invokedynamic instruction. We pass this information as tree attachment. + // + // see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + // instantiatedMethodType is derived from lambdaTarget's signature + // samMethodType is derived from samOf(functionalInterface)'s signature + apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface, sam)) + + apply } - // the result of the transformFunction method. - sealed abstract class TransformedFunction - // A class definition for the lambda, an expression instantiating the lambda class - case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction - case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction private val boxingBridgeMethods = mutable.ArrayBuffer[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(_, _) => - transformFunction(fun) match { - case DelambdafyAnonClass(lambdaClassDef, newExpr) => - // a lambda becomes a new class, an instantiation expression - val pkg = lambdaClassDef.symbol.owner - - // we'll add the lambda class to the package later - lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg) - - super.transform(newExpr) - case InvokeDynamicLambda(apply) => - // ... or an invokedynamic call - super.transform(apply) - } - case Template(_, _, _) => - try { - // during this call boxingBridgeMethods will be populated from the Function case - val Template(parents, self, body) = super.transform(tree) - Template(parents, self, body ++ boxingBridgeMethods) - } finally boxingBridgeMethods.clear() - case _ => super.transform(tree) + private def reboxValueClass(tp: Type) = tp match { + case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) + case _ => tp } - // 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] = { - // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same - // package when defining a package object. We only add the lambda class to one. See SI-9097. - super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil) + // exclude primitives and value classes, which need special boxing + private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && { + val sym = tp.typeSymbol + !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass) } - private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None + // determine which lambda target to use with java's LMF -- create a new one if scala-specific boxing is required + def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol, sam: Symbol): Symbol = { + val oldClass = fun.symbol.enclClass + val pos = fun.pos + + // At erasure, there won't be any captured arguments (they are added in constructors) + val functionParamTypes = exitingErasure(target.info.paramTypes) + val functionResultType = exitingErasure(target.info.resultType) + + val samParamTypes = exitingErasure(sam.info.paramTypes) + val samResultType = exitingErasure(sam.info.resultType) + + /** How to satisfy the linking invariants of https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + * + * Given samMethodType: (U1..Un)Ru and function type T1,..., Tn => Rt (the target method created by uncurry) + * + * Do we need a bridge, or can we use the original lambda target for implMethod: (<captured args> A1..An)Ra + * (We can ignore capture here.) + * + * If, for i=1..N: + * Ai =:= Ui || (Ai <:< Ui <:< AnyRef) + * Ru =:= void || (Ra =:= Ru || (Ra <:< AnyRef, Ru <:< AnyRef)) + * + * We can use the target method as-is -- if not, we create a bridging one that uses the types closest + * to the target method that still meet the above requirements. + */ + val resTpOk = ( + samResultType =:= UnitTpe + || functionResultType =:= samResultType + || (isReferenceType(samResultType) && isReferenceType(functionResultType))) // yes, this is what the spec says -- no further correspondance required + if (resTpOk && (samParamTypes corresponds functionParamTypes){ (samParamTp, funParamTp) => + funParamTp =:= samParamTp || (isReferenceType(funParamTp) && isReferenceType(samParamTp) && funParamTp <:< samParamTp) }) target + else { + // We have to construct a new lambda target that bridges to the one created by uncurry. + // The bridge must satisfy the above invariants, while also minimizing adaptation on our end. + // LMF will insert runtime casts according to the spec at the above link. + + // we use the more precise type between samParamTp and funParamTp to minimize boxing in the bridge method + // we are constructing a method whose signature matches the sam's signature (because the original target did not) + // whenever a type in the sam's signature is (erases to) a primitive type, we must pick the sam's version, + // as we don't implement the logic regarding widening that's performed by LMF -- we require =:= for primitives + // + // We use the sam's type for the check whether we're dealin with a reference type, as it could be a generic type, + // which means the function's parameter -- even if it expects a value class -- will need to be + // boxed on the generic call to the sam method. - // turns a lambda into a new class def, a New expression instantiating that class - private def transformFunction(originalFunction: Function): TransformedFunction = { - val formals = originalFunction.vparams.map(_.tpe) - val restpe = originalFunction.body.tpe.deconst - val oldClass = originalFunction.symbol.enclClass + val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) => + if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp + else samParamTp + } - // 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) + val bridgeResultType = + if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType + else samResultType - val target = targetMethod(originalFunction) - target.makeNotPrivate(target.owner) - if (!thisReferringMethods.contains(target)) - target setFlag STATIC + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) } + import typeAdapter.{adaptToType, unboxValueClass} - val isStatic = target.hasFlag(STATIC) + val targetParams = target.paramss.head + val numCaptures = targetParams.length - functionParamTypes.length + val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - def createBoxingBridgeMethod(functionParamTypes: List[Type], functionResultType: Type): Tree = { - // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required. - // If we need to improve performance, we could check the types first before creating the - // method and parameter symbols. val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) - var neededAdaptation = false - def boxedType(tpe: Type): Type = { - if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe} - else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe} - else tpe - } - val targetParams: List[Symbol] = target.paramss.head - val numCaptures = targetParams.length - functionParamTypes.length - val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - val bridgeParams: List[Symbol] = - targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) ::: - map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName)) - - val bridgeResultType: Type = { - if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) { - neededAdaptation = true - ObjectTpe - } else - boxedType(functionResultType) - } - val methodType = MethodType(bridgeParams, bridgeResultType) - methSym setInfo methodType - if (!neededAdaptation) - EmptyTree - else { - val bridgeParamTrees = bridgeParams.map(ValDef(_)) - - oldClass.info.decls enter methSym - - val body = localTyper.typedPos(originalFunction.pos) { - val newTarget = Select(gen.mkAttributedThis(oldClass), target) - val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) => - if (i < numCaptures) { - gen.mkAttributedRef(param) - } else { - val functionParam = functionParamTypes(i - numCaptures) - val targetParam = targetParams(i) - if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) { - val casted = cast(gen.mkAttributedRef(param), functionParam) - val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType) - unboxed - } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe) - } + val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) + val bridgeFunctionParams = + map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName)) + + val bridgeParams = bridgeCapturedParams ::: bridgeFunctionParams + + methSym setInfo MethodType(bridgeParams, bridgeResultType) + oldClass.info.decls enter methSym + + val forwarderCall = localTyper.typedPos(pos) { + val capturedArgRefs = bridgeCapturedParams map gen.mkAttributedRef + val functionArgRefs = + map3(bridgeFunctionParams, functionParamTypes, targetParams.drop(numCaptures)) { (bridgeParam, functionParamTp, targetParam) => + val bridgeParamRef = gen.mkAttributedRef(bridgeParam) + val targetParamTp = targetParam.tpe + + // TODO: can we simplify this to something like `adaptToType(adaptToType(bridgeParamRef, functionParamTp), targetParamTp)`? + val unboxed = + functionParamTp match { + case ErasedValueType(clazz, underlying) => + // when the original function expected an argument of value class type, + // the original target will expect the unboxed underlying value, + // whereas the bridge will receive the boxed value (since the sam's argument type did not match and we had to adapt) + localTyper.typed(unboxValueClass(bridgeParamRef, clazz, underlying), targetParamTp) + case _ => bridgeParamRef + } + + adaptToType(unboxed, targetParamTp) } - gen.mkMethodCall(newTarget, args) - } - val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) - adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType) - else adaptToType(body, bridgeResultType) - val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) - postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] - } - } - /** - * Creates the apply method for the anonymous subclass of FunctionN - */ - def createApplyMethod(newClass: Symbol, fun: Function, 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 qual = if (thisProxy.exists) - Select(gen.mkAttributedThis(newClass), thisProxy) - else - gen.mkAttributedThis(oldClass) // sort of a lie, EmptyTree.<static method> would be more honest, but the backend chokes on that. - - val body = localTyper typed Apply(Select(qual, target), oldParams) - body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol)) - body changeOwner (fun.symbol -> methSym) - - val methDef = DefDef(methSym, List(params), body) + gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs) + } - // 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 - } + val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))), + adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef] - /** - * 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( - atPos(newClass.pos)(Apply(gen.mkSuperInitCall, Nil)) - ) ++ assigns, - Literal(Constant(())): Tree - ) setPos newClass.pos - - (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef] + boxingBridgeMethods += bridge + bridge.symbol } + } - 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: ClassDef = { - val parents = addSerializable(abstractFunctionErasedType) - val funOwner = originalFunction.symbol.owner - - // TODO harmonize the naming of delambdafy anon-fun classes with those spun up by Uncurry - // - make `anonClass.isAnonymousClass` true. - // - use `newAnonymousClassSymbol` or push the required variations into a similar factory method - // - reinstate the assertion in `Erasure.resolveAnonymousBridgeClash` - val suffix = nme.DELAMBDAFY_LAMBDA_CLASS_NAME + "$" + ( - if (funOwner.isPrimaryConstructor) "" - else "$" + funOwner.name + "$" - ) - val oldClassPart = oldClass.name.decode - // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true - val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon")) - - val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation - lambdaClass.associatedFile = unit.source.file - // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes) - currentRun.symSource(lambdaClass) = funOwner.sourceFile - lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass) - assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name) - assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name) - - val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol] - captures foreach {capture => - val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC) - sym setInfo capture.info - captureProxies2 += ((capture, sym)) - } + private def transformFunction(originalFunction: Function): Tree = { + val target = targetMethod(originalFunction) + target.makeNotPrivate(target.owner) - // 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 = { - if (isStatic) - NoSymbol - else { - val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) - sym.setInfo(oldClass.tpe) - } - } + // must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall + if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC - val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy) + val funSym = originalFunction.tpe.typeSymbolDirect + // The functional interface that can be used to adapt the lambda target method `target` to the given function type. + val (functionalInterface, isSpecialized) = + if (!isFunctionSymbol(funSym)) (funSym, false) + else { + val specializedName = + specializeTypes.specializedFunctionName(funSym, + exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName - val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] + val isSpecialized = specializedName != funSym.name + val functionalInterface = // TODO: this is no longer needed, right? we can just use the regular function classes + if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J")) + else FunctionClass(originalFunction.vparams.length) - val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => - lambdaClass.info.decls enter member - ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos + (functionalInterface, isSpecialized) } - // constructor - val constr = createConstructor(lambdaClass, members) - - // apply method with same arguments and return type as original lambda. - val applyMethodDef = createApplyMethod(lambdaClass, decapturedFunction, thisProxy) - - val bridgeMethod = createBridgeMethod(lambdaClass, originalFunction, applyMethodDef) - - def fulldef(sym: Symbol) = - if (sym == NoSymbol) sym.toString - else s"$sym: ${sym.tpe} in ${sym.owner}" - - bridgeMethod foreach (bm => - // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases - // rather than the method+bridge pair. - if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe) - erasure.resolveAnonymousBridgeClash(applyMethodDef.symbol, 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(lambdaClass, body)).asInstanceOf[ClassDef] - } - - val allCaptureArgs: List[Tree] = { - val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil - val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList - thisArg ::: captureArgs - } - - val arity = originalFunction.vparams.length - - // Reconstruct the type of the function entering erasure. - // We do this by taking the type after erasure, and re-boxing `ErasedValueType`. - // - // Unfortunately, the more obvious `enteringErasure(target.info)` doesn't work - // as we would like, value classes in parameter position show up as the unboxed types. - val (functionParamTypes, functionResultType) = exitingErasure { - def boxed(tp: Type) = tp match { - case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) - case _ => tp - } - // We don't need to deeply map `boxedValueClassType` over the infos as `ErasedValueType` - // will only appear directly as a parameter type in a method signature, as shown - // https://gist.github.com/retronym/ba81dbd462282c504ff8 - val info = target.info - val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed) - (boxedParamTypes, boxed(info.resultType)) - } - val functionType = definitions.functionType(functionParamTypes, functionResultType) - - val (functionalInterface, isSpecialized) = java8CompatFunctionalInterface(target, functionType) - if (functionalInterface.exists) { - // Create a symbol representing a fictional lambda factory method that accepts the captured - // arguments and returns a Function. - val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) - val argTypes: List[Type] = allCaptureArgs.map(_.tpe) - val params = msym.newSyntheticValueParams(argTypes) - msym.setInfo(MethodType(params, functionType)) - val arity = originalFunction.vparams.length - - val lambdaTarget = - if (isSpecialized) - target - else { - createBoxingBridgeMethod(functionParamTypes, functionResultType) match { - case EmptyTree => - target - case bridge => - boxingBridgeMethods += bridge - bridge.symbol - } - } - - // We then apply this symbol to the captures. - val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] - - // The backend needs to know the target of the lambda and the functional interface in order - // to emit the invokedynamic instruction. We pass this information as tree attachment. - apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface)) - InvokeDynamicLambda(apply) - } else { - val anonymousClassDef = makeAnonymousClass - pkg.info.decls enter anonymousClassDef.symbol - val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType)) - val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) - DelambdafyAnonClass(anonymousClassDef, typedNewStat) - } + val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol) + mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized) } - /** - * 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)) - } - - def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = { - val (needsAdapt, adaptedTree) = adapt(tree, pt) - val trans = postErasure.newTransformer(unit) - val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes - (needsAdapt, postErasedTree) - } - - enteringPhase(currentRun.posterasurePhase) { - // e.g, in: - // class C(val a: Int) extends AnyVal; (x: Int) => new C(x) - // - // This type is: - // (x: Int)ErasedValueType(class C, Int) - val liftedBodyDefTpe: MethodType = { - val liftedBodySymbol = { - val Apply(method, _) = originalFunction.body - 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 - // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`. - // But that symbol doesn't have a type history that goes back before `delambdafy`, - // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`. - // This triggered primitive boxing, rather than value class boxing. - val resTp = liftedBodyDefTpe.finalResultType - val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp - val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe) - - val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation - if (needsBridge) { - val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody) - newClass.info.decls enter bridgeMethSym - Some((localTyper typed methDef).asInstanceOf[DefDef]) - } else None - } + // 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 => super.transform(transformFunction(fun)) + case Template(_, _, _) => + try { + // during this call boxingBridgeMethods will be populated from the Function case + val Template(parents, self, body) = super.transform(tree) + Template(parents, self, body ++ boxingBridgeMethods) + } finally boxingBridgeMethods.clear() + case _ => super.transform(tree) } } // 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 @@ -510,40 +297,36 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } } - // 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) + // finds all methods that reference 'this' + class ThisReferringMethodsTraverser extends Traverser { + // the set of methods that refer to this + private val thisReferringMethods = mutable.Set[Symbol]() + + // the set of lifted lambda body methods that each method refers to + private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + + def methodReferencesThisIn(tree: Tree) = { + traverse(tree) + liftedMethodReferences.keys foreach refersToThis + + thisReferringMethods } - } - /** - * Get the symbol of the target lifted lambda 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") - } + // 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 + private def refersToThis(symbol: Symbol): Boolean = + (thisReferringMethods contains symbol) || + (liftedMethodReferences(symbol) exists refersToThis) && { + // add it early to memoize + debuglog(s"$symbol indirectly refers to 'this'") + thisReferringMethods += symbol + true + } - // 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(_, _, _, _, _, _) => + case DefDef(_, _, _, _, _, _) if tree.symbol.isDelambdafyTarget => // 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 @@ -559,27 +342,10 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre debuglog(s"$currentMethod directly refers to 'this'") thisReferringMethods add currentMethod } + case _: ClassDef if !tree.symbol.isTopLevel => + case _: DefDef => case _ => super.traverse(tree) } } - - final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) - - // The functional interface that can be used to adapt the lambda target method `target` to the - // given function type. Returns `NoSymbol` if the compiler settings are unsuitable. - private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = { - val sym = functionType.typeSymbol - val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage - val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs) - val paramTps :+ restpe = functionType.typeArgs - val arity = paramTps.length - val isSpecialized = name1.toTypeName != sym.name - val functionalInterface = if (!isSpecialized) { - currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity) - } else { - pack.info.decl(name1.toTypeName.prepend("J")) - } - (functionalInterface, isSpecialized) - } } |