diff options
author | Adriaan Moors <adriaan.moors@typesafe.com> | 2016-02-08 18:24:43 -0800 |
---|---|---|
committer | Adriaan Moors <adriaan.moors@typesafe.com> | 2016-03-26 22:52:41 -0700 |
commit | 8433b6fa0e86dfdcd3db31b97844b14d65e45359 (patch) | |
tree | 08d2db915b88057ff1b16479e797bbca41a385ce | |
parent | 651d67cff7af581751257711ad99d318a5a2879a (diff) | |
download | scala-8433b6fa0e86dfdcd3db31b97844b14d65e45359.tar.gz scala-8433b6fa0e86dfdcd3db31b97844b14d65e45359.tar.bz2 scala-8433b6fa0e86dfdcd3db31b97844b14d65e45359.zip |
Treat `Function` literals uniformly, expecting SAM or FunctionN.
They both compile to INDY/MetaLambdaFactory, except when they
occur in a constructor call. (TODO: can we lift the ctor arg
expression to a method and avoid statically synthesizing
anonymous subclass altogether?)
Typers:
- no longer synthesize SAMs -- *adapt* a Function literal
to the expected (SAM/FunctionN) type
- Deal with polymorphic/existential sams (relevant tests:
pos/t8310, pos/t5099.scala, pos/t4869.scala) We know where
to find the result type, as all Function nodes have a
FunctionN-shaped type during erasure. (Including function
literals targeting a SAM type -- the sam type is tracked as
the *expected* type.)
Lift restriction on sam types being class types. It's enough
that they dealias to one, like regular instance creation
expressions.
Contexts:
- No longer need encl method hack for return in sam.
Erasure:
- erasure preserves SAM type for function nodes
- Normalize sam to erased function type during erasure,
otherwise we may box the function body from `$anonfun(args)`
to `{$anonfun(args); ()}` because the expected type for the
body is now `Object`, and thus `Unit` does not conform.
Delambdafy:
- must set static flag before calling createBoxingBridgeMethod
- Refactored `createBoxingBridgeMethod` to wrap my head around
boxing, reworked it to generalize from FunctionN's boxing
needs to arbitrary LMF targets.
Other refactorings: ThisReferringMethodsTraverser, TreeGen.
-rw-r--r-- | src/compiler/scala/tools/nsc/ast/TreeGen.scala | 76 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/Delambdafy.scala | 377 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 3 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/Typers.scala | 159 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/StdNames.scala | 1 | ||||
-rw-r--r-- | test/files/neg/sammy_restrictions.check | 5 | ||||
-rw-r--r-- | test/files/neg/sammy_restrictions.scala | 2 | ||||
-rw-r--r-- | test/files/pos/sammy_implicit.scala | 2 | ||||
-rw-r--r-- | test/files/pos/sammy_poly.scala | 3 | ||||
-rw-r--r-- | test/files/run/indylambda-boxing/test.scala | 7 | ||||
-rw-r--r-- | test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala | 29 |
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]]")) } } |