summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/ast/TreeGen.scala76
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala377
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala3
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala159
-rw-r--r--src/reflect/scala/reflect/internal/StdNames.scala1
-rw-r--r--test/files/neg/sammy_restrictions.check5
-rw-r--r--test/files/neg/sammy_restrictions.scala2
-rw-r--r--test/files/pos/sammy_implicit.scala2
-rw-r--r--test/files/pos/sammy_poly.scala3
-rw-r--r--test/files/run/indylambda-boxing/test.scala7
-rw-r--r--test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala29
12 files changed, 331 insertions, 335 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
index b2a54e16d3..27e366e725 100644
--- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala
+++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala
@@ -261,43 +261,59 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL {
mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition)
}
- /**
- * Create a method based on a Function
- *
- * Used both to under `-Ydelambdafy:method` create a lifted function and
- * under `-Ydelambdafy:inline` to create 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 DefDef
- *
- * @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
- */
- def mkMethodFromFunction(localTyper: analyzer.Typer)
- (owner: Symbol, fun: Function, name: TermName = nme.apply, additionalFlags: FlagSet = NoFlags) = {
- val funParamSyms = fun.vparams.map(_.symbol)
- val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
- val methParamSyms = funParamSyms.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) }
+ // Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type)
+ def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = {
+ // TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)`
+ val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol
+ if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)()
+ else {
+ val samMethType = fun.tpe memberInfo sam
+ mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType)
+ }
+ }
+
+ // used to create the lifted method that holds a function's body
+ def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) =
+ mkMethodForFunctionBody(localTyper)(owner, fun, nme.ANON_FUN_NAME)(additionalFlags = ARTIFACT)
+
- val body = fun.body
- body substituteSymbols (funParamSyms, methParamSyms)
- body changeOwner ((fun.symbol, methSym))
+ /**
+ * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R]
+ *
+ * It creates a method definition with value params derived from the original lambda
+ * or `methParamProtos` (used to create the correct override for sam methods).
+ *
+ * Replace the `fun.vparams` symbols by the newly created method params,
+ * changes owner of `fun.body` from `fun.symbol` to resulting method's symbol.
+ *
+ * @param owner The owner for the new method
+ * @param fun the function to take the body from
+ * @param name name for the new method
+ * @param additionalFlags flags to be put on the method in addition to FINAL
+ */
+ private def mkMethodForFunctionBody(localTyper: analyzer.Typer)
+ (owner: Symbol, fun: Function, name: TermName)
+ (methParamProtos: List[Symbol] = fun.vparams.map(_.symbol),
+ resTp: Type = functionResultType(fun.tpe),
+ additionalFlags: FlagSet = NoFlags): DefDef = {
+ val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags)
+ // for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parmeter types)
+ val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) }
+ methSym setInfo MethodType(methParamSyms, resTp)
- // Have to repack the type to avoid mismatches when existentials
- // appear in the result - see SI-4869.
- val resTp = localTyper.packedType(body, methSym).deconst
- methSym setInfo MethodType(methParamSyms, fun.tpe.typeArgs.last.deconst) // TODO: use `resTp` -- preserving wrong status quo because refactoring-only
+ // we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms
+ val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms)
+ // we're now owned by the method that holds the body, and not the function
+ val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym)
- newDefDef(methSym, body)(tpt = TypeTree(resTp))
+ newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp))
}
+ // TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface)
def functionClassType(fun: Function): Type =
- abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.tpe.typeArgs.last.deconst) // TODO: use `fun.body.tpe.deconst` -- preserving wrong status quo because refactoring-only
+ if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst)
+ else fun.tpe
def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = {
val parents = addObjectParent(addSerializable(functionClassType(fun)))
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 254bf81999..8a318b194f 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -31,6 +31,17 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: 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)
@@ -43,205 +54,214 @@ 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
-
+ 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?
- lazy 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
+ private[this] lazy val methodReferencesThis: Set[Symbol] =
+ (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body)
+
+ private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: 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
}
- methodReferringMap.keys foreach refersToThis
- referrers
+
+ // 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))
+ }
+
+ // We then apply this symbol to the captures.
+ val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs))
+
+ // 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)
+
+ // 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))
+
+ apply
}
+
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 => 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)
+ 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)
+ private def valueTypeToObject(tpe: Type): Type =
+ if (isPrimitiveValueClass(tpe.typeSymbol) || enteringErasure(tpe.typeSymbol.isDerivedValueClass)) ObjectTpe
+ else tpe
+
+ // exclude primitives and value classses, which need special boxing
+ private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && {
+ val sym = tp.typeSymbol
+ !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass)
}
- def createBoxingBridgeMethod(oldClass: Symbol, target: Symbol, functionParamTypes: List[Type], functionResultType: Type, pos: Position): Symbol = {
- // 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) target
+ // 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): 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 sam = samOf(functionalInterface.tpe) orElse functionalInterface.info.member(nme.apply)
+ 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 {
- val bridgeParamTrees = bridgeParams.map(ValDef(_))
+ // 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.
+
+ val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) =>
+ if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp
+ else samParamTp
+ }
+
+ val bridgeResultType =
+ if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType
+ else samResultType
+
+ val typeAdapter = new TypeAdapter { val typer = localTyper }
+ import typeAdapter.{box, unbox, cast, 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)
+ 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 body = localTyper.typedPos(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 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)
+
+ gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs)
}
- val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass))
- adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe))), bridgeResultType)
- else adaptToType(body, bridgeResultType)
- val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1)
- val bridge = postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef]
+
+ val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))),
+ adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef]
+
boxingBridgeMethods += bridge
bridge.symbol
}
}
- // turns a lambda into a new class def, a New expression instantiating that class
private def transformFunction(originalFunction: Function): Tree = {
- val oldClass = originalFunction.symbol.enclClass
- val arity = originalFunction.vparams.length
-
val target = targetMethod(originalFunction)
target.makeNotPrivate(target.owner)
- if (!thisReferringMethods.contains(target))
- target setFlag STATIC
-
- val isStatic = target.hasFlag(STATIC)
-
- // 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 funSym = functionType.typeSymbol
- val specializedName = specializeTypes.specializedFunctionName(funSym, functionType.typeArgs).toTypeName
- val isSpecialized = specializedName != funSym.name
+ // must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall
+ if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC
- // 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.
- val functionalInterface =
- if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J"))
- else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity)
+ 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
- assert(functionalInterface.exists)
+ val isSpecialized = specializedName != funSym.name
+ val functionalInterface =
+ if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J"))
+ else currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(originalFunction.vparams.length)
- val lambdaTarget =
- if (isSpecialized) target
- else createBoxingBridgeMethod(oldClass, target, functionParamTypes, functionResultType, originalFunction.pos)
-
- // We then apply this symbol to the captures.
- val apply = {
- val allCaptureArgs: List[Tree] = {
- // 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 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
- }
-
- // Create a symbol representing a fictional lambda factory method that accepts the captured
- // arguments and returns a Function.
- val msym = {
- val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT)
- val capturedParams = meth.newSyntheticValueParams(allCaptureArgs.map(_.tpe))
- meth.setInfo(MethodType(capturedParams, functionType))
+ (functionalInterface, isSpecialized)
}
- 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))
+ mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, isSpecialized)
+ }
- apply
+ // 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
- /**
- * 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")
- }
// A traverser that finds symbols used but not defined in the given Tree
// TODO freeVarTraverser in LambdaLift does a very similar task. With some
@@ -276,12 +296,33 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
}
// finds all methods that reference 'this'
- class ThisReferringMethodsTraverser() extends Traverser {
- private var currentMethod: Symbol = NoSymbol
+ class ThisReferringMethodsTraverser extends Traverser {
// the set of methods that refer to this
- val thisReferringMethods = mutable.Set[Symbol]()
+ private 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())
+ private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set())
+
+ def methodReferencesThisIn(tree: Tree) = {
+ traverse(tree)
+ liftedMethodReferences.keys foreach refersToThis
+
+ 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
+ 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
+ }
+
+ private var currentMethod: Symbol = NoSymbol
+
override def traverse(tree: Tree) = tree match {
case DefDef(_, _, _, _, _, _) =>
// we don't expect defs within defs. At this phase trees should be very flat
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index c142c96e66..7e9e0e2a92 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -194,7 +194,7 @@ abstract class UnCurry extends InfoTransform
else if (mustExpandFunction) gen.expandFunction(localTyper)(fun, inConstructorFlag)
else {
// method definition with the same arguments, return type, and body as the original lambda
- val liftedMethod = gen.mkMethodFromFunction(localTyper)(fun.symbol.owner, fun, nme.ANON_FUN_NAME, ARTIFACT)
+ val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun)
// new function whose body is just a call to the lifted method
val newFun = deriveFunction(fun)(_ => localTyper.typedPos(fun.pos)(
diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
index bdcf90681d..c5a3d605b1 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -478,8 +478,7 @@ trait Contexts { self: Analyzer =>
c(ConstructorSuffix) = !isTemplateOrPackage && c(ConstructorSuffix)
// SI-8245 `isLazy` need to skip lazy getters to ensure `return` binds to the right place
- // similarly for the synthetic method that holds as a SAM's body (as synthesized by `synthesizeSAMFunction`)
- c.enclMethod = if (isDefDef && !(owner.isLazy || owner.name.endsWith(nme.SAM_BODY_SUFFIX))) c else enclMethod
+ c.enclMethod = if (isDefDef && !owner.isLazy) c else enclMethod
if (tree != outer.tree)
c(TypeConstructorAllowed) = false
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 55d49929a8..5c108c5fda 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -1056,11 +1056,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
return instantiate(tree, mode, pt)
// we know `!(tree.tpe <:< pt)`; try to remedy if there's a sam for pt
- val sam = if (tree.isInstanceOf[Function] && !isFunctionType(pt)) samOf(pt) else NoSymbol
- if (sam.exists && samMatchesFunctionBasedOnArity(sam, tree.asInstanceOf[Function].vparams)) {
- // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body`
- // to an instance of the corresponding anonymous subclass of `pt`.
- val samTree = synthesizeSAMFunction(sam, tree.asInstanceOf[Function], pt, mode)
+ val sam = samMatchingFunction(tree, pt) // this implies tree.isInstanceOf[Function]
+ if (sam.exists) {
+ val samTree = adaptToSAM(sam, tree.asInstanceOf[Function], pt, mode)
if (samTree ne EmptyTree) return samTree
}
@@ -2748,137 +2746,62 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple.
*
*/
- def synthesizeSAMFunction(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = {
+ def adaptToSAM(sam: Symbol, fun: Function, pt: Type, mode: Mode): Tree = {
// `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that
// `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms)
// I tried very hard to leave `fun` untyped and rework everything into the right shape and type check once,
// but couldn't make it work due to retyping that happens down the line
// (implicit conversion retries/retypechecking, CBN transform, super call arg context nesting weirdness)
- val sampos = fun.pos.focus
+ def funTpMatchesExpected(pt: Type): Boolean = isFullyDefined(pt) && {
+ // what's the signature of the method that we should actually be overriding?
+ val samMethType = pt memberInfo sam
+ fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType)
+ }
- // Type check body def before classdef to fully determine samClassTp (if necessary).
- // As `samClassTp` determines a parent type for the class,
- // we can't type check `block` in one go unless `samClassTp` is fully defined.
- val ptFullyDefined =
- if (isFullyDefined(pt) && !isNonRefinementClassType(unwrapToClass(pt))) pt
- else try {
- val samClassSym = pt.typeSymbol
-
- // we're trying to fully define the type arguments for this type constructor
- val samTyCon = samClassSym.typeConstructor
-
- // the unknowns
- val tparams = samClassSym.typeParams
- // ... as typevars
- val tvars = tparams map freshVar
-
- val ptVars = appliedType(samTyCon, tvars)
-
- // carry over info from pt
- ptVars <:< pt
-
- val samInfoWithTVars = ptVars.memberInfo(sam)
-
- // use function type subtyping, not method type subtyping (the latter is invariant in argument types)
- fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType)
-
- val variances = tparams map varianceInType(sam.info)
-
- // solve constraints tracked by tvars
- val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil))
-
- debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams")
+ if (funTpMatchesExpected(pt)) fun.setType(pt)
+ else try {
+ val samClassSym = pt.typeSymbol
- // a fully defined samClassTp
- appliedType(samTyCon, targs)
- } catch {
- case e@(_: NoInstance | _: TypeError) => // TODO: we get here whenever pt contains a wildcardtype???
- debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e")
- return EmptyTree
- }
+ // we're trying to fully define the type arguments for this type constructor
+ val samTyCon = samClassSym.typeConstructor
- debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}")
+ // the unknowns
+ val tparams = samClassSym.typeParams
+ // ... as typevars
+ val tvars = tparams map freshVar
- // what's the signature of the method that we should actually be overriding?
- val samMethType = ptFullyDefined memberInfo sam
- val samFunTp = functionType(samMethType.paramTypes, samMethType.finalResultType)
+ val ptVars = appliedType(samTyCon, tvars)
- // TODO: should we preemptively check that ptFullyDefined is a valid class type?
- // (to avoid running into type errors when type checking the instantiation below)
- if (!(fun.tpe <:< samFunTp)) return EmptyTree
+ // carry over info from pt
+ ptVars <:< pt
+ val samInfoWithTVars = ptVars.memberInfo(sam)
- // TODO: defer the remainder of this synthesis to the back-end, and use invokedynamic/LMF instead
- // can we carry a Function node down the pipeline if it has type `ptFullyDefined`, which is not a `FunctionN`?
- val bodyName = (sam.name append nme.SAM_BODY_SUFFIX).toTermName
+ // use function type subtyping, not method type subtyping (the latter is invariant in argument types)
+ fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType)
- // drop symbol info
- val samBodyDefParams = fun.vparams.map { vd => ValDef(vd.mods, vd.name, vd.tpt, vd.rhs) }
+ val variances = tparams map varianceInType(sam.info)
- // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body`
- //
- // TODO: make this behave like a static method during type checking to enforce scoping:
- // the sam class's self reference should not be in scope here.
- // To survive retyping, we must package samBodyDef as a member of the sam's class, however.
- // Scoping is not a problem (regardless of whether STATIC works), as the body has already been type checked in typedFunction.
- //
- // (Lifting samBodyDef into the surrounding block cause several problems:
- // when the tree is in a ctor arg / implicitly converted in multiple tries --> lambda lift crash "Could not find proxy...")
- //
- // the rhs is already typed, so it doesn't matter it uses the wrong parameter symbols (must wait until block is typed before we can fix them)
- val samBodyDef =
- DefDef(NoMods, // TODO: Modifiers(STATIC) when the backend supports it (currently it causes VerifyErrors)
- bodyName,
- Nil,
- List(samBodyDefParams),
- TypeTree(fun.body.tpe) setPos sampos,
- fun.body)
-
- // drop symbol info, use type info from sam so we implement the right method
- val samDefParams = map2(fun.vparams, samMethType.paramTypes) { (vd, tp) => ValDef(vd.mods, vd.name, TypeTree(tp), vd.rhs) }
-
- // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)`
- val samDef =
- DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC),
- sam.name.toTermName,
- Nil,
- List(samDefParams),
- TypeTree(samMethType.finalResultType) setPos sampos,
- Apply(Ident(bodyName), fun.vparams.map(gen.paramToArg))
- )
+ // solve constraints tracked by tvars
+ val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil))
- val samSubclassName = tpnme.ANON_FUN_NAME
- val classDef =
- ClassDef(Modifiers(FINAL), samSubclassName, tparams = Nil,
- gen.mkTemplate(
- parents = TypeTree(ptFullyDefined) :: (if (typeIsSubTypeOfSerializable(pt)) Nil else List(TypeTree(SerializableTpe))),
- self = noSelfType,
- constrMods = NoMods,
- vparamss = ListOfNil,
- body = List(samBodyDef, samDef),
- superPos = sampos
- )
- )
+ debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams")
- // type checking the whole block, so that everything is packaged together nicely
- // and we don't have to create any symbols by hand
- val block =
- typedPos(sampos, mode, pt) {
- Block(classDef, Apply(Select(New(Ident(samSubclassName)), nme.CONSTRUCTOR), Nil))
+ // a fully defined samClassTp
+ val ptFullyDefined = appliedType(samTyCon, targs)
+ if (funTpMatchesExpected(ptFullyDefined)) {
+ debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}")
+ fun.setType(ptFullyDefined)
+ } else {
+ debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)")
+ EmptyTree
}
-
- // fix owner for parts of the function we reused now that samBodyDef has a symbol
- samBodyDef.rhs.changeOwner((fun.symbol, samBodyDef.symbol))
- samBodyDef.rhs.substituteSymbols(fun.vparams.map(_.symbol), samBodyDef.vparamss.head.map(_.symbol))
-
- // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`)
- // the errors in the function don't get out...
- if (block exists (_.isErroneous))
- context.error(fun.pos, s"Could not derive subclass of $pt\n (with SAM `${sam.defStringSeenAs(ptFullyDefined)}`)\n based on: $fun.")
-
- classDef.symbol addAnnotation SerialVersionUIDAnnotation
- block
+ } catch {
+ case e@(_: NoInstance | _: TypeError) => // TODO: we get here whenever pt contains a wildcardtype???
+ debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e")
+ EmptyTree
+ }
}
/** Type check a function literal.
diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala
index 25498b73ce..6abd7f2f19 100644
--- a/src/reflect/scala/reflect/internal/StdNames.scala
+++ b/src/reflect/scala/reflect/internal/StdNames.scala
@@ -348,7 +348,6 @@ trait StdNames {
val REIFY_FREE_THIS_SUFFIX: NameType = "$this"
val REIFY_FREE_VALUE_SUFFIX: NameType = "$value"
val REIFY_SYMDEF_PREFIX: NameType = "symdef$"
- val SAM_BODY_SUFFIX: NameType = "$body"
val QUASIQUOTE_CASE: NameType = "$quasiquote$case$"
val QUASIQUOTE_EARLY_DEF: NameType = "$quasiquote$early$def$"
val QUASIQUOTE_FILE: String = "<quasiquote>"
diff --git a/test/files/neg/sammy_restrictions.check b/test/files/neg/sammy_restrictions.check
index 8cc49f9aa9..0276f3a067 100644
--- a/test/files/neg/sammy_restrictions.check
+++ b/test/files/neg/sammy_restrictions.check
@@ -8,9 +8,6 @@ sammy_restrictions.scala:32: error: type mismatch;
required: TwoAbstract
((x: Int) => 0): TwoAbstract
^
-sammy_restrictions.scala:34: error: class type required but DerivedOneAbstract with OneAbstract found
- ((x: Int) => 0): NonClassType // "class type required". I think we should avoid SAM translation here.
- ^
sammy_restrictions.scala:35: error: type mismatch;
found : Int => Int
required: NoEmptyConstructor
@@ -46,4 +43,4 @@ sammy_restrictions.scala:44: error: type mismatch;
required: PolyMethod
((x: Int) => 0): PolyMethod
^
-10 errors found
+9 errors found
diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala
index d003cfaf36..101342ad0b 100644
--- a/test/files/neg/sammy_restrictions.scala
+++ b/test/files/neg/sammy_restrictions.scala
@@ -31,7 +31,7 @@ object Test {
(() => 0) : NoAbstract
((x: Int) => 0): TwoAbstract
((x: Int) => 0): DerivedOneAbstract // okay
- ((x: Int) => 0): NonClassType // "class type required". I think we should avoid SAM translation here.
+ ((x: Int) => 0): NonClassType // okay -- we also allow type aliases in instantiation expressions, if they resolve to a class type
((x: Int) => 0): NoEmptyConstructor
((x: Int) => 0): OneEmptyConstructor // okay
((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call.
diff --git a/test/files/pos/sammy_implicit.scala b/test/files/pos/sammy_implicit.scala
index c9c2519bab..e4b82df4cc 100644
--- a/test/files/pos/sammy_implicit.scala
+++ b/test/files/pos/sammy_implicit.scala
@@ -6,5 +6,5 @@ abstract class SamImplicitConvert {
implicit def conv(xs: Array[Int]): Lst[Int]
- val encoded = flatMap (_.getBytes)
+ def encoded = flatMap (_.getBytes)
}
diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala
index c629be7166..75ee36f654 100644
--- a/test/files/pos/sammy_poly.scala
+++ b/test/files/pos/sammy_poly.scala
@@ -1,7 +1,8 @@
// test synthesizeSAMFunction where the sam type is not fully defined
class T {
trait F[T, U] { def apply(x: T): U }
+// type F[T, U] = T => U
// NOTE: the f(x) desugaring for now assumes the single abstract method is called 'apply'
def app[T, U](x: T)(f: F[T, U]): U = f(x)
app(1)(x => List(x))
-} \ No newline at end of file
+}
diff --git a/test/files/run/indylambda-boxing/test.scala b/test/files/run/indylambda-boxing/test.scala
index cc0a460640..82f8d2f497 100644
--- a/test/files/run/indylambda-boxing/test.scala
+++ b/test/files/run/indylambda-boxing/test.scala
@@ -2,15 +2,16 @@ class Capture
class Test {
def test1 = (i: Int) => ""
def test2 = (i: VC) => i
- def test3 = (i: Int) => i
+ def test3 = (i: Int) => i // not adapted, specialized
- def test4 = {val c = new Capture; (i: Int) => {(c, Test.this.toString); 42} }
+ def test4 = {val c = new Capture; (i: Int) => {(c, Test.this.toString); 42} } // not adapted, specialized
def test5 = {val c = new Capture; (i: VC) => (c, Test.this.toString) }
def test6 = {val c = new Capture; (i: Int) => (c, Test.this.toString) }
def test7 = {val vc = new Capture; (i: Int) => vc }
- def test8 = {val c = 42; (s: String) => (s, c)}
+ def test8 = {val c = 42; (s: String) => (s, c)} // not adapted
def test9 = {val c = 42; (s: String) => ()}
+ def test10 = {(s: List[String]) => ()}
}
object Test {
diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala
index 758566fe53..d29f6b0a13 100644
--- a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala
+++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala
@@ -31,25 +31,44 @@ class IndyLambdaTest extends ClearAfterClass {
case _ => Nil
}.head
}
+
+ val obj = "Ljava/lang/Object;"
+ val str = "Ljava/lang/String;"
+
// unspecialized functions that have a primitive in parameter or return position
// give rise to a "boxing bridge" method (which has the suffix `$adapted`).
// This is because Scala's unboxing of null values gives zero, whereas Java's throw a NPE.
// 1. Here we show that we are calling the boxing bridge (the lambda bodies here are compiled into
// methods of `(I)Ljava/lang/Object;` / `(I)Ljava/lang/Object;` respectively.)
- assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Int) => new Object"))
- assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Object) => 0"))
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Int) => new Object"))
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Object) => 0"))
// 2a. We don't need such adaptations for parameters or return values with types that differ
// from Object due to other generic substitution, LambdaMetafactory will downcast the arguments.
- assertEquals("(Ljava/lang/String;)Ljava/lang/String;", implMethodDescriptorFor("(x: String) => x"))
+ assertEquals(s"($str)$str", implMethodDescriptorFor("(x: String) => x"))
// 2b. Testing 2a. in combination with 1.
- assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", implMethodDescriptorFor("(x: Int) => \"\""))
- assertEquals("(Ljava/lang/String;)Ljava/lang/Object;", implMethodDescriptorFor("(x: String) => 0"))
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("(x: Int) => \"\""))
+ assertEquals(s"($str)$obj", implMethodDescriptorFor("(x: String) => 0"))
// 3. Specialized functions, don't need any of this as they implement a method like `apply$mcII$sp`,
// and the (un)boxing is handled in the base class in code emitted by scalac.
assertEquals("(I)I", implMethodDescriptorFor("(x: Int) => x"))
+
+ // non-builtin sams are like specialized functions
+ compileClasses(compiler)("class VC(private val i: Int) extends AnyVal; trait FunVC { def apply(a: VC): VC }")
+ assertEquals("(I)I", implMethodDescriptorFor("((x: VC) => x): FunVC"))
+
+ compileClasses(compiler)("trait Fun1[T, U] { def apply(a: T): U }")
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("(x => x.toString): Fun1[Int, String]"))
+ assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x => println(x)): Fun1[Int, Unit]"))
+ assertEquals(s"($obj)$str", implMethodDescriptorFor("((x: VC) => \"\") : Fun1[VC, String]"))
+ assertEquals(s"($str)$obj", implMethodDescriptorFor("((x: String) => new VC(0)) : Fun1[String, VC]"))
+
+ compileClasses(compiler)("trait Coll[A, Repr] extends Any")
+ compileClasses(compiler)("final class ofInt(val repr: Array[Int]) extends AnyVal with Coll[Int, Array[Int]]")
+
+ assertEquals(s"([I)$obj", implMethodDescriptorFor("((xs: Array[Int]) => new ofInt(xs)): Array[Int] => Coll[Int, Array[Int]]"))
}
}