diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/UnCurry.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/UnCurry.scala | 241 |
1 files changed, 119 insertions, 122 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index d5a7213cfb..f35dd6556f 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -7,9 +7,10 @@ package scala package tools.nsc package transform +import scala.annotation.tailrec + import symtab.Flags._ -import scala.collection.{ mutable, immutable } -import scala.language.postfixOps +import scala.collection.mutable import scala.reflect.internal.util.ListOfNil /*<export> */ @@ -68,19 +69,30 @@ abstract class UnCurry extends InfoTransform // uncurry and uncurryType expand type aliases class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline" + private val forceExpandFunction = settings.Ydelambdafy.value == "inline" private var needTryLift = false private var inConstructorFlag = 0L private val byNameArgs = mutable.HashSet[Tree]() private val noApply = mutable.HashSet[Tree]() private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]() - private lazy val forceSpecializationInfoTransformOfFunctionN: Unit = { - if (currentRun.specializePhase != NoPhase) { // be robust in case of -Ystop-after:uncurry - exitingSpecialize { - FunctionClass.seq.foreach(cls => cls.info) - } - } + // Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363) + // We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner + private def mustExpandFunction(fun: Function) = { + // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding) + val canUseLambdaMetaFactory = (fun.attachments.get[SAMFunction] match { + case Some(SAMFunction(userDefinedSamTp, sam)) => + // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply + erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) && + // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167) + // specialization and LMF are at odds, since LMF implements the single abstract method, + // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing + !specializeTypes.isSpecializedIn(sam, userDefinedSamTp) + + case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction + }) + + !canUseLambdaMetaFactory } /** Add a new synthetic member for `currentOwner` */ @@ -91,25 +103,17 @@ abstract class UnCurry extends InfoTransform @inline private def useNewMembers[T](owner: Symbol)(f: List[Tree] => T): T = f(newMembers.remove(owner).getOrElse(Nil).toList) - private def newFunction0(body: Tree): Tree = { - val result = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] - log("Change owner from %s to %s in %s".format(currentOwner, result.symbol, result.body)) - result.body changeOwner (currentOwner -> result.symbol) - transformFunction(result) - } - // I don't have a clue why I'm catching TypeErrors here, but it's better // than spewing stack traces at end users for internal errors. Examples // which hit at this point should not be hard to come by, but the immediate // motivation can be seen in continuations-neg/t3718. - override def transform(tree: Tree): Tree = ( + override def transform(tree: Tree): Tree = try postTransform(mainTransform(tree)) catch { case ex: TypeError => reporter.error(ex.pos, ex.msg) debugStack(ex) EmptyTree } - ) /* Is tree a reference `x` to a call by name parameter that needs to be converted to * x.apply()? Note that this is not the case if `x` is used as an argument to another @@ -118,7 +122,7 @@ abstract class UnCurry extends InfoTransform def isByNameRef(tree: Tree) = ( tree.isTerm && (tree.symbol ne null) - && (isByName(tree.symbol)) + && isByName(tree.symbol) && !byNameArgs(tree) ) @@ -195,16 +199,6 @@ abstract class UnCurry extends InfoTransform // ------ Transforming anonymous functions and by-name-arguments ---------------- - /** Undo eta expansion for parameterless and nullary methods */ - def deEta(fun: Function): Tree = fun match { - case Function(List(), expr) if isByNameRef(expr) => - noApply += expr - expr - case _ => - fun - } - - /** Transform a function node (x_1,...,x_n) => body of type FunctionN[T_1, .., T_N, R] to * * class $anon() extends AbstractFunctionN[T_1, .., T_N, R] with Serializable { @@ -213,66 +207,38 @@ abstract class UnCurry extends InfoTransform * new $anon() * */ - def transformFunction(fun: Function): Tree = { - fun.tpe match { - // can happen when analyzer plugins assign refined types to functions, e.g. - // (() => Int) { def apply(): Int @typeConstraint } - case RefinedType(List(funTp), decls) => - debuglog(s"eliminate refinement from function type ${fun.tpe}") - fun.setType(funTp) - case _ => - () - } - - deEta(fun) match { - // nullary or parameterless - case fun1 if fun1 ne fun => fun1 - case _ => - def typedFunPos(t: Tree) = localTyper.typedPos(fun.pos)(t) - val funParams = fun.vparams map (_.symbol) - def mkMethod(owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags): DefDef = - gen.mkMethodFromFunction(localTyper)(fun, owner, name, additionalFlags) - - def isSpecialized = { - forceSpecializationInfoTransformOfFunctionN - val specialized = specializeTypes.specializedType(fun.tpe) - !(specialized =:= fun.tpe) - } + def transformFunction(fun: Function): Tree = + // Undo eta expansion for parameterless and nullary methods, EXCEPT if `fun` targets a SAM. + // Normally, we can unwrap `() => cbn` to `cbn` where `cbn` refers to a CBN argument (typically `cbn` is an Ident), + // because we know `cbn` will already be a `Function0` thunk. When we're targeting a SAM, + // the types don't align and we must preserve the function wrapper. + if (fun.vparams.isEmpty && isByNameRef(fun.body) && fun.attachments.get[SAMFunction].isEmpty) { noApply += fun.body ; fun.body } + else if (forceExpandFunction || inConstructorFlag != 0) { + // Expand the function body into an anonymous class + gen.expandFunction(localTyper)(fun, inConstructorFlag) + } else { + val mustExpand = mustExpandFunction(fun) + // method definition with the same arguments, return type, and body as the original lambda + 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)( + gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil) + )) - def canUseDelamdafyMethod = ( - (inConstructorFlag == 0) // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation - && (!isSpecialized || (settings.isBCodeActive && settings.target.value == "jvm-1.8")) // DelambdafyTransformer currently only emits generic FunctionN-s, use the old style in the meantime - ) - if (inlineFunctionExpansion || !canUseDelamdafyMethod) { - val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) - val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation - // The original owner is used in the backend for the EnclosingMethod attribute. If fun is - // nested in a value-class method, its owner was already changed to the extension method. - // Saving the original owner allows getting the source structure from the class symbol. - defineOriginalOwner(anonClass, fun.symbol.originalOwner) - anonClass setInfo ClassInfoType(parents, newScope, anonClass) - - val applyMethodDef = mkMethod(anonClass, nme.apply) - anonClass.info.decls enter applyMethodDef.symbol - - typedFunPos { - Block( - ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos), - Typed(New(anonClass.tpe), TypeTree(fun.tpe))) - } - } else { - // method definition with the same arguments, return type, and body as the original lambda - val liftedMethod = mkMethod(fun.symbol.owner, nme.ANON_FUN_NAME, additionalFlags = ARTIFACT) - - // new function whose body is just a call to the lifted method - val newFun = deriveFunction(fun)(_ => typedFunPos( - gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), funParams :: Nil) - )) - typedFunPos(Block(liftedMethod, super.transform(newFun))) - } + if (!mustExpand) { + liftedMethod.symbol.updateAttachment(DelambdafyTarget) + liftedMethod.updateAttachment(DelambdafyTarget) } - } + val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun))) + if (mustExpand) { + val Block(stats, expr : Function) = typedNewFun + treeCopy.Block(typedNewFun, stats, gen.expandFunction(localTyper)(expr, inConstructorFlag)) + } else { + typedNewFun + } + } def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = { val isJava = fun.isJavaDefined @@ -350,25 +316,22 @@ abstract class UnCurry extends InfoTransform val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head) else args map2(formals, args1) { (formal, arg) => - if (!isByNameParamType(formal)) - arg - else if (isByNameRef(arg)) { + if (!isByNameParamType(formal)) arg + else if (isByNameRef(arg)) { // thunk does not need to be forced because it's a reference to a by-name arg passed to a by-name param byNameArgs += arg arg setType functionType(Nil, arg.tpe) - } - else { + } else { log(s"Argument '$arg' at line ${arg.pos.line} is $formal from ${fun.fullName}") - def canUseDirectly(recv: Tree) = ( - recv.tpe.typeSymbol.isSubClass(FunctionClass(0)) - && treeInfo.isExprSafeToInline(recv) - ) + def canUseDirectly(qual: Tree) = qual.tpe.typeSymbol.isSubClass(FunctionClass(0)) && treeInfo.isExprSafeToInline(qual) arg match { // don't add a thunk for by-name argument if argument already is an application of // a Function0. We can then remove the application and use the existing Function0. - case Apply(Select(recv, nme.apply), Nil) if canUseDirectly(recv) => - recv - case _ => - newFunction0(arg) + case Apply(Select(qual, nme.apply), Nil) if canUseDirectly(qual) => qual + case body => + val thunkFun = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] + log(s"Change owner from $currentOwner to ${thunkFun.symbol} in ${thunkFun.body}") + thunkFun.body.changeOwner((currentOwner, thunkFun.symbol)) + transformFunction(thunkFun) } } } @@ -379,23 +342,33 @@ abstract class UnCurry extends InfoTransform * the whole tree with it. */ private def replaceElidableTree(tree: Tree): Tree = { + def elisionOf(t: Type): Tree = t.typeSymbol match { + case StringClass => Literal(Constant("")) setType t + case _ => gen.mkZero(t) + } tree match { - case DefDef(_,_,_,_,_,_) => - deriveDefDef(tree)(rhs => Block(Nil, gen.mkZero(rhs.tpe)) setType rhs.tpe) setSymbol tree.symbol setType tree.tpe + case DefDef(_,_,_,_,_,rhs) => + val rhs1 = if (rhs == EmptyTree) rhs else Block(Nil, elisionOf(rhs.tpe)) setType rhs.tpe + deriveDefDef(tree)(_ => rhs1) setSymbol tree.symbol setType tree.tpe case _ => - gen.mkZero(tree.tpe) setType tree.tpe + elisionOf(tree.tpe) } } private def isSelfSynchronized(ddef: DefDef) = ddef.rhs match { case Apply(fn @ TypeApply(Select(sel, _), _), _) => - fn.symbol == Object_synchronized && sel.symbol == ddef.symbol.enclClass && !ddef.symbol.enclClass.isTrait + fn.symbol == Object_synchronized && sel.symbol == ddef.symbol.enclClass && !ddef.symbol.enclClass.isTrait && + !ddef.symbol.isDelambdafyTarget /* these become static later, unsuitable for ACC_SYNCHRONIZED */ case _ => false } /** If an eligible method is entirely wrapped in a call to synchronized * locked on the same instance, remove the synchronized scaffolding and * mark the method symbol SYNCHRONIZED for bytecode generation. + * + * Delambdafy targets are deemed ineligible as the Delambdafy phase will + * replace `this.synchronized` with `$this.synchronized` now that it emits + * all lambda impl methods as static. */ private def translateSynchronized(tree: Tree) = tree match { case dd @ DefDef(_, _, _, _, _, Apply(fn, body :: Nil)) if isSelfSynchronized(dd) => @@ -438,10 +411,20 @@ abstract class UnCurry extends InfoTransform val sym = tree.symbol // true if the target is a lambda body that's been lifted into a method - def isLiftedLambdaBody(target: Tree) = target.symbol.isLocalToBlock && target.symbol.isArtifact && target.symbol.name.containsName(nme.ANON_FUN_NAME) + def isLiftedLambdaMethod(funSym: Symbol) = + funSym.isArtifact && funSym.name.containsName(nme.ANON_FUN_NAME) && funSym.isLocalToBlock - val result = ( - if ((sym ne null) && sym.elisionLevel.exists(_ < settings.elidebelow.value)) + def checkIsElisible(sym: Symbol): Boolean = + (sym ne null) && sym.elisionLevel.exists { level => + if (sym.isMethod) level < settings.elidebelow.value + else { + if (settings.isScala213) reporter.error(sym.pos, s"${sym.name}: Only methods can be marked @elidable!") + false + } + } + + val result = + if (checkIsElisible(sym)) replaceElidableTree(tree) else translateSynchronized(tree) match { case dd @ DefDef(mods, name, tparams, _, tpt, rhs) => @@ -473,9 +456,9 @@ abstract class UnCurry extends InfoTransform super.transform(treeCopy.DefDef(dd, mods, name, tparams, vparamssNoRhs, tpt, rhs)) } } - case ValDef(_, _, _, rhs) => + case ValDef(mods, _, _, rhs) => if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit) - if (!sym.owner.isSourceMethod) + if (!sym.owner.isSourceMethod || mods.isLazy) withNeedLift(needLift = true) { super.transform(tree) } else super.transform(tree) @@ -493,7 +476,7 @@ abstract class UnCurry extends InfoTransform case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) => withNeedLift(needLift = true) { super.transform(tree) } - case ret @ Return(_) if (isNonLocalReturn(ret)) => + case ret @ Return(_) if isNonLocalReturn(ret) => withNeedLift(needLift = true) { super.transform(ret) } case Try(_, Nil, _) => @@ -512,7 +495,7 @@ abstract class UnCurry extends InfoTransform treeCopy.CaseDef(tree, pat1, transform(guard), transform(body)) // if a lambda is already the right shape we don't need to transform it again - case fun @ Function(_, Apply(target, _)) if (!inlineFunctionExpansion) && isLiftedLambdaBody(target) => + case fun @ Function(_, Apply(target, _)) if !forceExpandFunction && isLiftedLambdaMethod(target.symbol) => super.transform(fun) case fun @ Function(_, _) => @@ -532,9 +515,8 @@ abstract class UnCurry extends InfoTransform } tree1 } - ) - assert(result.tpe != null, result.shortClass + " tpe is null:\n" + result) - result modifyType uncurry + + result.setType(uncurry(result.tpe)) } def postTransform(tree: Tree): Tree = exitingUncurry { @@ -545,15 +527,18 @@ abstract class UnCurry extends InfoTransform case MethodType(_, _) => tree case tp => tree setType MethodType(Nil, tp.resultType) } - if (tree.symbol.isMethod && !tree.tpe.isInstanceOf[PolyType]) - gen.mkApplyIfNeeded(removeNullary()) + val sym = tree.symbol + // our info transformer may not have run yet, so duplicate flag logic instead of forcing it to run + val isMethodExitingUncurry = (sym hasFlag METHOD) || (sym hasFlag MODULE) && !sym.isStatic + if (isMethodExitingUncurry && !tree.tpe.isInstanceOf[PolyType]) + gen.mkApplyIfNeeded(removeNullary()) // apply () if tree.tpe has zero-arg MethodType else if (tree.isType) TypeTree(tree.tpe) setPos tree.pos else tree } - def isThrowable(pat: Tree): Boolean = pat match { + @tailrec def isThrowable(pat: Tree): Boolean = pat match { case Typed(Ident(nme.WILDCARD), tpt) => tpt.tpe =:= ThrowableTpe case Bind(_, pat) => @@ -579,6 +564,7 @@ abstract class UnCurry extends InfoTransform } case dd @ DefDef(_, _, _, vparamss0, _, rhs0) => + val ddSym = dd.symbol val (newParamss, newRhs): (List[List[ValDef]], Tree) = if (dependentParamTypeErasure isDependent dd) dependentParamTypeErasure erase dd @@ -590,11 +576,22 @@ abstract class UnCurry extends InfoTransform (vparamss1, rhs0) } + // A no-arg method with ConstantType result type can safely be reduced to the corresponding Literal + // (only pure methods are typed as ConstantType). We could also do this for methods with arguments, + // after ensuring the arguments are not referenced. + val literalRhsIfConst = + if (newParamss.head.isEmpty) { // We know newParamss.length == 1 from above + ddSym.info.resultType match { + case tp@ConstantType(value) => Literal(value) setType tp setPos newRhs.pos // inlining of gen.mkAttributedQualifier(tp) + case _ => newRhs + } + } else newRhs + val flatdd = copyDefDef(dd)( vparamss = newParamss, - rhs = nonLocalReturnKeys get dd.symbol match { - case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(newRhs, k, dd.symbol)) - case None => newRhs + rhs = nonLocalReturnKeys get ddSym match { + case Some(k) => atPos(newRhs.pos)(nonLocalReturnTry(literalRhsIfConst, k, ddSym)) + case None => literalRhsIfConst } ) // Only class members can reasonably be called from Java due to name mangling. @@ -619,7 +616,7 @@ abstract class UnCurry extends InfoTransform case Select(_, _) | TypeApply(_, _) => applyUnary() case ret @ Return(expr) if isNonLocalReturn(ret) => - log("non-local return from %s to %s".format(currentOwner.enclMethod, ret.symbol)) + log(s"non-local return from ${currentOwner.enclMethod} to ${ret.symbol}") atPos(ret.pos)(nonLocalReturnThrow(expr, ret.symbol)) case TypeTree() => tree @@ -712,7 +709,7 @@ abstract class UnCurry extends InfoTransform // // So what we need to do is to use the pre-uncurry type when creating `l$1`, which is `c.Tree` and is // correct. Now, there are two additional problems: - // 1. when varargs and byname params are involved, the uncurry transformation desugares these special + // 1. when varargs and byname params are involved, the uncurry transformation desugars these special // cases to actual typerefs, eg: // ``` // T* ~> Seq[T] (Scala-defined varargs) @@ -744,7 +741,7 @@ abstract class UnCurry extends InfoTransform case Packed(param, tempVal) => (param, tempVal) }.unzip - val rhs1 = if (tempVals.isEmpty) rhs else { + val rhs1 = if (rhs == EmptyTree || tempVals.isEmpty) rhs else { localTyper.typedPos(rhs.pos) { // Patch the method body to refer to the temp vals val rhsSubstituted = rhs.substituteSymbols(packedParams map (_.symbol), tempVals map (_.symbol)) |