summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/Delambdafy.scala')
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala755
1 files changed, 280 insertions, 475 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 8e323de623..034cf118d7 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -4,35 +4,21 @@ 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 responsible for preparing lambdas for runtime, by either translating to anonymous classes
- * or to a tree that will be convereted 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 -target:jvm-1.7 and below:
- *
- * 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 -target:jvm-1.8 with 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._
@@ -42,6 +28,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, isSerializable: Boolean, addScalaSerializableMarker: Boolean)
+
+ /**
+ * 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)
@@ -54,433 +53,250 @@ 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
-
-
- 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
+ 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
+ def isSelfParam(p: Symbol) = p.isSynthetic && p.name == nme.SELF
+ val hasSelfParam = isSelfParam(target.firstParam)
+
+ 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 (!hasSelfParam) captureArgs.filterNot(arg => isSelfParam(arg.symbol))
+ else if (currentMethod.hasFlag(Flags.STATIC)) captureArgs
+ else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs
}
- methodReferringMap.keys foreach refersToThis
- referrers
- }
-
- // 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)
- }
- // 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)
- }
-
- 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
- private def transformFunction(originalFunction: Function): TransformedFunction = {
- val functionTpe = originalFunction.tpe
- val targs = functionTpe.typeArgs
- val formals :+ restpe = targs
- val oldClass = originalFunction.symbol.enclClass
+ // 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))
+ }
- // 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)
+ // We then apply this symbol to the captures.
+ val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs))
- val target = targetMethod(originalFunction)
- target.makeNotPrivate(target.owner)
- if (!thisReferringMethods.contains(target))
- target setFlag STATIC
-
- val isStatic = target.hasFlag(STATIC)
-
- 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)
- }
- }
- 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]
- }
+ // TODO: this is a bit gross
+ val sam = samUserDefined orElse {
+ if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get
+ else functionalInterface.info.member(nme.apply)
}
- /**
- * 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
+ // 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)
+ val isSerializable = samUserDefined == NoSymbol || samUserDefined.owner.isNonBottomSubClass(definitions.JavaSerializableClass)
+ val addScalaSerializableMarker = samUserDefined == NoSymbol
- newClass.info.decls enter methSym
+ // 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, isSerializable, addScalaSerializableMarker))
- 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.
+ apply
+ }
- 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)
+ private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]()
- // 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
- }
+ private def reboxValueClass(tp: Type) = tp match {
+ case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil)
+ case _ => tp
+ }
- /**
- * 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]
- }
+ // 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)
+ }
- 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))
- }
+ // 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 correspondence 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 dealing 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.
- // 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)
- }
+ val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) =>
+ if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp
+ else samParamTp
}
- val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy)
-
- val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function]
+ val bridgeResultType =
+ if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType
+ else samResultType
+
+ val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) }
+ import typeAdapter.{adaptToType, unboxValueClass}
+
+ val targetParams = target.paramss.head
+ val numCaptures = targetParams.length - functionParamTypes.length
+ val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures)
+
+ val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT | STATIC)
+ 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)
+ }
- val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member =>
- lambdaClass.info.decls enter member
- ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos
+ gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs)
}
- // 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}"
+ val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))),
+ adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef]
- 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)
+ boxingBridgeMethods += bridge
+ bridge.symbol
}
}
- /**
- * 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)
+ private def transformFunction(originalFunction: Function): Tree = {
+ val target = targetMethod(originalFunction)
+ assert(target.hasFlag(Flags.STATIC))
+ target.setFlag(notPRIVATE)
- val methodType = MethodType(bridgeSyms, ObjectTpe)
- bridgeMethSym setInfo methodType
+ 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 isSpecialized = specializedName != funSym.name
+ val functionalInterface =
+ if (isSpecialized) {
+ // Unfortunately we still need to use custom functional interfaces for specialized functions so that the
+ // unboxed apply method is left abstract for us to implement.
+ currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J"))
+ }
+ else FunctionClass(originalFunction.vparams.length)
- def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = {
- if (tree.tpe =:= expectedTpe) (false, tree)
- else (true, adaptToType(tree, expectedTpe))
- }
+ (functionalInterface, isSpecialized)
+ }
- 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)
- }
+ val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol)
+ mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized)
+ }
- 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]
+ // 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(_, _, _) =>
+ def pretransform(tree: Tree): Tree = tree match {
+ case dd: DefDef if dd.symbol.isDelambdafyTarget =>
+ if (!dd.symbol.hasFlag(STATIC) && methodReferencesThis(dd.symbol)) {
+ gen.mkStatic(dd, dd.symbol.name, sym => sym)
+ } else {
+ dd.symbol.setFlag(STATIC)
+ dd
+ }
+ case t => t
}
- 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
- }
+ try {
+ // during this call boxingBridgeMethods will be populated from the Function case
+ val Template(parents, self, body) = super.transform(deriveTemplate(tree)(_.mapConserve(pretransform)))
+ Template(parents, self, body ++ boxingBridgeMethods)
+ } finally boxingBridgeMethods.clear()
+ case dd: DefDef if dd.symbol.isLiftedMethod && !dd.symbol.isDelambdafyTarget =>
+ // SI-9390 emit lifted methods that don't require a `this` reference as STATIC
+ // delambdafy targets are excluded as they are made static by `transformFunction`.
+ if (!dd.symbol.hasFlag(STATIC) && !methodReferencesThis(dd.symbol)) {
+ dd.symbol.setFlag(STATIC)
+ dd.symbol.removeAttachment[mixer.NeedStaticImpl.type]
+ }
+ super.transform(tree)
+ case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) =>
+ val nullOuter = gen.mkZero(outer.tpe)
+ treeCopy.Apply(tree, transform(fun), nullOuter :: transformTrees(rest))
+ case _ => super.transform(tree)
}
} // DelambdafyTransformer
+ private def shouldElideOuterArg(fun: Symbol, outerArg: Tree): Boolean =
+ fun.isConstructor && treeInfo.isQualifierSafeToElide(outerArg) && fun.hasAttachment[OuterArgCanBeElided.type]
+
// 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
@@ -513,40 +329,45 @@ 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 = {
+ val seen = mutable.Set[Symbol]()
+ def loop(symbol: Symbol): Boolean = {
+ if (seen(symbol)) false
+ else {
+ seen += symbol
+ (thisReferringMethods contains symbol) ||
+ (liftedMethodReferences(symbol) exists loop) && {
+ // add it early to memoize
+ debuglog(s"$symbol indirectly refers to 'this'")
+ thisReferringMethods += symbol
+ true
+ }
+ }
+ }
+ loop(symbol)
+ }
- // 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 || tree.symbol.isLiftedMethod =>
// 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
@@ -557,37 +378,21 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
// 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 Apply(sel @ Select(This(_), _), args) if sel.symbol.isLiftedMethod =>
+ if (currentMethod.exists) liftedMethodReferences(currentMethod) += sel.symbol
+ super.traverseTrees(args)
+ case Apply(fun, outer :: rest) if shouldElideOuterArg(fun.symbol, outer) =>
+ super.traverse(fun)
+ super.traverseTrees(rest)
case This(_) =>
if (currentMethod.exists && tree.symbol == currentMethod.enclClass) {
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 canUseLambdaMetafactory: Boolean = {
- val isTarget18 = settings.target.value.contains("jvm-1.8")
- settings.isBCodeActive && isTarget18
- }
-
- 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"))
- }
- (if (canUseLambdaMetafactory) functionalInterface else NoSymbol, isSpecialized)
- }
}