summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/transform
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform')
-rw-r--r--src/compiler/scala/tools/nsc/transform/AddInterfaces.scala310
-rw-r--r--src/compiler/scala/tools/nsc/transform/CleanUp.scala157
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala551
-rw-r--r--src/compiler/scala/tools/nsc/transform/Delambdafy.scala699
-rw-r--r--src/compiler/scala/tools/nsc/transform/Erasure.scala100
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala63
-rw-r--r--src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala9
-rw-r--r--src/compiler/scala/tools/nsc/transform/Flatten.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/InlineErasure.scala11
-rw-r--r--src/compiler/scala/tools/nsc/transform/LambdaLift.scala203
-rw-r--r--src/compiler/scala/tools/nsc/transform/LazyVals.scala95
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala425
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala63
-rw-r--r--src/compiler/scala/tools/nsc/transform/Statics.scala57
-rw-r--r--src/compiler/scala/tools/nsc/transform/TailCalls.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala171
-rw-r--r--src/compiler/scala/tools/nsc/transform/TypingTransformers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala204
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/Logic.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala30
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala14
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala4
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala5
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala59
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala6
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala2
26 files changed, 1184 insertions, 2066 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
index 82e7c76409..9a8eca152f 100644
--- a/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
+++ b/src/compiler/scala/tools/nsc/transform/AddInterfaces.scala
@@ -8,208 +8,17 @@ package transform
import symtab._
import Flags._
-import scala.tools.nsc.util.ClassPath
abstract class AddInterfaces extends InfoTransform { self: Erasure =>
import global._ // the global environment
import definitions._ // standard classes and methods
- /** The phase sets lateINTERFACE for non-interface traits that now
- * become interfaces. It sets lateDEFERRED for formerly concrete
- * methods in such traits.
+ /** lateDEFERRED for formerly concrete methods in such traits.
*/
- override def phaseNewFlags: Long = lateDEFERRED | lateINTERFACE
-
- /** A lazily constructed map that associates every non-interface trait with
- * its implementation class.
- */
- private val implClassMap = perRunCaches.newMap[Symbol, Symbol]()
-
- /** A lazily constructed map that associates every concrete method in a non-interface
- * trait that's currently compiled with its corresponding method in the trait's
- * implementation class.
- */
- private val implMethodMap = perRunCaches.newMap[Symbol, Symbol]()
-
- override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = {
- implClassMap.clear()
- implMethodMap.clear()
- super.newPhase(prev)
- }
-
- /** Is given trait member symbol a member of the trait's interface
- * after this transform is performed?
- */
- private def isInterfaceMember(sym: Symbol) = (
- sym.isType || {
- sym.info // initialize to set lateMETHOD flag if necessary
-
- ( sym.isMethod
- && !sym.isLabel
- && !sym.isPrivate
- && (!(sym hasFlag BRIDGE) || sym.hasBridgeAnnotation) // count @bridge annotated classes as interface members
- && !sym.isConstructor
- && !sym.isImplOnly
- )
- }
- )
-
- /** Does symbol need an implementation method? */
- def needsImplMethod(sym: Symbol) = (
- sym.isMethod
- && isInterfaceMember(sym)
- && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED))
- )
-
- def implClassPhase = currentRun.erasurePhase.next
-
- private def newImplClass(iface: Symbol): Symbol = {
- val inClass = iface.owner.isClass
- val implName = tpnme.implClassName(iface.name)
- val implFlags = (iface.flags & ~(INTERFACE | lateINTERFACE)) | IMPLCLASS
-
- val impl0 = {
- if (!inClass) NoSymbol
- else {
- val typeInfo = iface.owner.info
- typeInfo.decl(implName) match {
- case NoSymbol => NoSymbol
- case implSym =>
- // Unlink a pre-existing symbol only if the implementation class is
- // visible on the compilation classpath. In general this is true under
- // -optimise and not otherwise, but the classpath can use arbitrary
- // logic so the classpath must be queried.
- // TODO this is not taken into account by flat classpath yet
- classPath match {
- case cp: ClassPath[_] if !cp.context.isValidName(implName + ".class") =>
- log(s"not unlinking $iface's existing implClass ${implSym.name} because it is not on the classpath.")
- implSym
- case _ =>
- typeInfo.decls unlink implSym
- NoSymbol
- }
- }
- }
- }
-
- val impl = impl0 orElse {
- val impl = iface.owner.newImplClass(implName, iface.pos, implFlags)
- if (iface.thisSym != iface) {
- impl.typeOfThis = iface.typeOfThis
- impl.thisSym setName iface.thisSym.name
- }
- impl.associatedFile = iface.sourceFile
- if (inClass)
- iface.owner.info.decls enter impl
-
- impl
- }
- if (currentRun compiles iface)
- currentRun.symSource(impl) = iface.sourceFile
-
- implClassMap(iface) = impl
- impl setInfo new LazyImplClassType(iface)
- }
-
- /** Return the implementation class of a trait; create a new one if one does not yet exist */
- def implClass(iface: Symbol): Symbol = {
- iface.info
-
- implClassMap.getOrElse(iface, enteringPhase(implClassPhase) {
- if (iface.implClass eq NoSymbol)
- debuglog(s"${iface.fullLocationString} has no implClass yet, creating it now.")
- else
- log(s"${iface.fullLocationString} impl class is ${iface.implClass.nameString}")
-
- newImplClass(iface)
- })
- }
-
- /** A lazy type to set the info of an implementation class
- * The parents of an implementation class for trait iface are:
- *
- * - superclass: Object
- * - mixin classes: mixin classes of iface where every non-interface
- * trait is mapped to its implementation class, followed by iface itself.
- *
- * The declarations of a mixin class are:
- * - for every interface member of iface: its implementation method, if one is needed
- * - every former member of iface that is implementation only
- */
- private class LazyImplClassType(iface: Symbol) extends LazyType with FlagAgnosticCompleter {
- /** Compute the decls of implementation class implClass,
- * given the decls ifaceDecls of its interface.
- */
- private def implDecls(implClass: Symbol, ifaceDecls: Scope): Scope = {
- debuglog("LazyImplClassType calculating decls for " + implClass)
-
- val decls = newScope
- if ((ifaceDecls lookup nme.MIXIN_CONSTRUCTOR) == NoSymbol) {
- log("Adding mixin constructor to " + implClass)
-
- decls enter (
- implClass.newMethod(nme.MIXIN_CONSTRUCTOR, implClass.pos)
- setInfo MethodType(Nil, UnitTpe)
- )
- }
-
- for (sym <- ifaceDecls) {
- if (isInterfaceMember(sym)) {
- if (needsImplMethod(sym)) {
- val clone = sym.cloneSymbol(implClass).resetFlag(lateDEFERRED)
- if (currentRun.compiles(implClass)) implMethodMap(sym) = clone
- decls enter clone
- sym setFlag lateDEFERRED
- if (!sym.isSpecialized)
- log(s"Cloned ${sym.name} from ${sym.owner} into implClass ${implClass.fullName}")
- }
- }
- else {
- log(s"Destructively modifying owner of $sym from ${sym.owner} to $implClass")
- sym.owner = implClass
- // note: OK to destructively modify the owner here,
- // because symbol will not be accessible from outside the sourcefile.
- // mixin constructors are corrected separately; see TermSymbol.owner
- decls enter sym
- }
- }
-
- decls
- }
-
- override def complete(implSym: Symbol) {
- debuglog("LazyImplClassType completing " + implSym)
-
- /* If `tp` refers to a non-interface trait, return a
- * reference to its implementation class. Otherwise return `tp`.
- */
- def mixinToImplClass(tp: Type): Type = AddInterfaces.this.erasure(implSym) {
- tp match { //@MATN: no normalize needed (comes after erasure)
- case TypeRef(pre, sym, _) if sym.needsImplClass =>
- typeRef(pre, implClass(sym), Nil)
- case _ =>
- tp
- }
- }
- def implType(tp: Type): Type = tp match {
- case ClassInfoType(parents, decls, _) =>
- assert(phase == implClassPhase, tp)
- // Impl class parents: Object first, matching interface last.
- val implParents = ObjectTpe +: (parents.tail map mixinToImplClass filter (_.typeSymbol != ObjectClass)) :+ iface.tpe
- ClassInfoType(implParents, implDecls(implSym, decls), implSym)
- case PolyType(_, restpe) =>
- implType(restpe)
- }
- implSym setInfo implType(enteringErasure(iface.info))
- }
-
- override def load(clazz: Symbol) { complete(clazz) }
- }
+ override def phaseNewFlags: Long = lateDEFERRED
def transformMixinInfo(tp: Type): Type = tp match {
case ClassInfoType(parents, decls, clazz) if clazz.isPackageClass || !clazz.isJavaDefined =>
- if (clazz.needsImplClass)
- implClass(clazz setFlag lateINTERFACE) // generate an impl class
val parents1 = parents match {
case Nil => Nil
@@ -218,21 +27,20 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure =>
if (clazz.isTrait) ObjectTpe :: tl
else parents
}
- val decls1 = scopeTransform(clazz)(
- decls filter (sym =>
- if (clazz.isInterface) isInterfaceMember(sym)
- else sym.isClass || sym.isTerm
- )
- )
- ClassInfoType(parents1, decls1, clazz)
+ if (clazz.isTrait) {
+ decls foreach { sym =>
+ if (!sym.isType) sym.info // initialize to set lateMETHOD flag if necessary
+ }
+ }
+ if (parents1 eq parents) tp
+ else ClassInfoType(parents1, decls, clazz)
case _ =>
tp
}
// Tree transformation --------------------------------------------------------------
-
private class ChangeOwnerAndReturnTraverser(oldowner: Symbol, newowner: Symbol)
- extends ChangeOwnerTraverser(oldowner, newowner) {
+ extends ChangeOwnerTraverser(oldowner, newowner) {
override def traverse(tree: Tree) {
tree match {
case _: Return => change(tree.symbol)
@@ -242,63 +50,10 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure =>
}
}
- private def createMemberDef(tree: Tree, isForInterface: Boolean)(create: Tree => Tree) = {
- val isInterfaceTree = tree.isDef && isInterfaceMember(tree.symbol)
- if (isInterfaceTree && needsImplMethod(tree.symbol))
- create(tree)
- else if (isInterfaceTree == isForInterface)
- tree
- else
- EmptyTree
- }
- private def implMemberDef(tree: Tree): Tree = createMemberDef(tree, false)(implMethodDef)
- private def ifaceMemberDef(tree: Tree): Tree = createMemberDef(tree, true)(t => DefDef(t.symbol, EmptyTree))
-
- private def ifaceTemplate(templ: Template): Template =
- treeCopy.Template(templ, templ.parents, noSelfType, templ.body map ifaceMemberDef)
-
- /** Transforms the member tree containing the implementation
- * into a member of the impl class.
- */
- private def implMethodDef(tree: Tree): Tree = {
- val impl = implMethodMap.getOrElse(tree.symbol, abort("implMethod missing for " + tree.symbol))
-
- val newTree = if (impl.isErroneous) tree else { // e.g. res/t687
- // SI-5167: Ensure that the tree that we are grafting refers the parameter symbols from the
- // new method symbol `impl`, rather than the symbols of the original method signature in
- // the trait. `tree setSymbol impl` does *not* suffice!
- val DefDef(_, _, _, vparamss, _, _) = tree
- val oldSyms = vparamss.flatten.map(_.symbol)
- val newSyms = impl.info.paramss.flatten
- assert(oldSyms.length == newSyms.length, (oldSyms, impl, impl.info))
- tree.substituteSymbols(oldSyms, newSyms)
- }
- new ChangeOwnerAndReturnTraverser(newTree.symbol, impl)(newTree setSymbol impl)
- }
-
- /** Add mixin constructor definition
- * def $init$(): Unit = ()
- * to `stats` unless there is already one.
- */
- private def addMixinConstructorDef(clazz: Symbol, stats: List[Tree]): List[Tree] =
- if (treeInfo.firstConstructor(stats) != EmptyTree) stats
- else DefDef(clazz.primaryConstructor, Block(List(), Literal(Constant(())))) :: stats
-
- private def implTemplate(clazz: Symbol, templ: Template): Template = atPos(templ.pos) {
- val templ1 = (
- Template(templ.parents, noSelfType, addMixinConstructorDef(clazz, templ.body map implMemberDef))
- setSymbol clazz.newLocalDummy(templ.pos)
- )
- templ1.changeOwner(templ.symbol.owner -> clazz, templ.symbol -> templ1.symbol)
- templ1
- }
-
- def implClassDefs(trees: List[Tree]): List[Tree] = {
- trees collect {
- case cd: ClassDef if cd.symbol.needsImplClass =>
- val clazz = implClass(cd.symbol).initialize
- ClassDef(clazz, implTemplate(clazz, cd.impl))
- }
+ private def mkAssign(clazz: Symbol, assignSym: Symbol, rhs: Tree): Tree = {
+ val qual = Select(This(clazz), assignSym)
+ if (assignSym.isSetter) Apply(qual, List(rhs))
+ else Assign(qual, rhs)
}
/** Add calls to supermixin constructors
@@ -306,15 +61,16 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure =>
* to tree, which is assumed to be the body of a constructor of class clazz.
*/
private def addMixinConstructorCalls(tree: Tree, clazz: Symbol): Tree = {
- def mixinConstructorCall(impl: Symbol): Tree = atPos(tree.pos) {
- Apply(Select(This(clazz), impl.primaryConstructor), List())
+ def mixinConstructorCall(mc: Symbol): Tree = atPos(tree.pos) {
+ Apply(SuperSelect(clazz, mc.primaryConstructor), Nil)
}
val mixinConstructorCalls: List[Tree] = {
for (mc <- clazz.mixinClasses.reverse
- if mc.hasFlag(lateINTERFACE))
- yield mixinConstructorCall(implClass(mc))
+ if mc.isTrait && mc.primaryConstructor != NoSymbol)
+ yield mixinConstructorCall(mc)
}
tree match {
+
case Block(Nil, expr) =>
// AnyVal constructor - have to provide a real body so the
// jvm doesn't throw a VerifyError. But we can't add the
@@ -331,42 +87,14 @@ abstract class AddInterfaces extends InfoTransform { self: Erasure =>
}
protected val mixinTransformer = new Transformer {
- override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] =
- (super.transformStats(stats, exprOwner) :::
- super.transformStats(implClassDefs(stats), exprOwner))
override def transform(tree: Tree): Tree = {
val sym = tree.symbol
val tree1 = tree match {
- case ClassDef(mods, _, _, impl) if sym.needsImplClass =>
- implClass(sym).initialize // to force lateDEFERRED flags
- copyClassDef(tree)(mods = mods | INTERFACE, impl = ifaceTemplate(impl))
case DefDef(_,_,_,_,_,_) if sym.isClassConstructor && sym.isPrimaryConstructor && sym.owner != ArrayClass =>
deriveDefDef(tree)(addMixinConstructorCalls(_, sym.owner)) // (3)
case Template(parents, self, body) =>
val parents1 = sym.owner.info.parents map (t => TypeTree(t) setPos tree.pos)
treeCopy.Template(tree, parents1, noSelfType, body)
- case This(_) if sym.needsImplClass =>
- val impl = implClass(sym)
- var owner = currentOwner
- while (owner != sym && owner != impl) owner = owner.owner;
- if (owner == impl) This(impl) setPos tree.pos
- else tree
- //TODO what about this commented out code?
-/* !!!
- case Super(qual, mix) =>
- val mix1 = mix
- if (mix == tpnme.EMPTY) mix
- else {
- val ps = enteringErasure {
- sym.info.parents dropWhile (p => p.symbol.name != mix)
- }
- assert(!ps.isEmpty, tree);
- if (ps.head.symbol.needsImplClass) implClass(ps.head.symbol).name
- else mix
- }
- if (sym.needsImplClass) Super(implClass(sym), mix1) setPos tree.pos
- else treeCopy.Super(tree, qual, mix1)
-*/
case _ =>
tree
}
diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
index c29826551b..0fb6213d36 100644
--- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala
+++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala
@@ -21,16 +21,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
val phaseName: String = "cleanup"
/* used in GenBCode: collects ClassDef symbols owning a main(Array[String]) method */
- private var entryPoints: List[Symbol] = null
- def getEntryPoints: List[Symbol] = {
- assert(settings.isBCodeActive, "Candidate Java entry points are collected here only when GenBCode in use.")
- entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages.
- }
-
- override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = {
- entryPoints = if (settings.isBCodeActive) Nil else null;
- super.newPhase(prev)
- }
+ private var entryPoints: List[Symbol] = Nil
+ def getEntryPoints: List[Symbol] = entryPoints sortBy ("" + _.fullName) // For predictably ordered error messages.
protected def newTransformer(unit: CompilationUnit): Transformer =
new CleanUpTransformer(unit)
@@ -49,7 +41,9 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
clearStatics()
val newBody = transformTrees(body)
val templ = deriveTemplate(tree)(_ => transformTrees(newStaticMembers.toList) ::: newBody)
- try addStaticInits(templ, newStaticInits, localTyper) // postprocess to include static ctors
+ try
+ if (newStaticInits.isEmpty) templ
+ else deriveTemplate(templ)(body => staticConstructor(body, localTyper, templ.pos)(newStaticInits.toList) :: body)
finally clearStatics()
}
private def mkTerm(prefix: String): TermName = unit.freshTermName(prefix)
@@ -85,24 +79,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
/* ### CREATING THE METHOD CACHE ### */
- def addStaticVariableToClass(forName: TermName, forType: Type, forInit: Tree, isFinal: Boolean): Symbol = {
- val flags = PRIVATE | STATIC | SYNTHETIC | (
- if (isFinal) FINAL else 0
- )
-
- val varSym = currentClass.newVariable(mkTerm("" + forName), ad.pos, flags.toLong) setInfoAndEnter forType
- if (!isFinal)
- varSym.addAnnotation(VolatileAttr)
-
- val varDef = typedPos(ValDef(varSym, forInit))
- newStaticMembers append transform(varDef)
-
- val varInit = typedPos( REF(varSym) === forInit )
- newStaticInits append transform(varInit)
-
- varSym
- }
-
def addStaticMethodToClass(forBody: (Symbol, Symbol) => Tree): Symbol = {
val methSym = currentClass.newMethod(mkTerm(nme.reflMethodName.toString), ad.pos, STATIC | SYNTHETIC)
val params = methSym.newSyntheticValueParams(List(ClassClass.tpe))
@@ -113,9 +89,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
methSym
}
- def fromTypesToClassArrayLiteral(paramTypes: List[Type]): Tree =
- ArrayValue(TypeTree(ClassClass.tpe), paramTypes map LIT)
-
def reflectiveMethodCache(method: String, paramTypes: List[Type]): Symbol = {
/* Implementation of the cache is as follows for method "def xyz(a: A, b: B)"
(SoftReference so that it does not interfere with classloader garbage collection,
@@ -126,7 +99,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
var reflPoly$Cache: SoftReference[scala.runtime.MethodCache] = new SoftReference(new EmptyMethodCache())
def reflMethod$Method(forReceiver: JClass[_]): JMethod = {
- var methodCache: MethodCache = reflPoly$Cache.find(forReceiver)
+ var methodCache: StructuralCallSite = indy[StructuralCallSite.bootstrap, "(LA;LB;)Ljava/lang/Object;]
if (methodCache eq null) {
methodCache = new EmptyMethodCache
reflPoly$Cache = new SoftReference(methodCache)
@@ -135,41 +108,32 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
if (method ne null)
return method
else {
- method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", reflParams$Cache))
- reflPoly$Cache = new SoftReference(methodCache.add(forReceiver, method))
+ method = ScalaRunTime.ensureAccessible(forReceiver.getMethod("xyz", methodCache.parameterTypes()))
+ methodCache.add(forReceiver, method)
return method
}
}
- */
-
- val reflParamsCacheSym: Symbol =
- addStaticVariableToClass(nme.reflParamsCacheName, arrayType(ClassClass.tpe), fromTypesToClassArrayLiteral(paramTypes), true)
-
- def mkNewPolyCache = gen.mkSoftRef(NEW(TypeTree(EmptyMethodCacheClass.tpe)))
- val reflPolyCacheSym: Symbol = addStaticVariableToClass(nme.reflPolyCacheName, SoftReferenceClass.tpe, mkNewPolyCache, false)
- def getPolyCache = gen.mkCast(fn(REF(reflPolyCacheSym), nme.get), MethodCacheClass.tpe)
+ invokedynamic is used rather than a static field for the cache to support emitting bodies of methods
+ in Java 8 interfaces, which don't support private static fields.
+ */
addStaticMethodToClass((reflMethodSym, forReceiverSym) => {
- val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo MethodCacheClass.tpe
+ val methodCache = reflMethodSym.newVariable(mkTerm("methodCache"), ad.pos) setInfo StructuralCallSite.tpe
val methodSym = reflMethodSym.newVariable(mkTerm("method"), ad.pos) setInfo MethodClass.tpe
+ val dummyMethodType = MethodType(NoSymbol.newSyntheticValueParams(paramTypes), AnyTpe)
BLOCK(
- ValDef(methodCache, getPolyCache),
- IF (REF(methodCache) OBJ_EQ NULL) THEN BLOCK(
- REF(methodCache) === NEW(TypeTree(EmptyMethodCacheClass.tpe)),
- REF(reflPolyCacheSym) === gen.mkSoftRef(REF(methodCache))
- ) ENDIF,
-
- ValDef(methodSym, (REF(methodCache) DOT methodCache_find)(REF(forReceiverSym))),
+ ValDef(methodCache, ApplyDynamic(gen.mkAttributedIdent(StructuralCallSite_dummy), LIT(StructuralCallSite_bootstrap) :: LIT(dummyMethodType) :: Nil).setType(StructuralCallSite.tpe)),
+ ValDef(methodSym, (REF(methodCache) DOT StructuralCallSite_find)(REF(forReceiverSym))),
IF (REF(methodSym) OBJ_NE NULL) .
THEN (Return(REF(methodSym)))
ELSE {
- def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), REF(reflParamsCacheSym)))
- def cacheRHS = ((REF(methodCache) DOT methodCache_add)(REF(forReceiverSym), REF(methodSym)))
+ def methodSymRHS = ((REF(forReceiverSym) DOT Class_getMethod)(LIT(method), (REF(methodCache) DOT StructuralCallSite_getParameterTypes)()))
+ def cacheAdd = ((REF(methodCache) DOT StructuralCallSite_add)(REF(forReceiverSym), REF(methodSym)))
BLOCK(
REF(methodSym) === (REF(currentRun.runDefinitions.ensureAccessibleMethod) APPLY (methodSymRHS)),
- REF(reflPolyCacheSym) === gen.mkSoftRef(cacheRHS),
+ cacheAdd,
Return(REF(methodSym))
)
}
@@ -369,6 +333,8 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
reporter.error(ad.pos, "Cannot resolve overload.")
(Nil, NoType)
}
+ case NoType =>
+ abort(ad.symbol.toString)
}
typedPos {
val sym = currentOwner.newValue(mkTerm("qual"), ad.pos) setInfo qual0.tpe
@@ -404,11 +370,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
}
override def transform(tree: Tree): Tree = tree match {
-
- case _: ClassDef
- if (entryPoints != null) &&
- genBCode.isJavaEntryPoint(tree.symbol, currentUnit)
- =>
+ case _: ClassDef if genBCode.isJavaEntryPoint(tree.symbol, currentUnit) =>
// collecting symbols for entry points here (as opposed to GenBCode where they are used)
// has the advantage of saving an additional pass over all ClassDefs.
entryPoints ::= tree.symbol
@@ -446,7 +408,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
* refinement, where the refinement defines a parameter based on a
* type variable. */
- case tree: ApplyDynamic =>
+ case tree: ApplyDynamic if tree.symbol.owner.isRefinementClass =>
transformApplyDynamic(tree)
/* Some cleanup transformations add members to templates (classes, traits, etc).
@@ -476,46 +438,15 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
/*
* This transformation should identify Scala symbol invocations in the tree and replace them
- * with references to a static member. Also, whenever a class has at least a single symbol invocation
- * somewhere in its methods, a new static member should be created and initialized for that symbol.
- * For instance, say we have a Scala class:
- *
- * class Cls {
- * def someSymbol1 = 'Symbolic1
- * def someSymbol2 = 'Symbolic2
- * def sameSymbol1 = 'Symbolic1
- * val someSymbol3 = 'Symbolic3
- * }
- *
- * After transformation, this class looks like this:
- *
- * class Cls {
- * private <static> var symbol$1: scala.Symbol
- * private <static> var symbol$2: scala.Symbol
- * private <static> var symbol$3: scala.Symbol
- * private val someSymbol3: scala.Symbol
- *
- * private <static> def <clinit> = {
- * symbol$1 = Symbol.apply("Symbolic1")
- * symbol$2 = Symbol.apply("Symbolic2")
- * }
- *
- * private def <init> = {
- * someSymbol3 = symbol$3
- * }
- *
- * def someSymbol1 = symbol$1
- * def someSymbol2 = symbol$2
- * def sameSymbol1 = symbol$1
- * val someSymbol3 = someSymbol3
- * }
+ * with references to a statically cached instance.
*
* The reasoning behind this transformation is the following. Symbols get interned - they are stored
* in a global map which is protected with a lock. The reason for this is making equality checks
* quicker. But calling Symbol.apply, although it does return a unique symbol, accesses a locked object,
* making symbol access slow. To solve this, the unique symbol from the global symbol map in Symbol
- * is accessed only once during class loading, and after that, the unique symbol is in the static
- * member. Hence, it is cheap to both reach the unique symbol and do equality checks on it.
+ * is accessed only once during class loading, and after that, the unique symbol is in the statically
+ * initialized call site returned by invokedynamic. Hence, it is cheap to both reach the unique symbol
+ * and do equality checks on it.
*
* And, finally, be advised - Scala's Symbol literal (scala.Symbol) and the Symbol class of the compiler
* have little in common.
@@ -523,15 +454,7 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
case Apply(fn @ Select(qual, _), (arg @ Literal(Constant(symname: String))) :: Nil)
if treeInfo.isQualifierSafeToElide(qual) && fn.symbol == Symbol_apply && !currentClass.isTrait =>
- def transformApply = {
- // add the symbol name to a map if it's not there already
- val rhs = gen.mkMethodCall(Symbol_apply, arg :: Nil)
- val staticFieldSym = getSymbolStaticField(tree.pos, symname, rhs, tree)
- // create a reference to a static field
- val ntree = typedWithPos(tree.pos)(REF(staticFieldSym))
- super.transform(ntree)
- }
- transformApply
+ super.transform(treeCopy.ApplyDynamic(tree, atPos(fn.pos)(Ident(SymbolLiteral_dummy).setType(SymbolLiteral_dummy.info)), LIT(SymbolLiteral_bootstrap) :: arg :: Nil))
// Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), <tag>)`
// with just `ArrayValue(...).$asInstanceOf[...]`
@@ -548,32 +471,6 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL {
super.transform(tree)
}
- /* Returns the symbol and the tree for the symbol field interning a reference to a symbol 'synmname'.
- * If it doesn't exist, i.e. the symbol is encountered the first time,
- * it creates a new static field definition and initialization and returns it.
- */
- private def getSymbolStaticField(pos: Position, symname: String, rhs: Tree, tree: Tree): Symbol = {
- symbolsStoredAsStatic.getOrElseUpdate(symname, {
- val theTyper = typer.atOwner(tree, currentClass)
-
- // create a symbol for the static field
- val stfieldSym = (
- currentClass.newVariable(mkTerm("symbol$"), pos, PRIVATE | STATIC | SYNTHETIC | FINAL)
- setInfoAndEnter SymbolClass.tpe
- )
-
- // create field definition and initialization
- val stfieldDef = theTyper.typedPos(pos)(ValDef(stfieldSym, rhs))
- val stfieldInit = theTyper.typedPos(pos)(REF(stfieldSym) === rhs)
-
- // add field definition to new defs
- newStaticMembers append stfieldDef
- newStaticInits append stfieldInit
-
- stfieldSym
- })
- }
-
} // CleanUpTransformer
}
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index b2aac587eb..636fb08b89 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -6,8 +6,7 @@
package scala.tools.nsc
package transform
-import scala.collection.{ mutable, immutable }
-import scala.collection.mutable.ListBuffer
+import scala.collection.mutable
import scala.reflect.internal.util.ListOfNil
import symtab.Flags._
@@ -28,7 +27,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
private val ctorParams: mutable.Map[Symbol, List[Symbol]] = perRunCaches.newMap[Symbol, List[Symbol]]()
class ConstructorTransformer(unit: CompilationUnit) extends Transformer {
-
/*
* Inspect for obvious out-of-order initialization; concrete, eager vals or vars, declared in this class,
* for which a reference to the member precedes its definition.
@@ -75,7 +73,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
override def transform(tree: Tree): Tree = {
tree match {
- case cd @ ClassDef(mods0, name0, tparams0, impl0) if !cd.symbol.isInterface && !isPrimitiveValueClass(cd.symbol) =>
+ case cd @ ClassDef(mods0, name0, tparams0, impl0) if !isPrimitiveValueClass(cd.symbol) && cd.symbol.primaryConstructor != NoSymbol =>
if(cd.symbol eq AnyValClass) {
cd
}
@@ -121,15 +119,15 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* What trees can be visited at this point?
* To recap, by the time the constructors phase runs, local definitions have been hoisted out of their original owner.
* Moreover, by the time elision is about to happen, the `intoConstructors` rewriting
- * of template-level statements has taken place (the resulting trees can be found in `constrStatBuf`).
+ * of template-level statements has taken place (the resulting trees can be found in `constructorStats`).
*
* That means:
*
- * - nested classes are to be found in `defBuf`
+ * - nested classes are to be found in `defs`
*
- * - value and method definitions are also in `defBuf` and none of them contains local methods or classes.
+ * - value and method definitions are also in `defs` and none of them contains local methods or classes.
*
- * - auxiliary constructors are to be found in `auxConstructorBuf`
+ * - auxiliary constructors are to be found in `auxConstructors`
*
* Coming back to the question which trees may contain accesses:
*
@@ -148,62 +146,56 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* (the primary constructor) into a dedicated synthetic method that an anon-closure may invoke, as required by DelayedInit.
*
*/
- private trait OmittablesHelper { self: TemplateTransformer =>
-
- /*
- * Initially populated with all elision candidates.
- * Trees are traversed, and those candidates are removed which are actually needed.
- * After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls.
- */
- val omittables = mutable.Set.empty[Symbol]
-
- def populateOmittables() {
-
- omittables.clear()
-
- if(isDelayedInitSubclass) {
- return
- }
-
- def isParamCandidateForElision(sym: Symbol) = (sym.isParamAccessor && sym.isPrivateLocal)
- def isOuterCandidateForElision(sym: Symbol) = (sym.isOuterAccessor && sym.owner.isEffectivelyFinal && !sym.isOverridingSymbol)
-
- val paramCandidatesForElision: Set[ /*Field*/ Symbol] = (clazz.info.decls.toSet filter isParamCandidateForElision)
- val outerCandidatesForElision: Set[ /*Method*/ Symbol] = (clazz.info.decls.toSet filter isOuterCandidateForElision)
-
- omittables ++= paramCandidatesForElision
- omittables ++= outerCandidatesForElision
-
- val bodyOfOuterAccessor: Map[Symbol, DefDef] =
- defBuf.collect { case dd: DefDef if outerCandidatesForElision(dd.symbol) => dd.symbol -> dd }.toMap
+ private trait OmittablesHelper {
+ def computeOmittableAccessors(clazz: Symbol, defs: List[Tree], auxConstructors: List[Tree]): Set[Symbol] = {
+ val decls = clazz.info.decls.toSet
+ val isEffectivelyFinal = clazz.isEffectivelyFinal
+
+ // Initially populated with all elision candidates.
+ // Trees are traversed, and those candidates are removed which are actually needed.
+ // After that, `omittables` doesn't shrink anymore: each symbol it contains can be unlinked from clazz.info.decls.
+ //
+ // Note: elision of outer reference is based on a class-wise analysis, if a class might have subclasses,
+ // it doesn't work. For example, `LocalParent` retains the outer reference in:
+ //
+ // class Outer { def test = {class LocalParent; class LocalChild extends LocalParent } }
+ //
+ // See run/t9408.scala for related test cases.
+ def omittableParamAcc(sym: Symbol) = sym.isParamAccessor && sym.isPrivateLocal
+ def omittableOuterAcc(sym: Symbol) = isEffectivelyFinal && sym.isOuterAccessor && !sym.isOverridingSymbol
+ val omittables = mutable.Set.empty[Symbol] ++ (decls filter (sym => omittableParamAcc(sym) || omittableOuterAcc(sym))) // the closure only captures isEffectivelyFinal
// no point traversing further once omittables is empty, all candidates ruled out already.
object detectUsages extends Traverser {
- private def markUsage(sym: Symbol) {
- omittables -= debuglogResult("omittables -= ")(sym)
- // recursive call to mark as needed the field supporting the outer-accessor-method.
- bodyOfOuterAccessor get sym foreach (this traverse _.rhs)
- }
- override def traverse(tree: Tree): Unit = if (omittables.nonEmpty) {
- def sym = tree.symbol
- tree match {
- // don't mark as "needed" the field supporting this outer-accessor, ie not just yet.
- case _: DefDef if outerCandidatesForElision(sym) => ()
- case _: Select if omittables(sym) => markUsage(sym) ; super.traverse(tree)
- case _ => super.traverse(tree)
+ lazy val bodyOfOuterAccessor = defs.collect{ case dd: DefDef if omittableOuterAcc(dd.symbol) => dd.symbol -> dd.rhs }.toMap
+
+ override def traverse(tree: Tree): Unit =
+ if (omittables.nonEmpty) {
+ def sym = tree.symbol
+ tree match {
+ case _: DefDef if (sym.owner eq clazz) && omittableOuterAcc(sym) => // don't mark as "needed" the field supporting this outer-accessor (not just yet)
+ case _: Select if omittables(sym) => omittables -= sym // mark usage
+ bodyOfOuterAccessor get sym foreach traverse // recurse to mark as needed the field supporting the outer-accessor-method
+ super.traverse(tree)
+ case _ => super.traverse(tree)
+ }
}
- }
- def walk(xs: Seq[Tree]) = xs.iterator foreach traverse
}
- if (omittables.nonEmpty) {
- detectUsages walk defBuf
- detectUsages walk auxConstructorBuf
- }
- }
- def mustBeKept(sym: Symbol) = !omittables(sym)
+ if (omittables.nonEmpty)
+ (defs.iterator ++ auxConstructors.iterator) foreach detectUsages.traverse
+
+ omittables.toSet
+ }
} // OmittablesHelper
+ trait ConstructorTransformerBase {
+ def unit: CompilationUnit
+ def impl: Template
+ def clazz: Symbol
+ def localTyper: analyzer.Typer
+ }
+
/*
* TemplateTransformer rewrites DelayedInit subclasses.
* The list of statements that will end up in the primary constructor can be split into:
@@ -248,10 +240,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* @return the DefDef for (c) above
*
* */
- private trait DelayedInitHelper { self: TemplateTransformer =>
-
+ private trait DelayedInitHelper extends ConstructorTransformerBase {
private def delayedEndpointDef(stats: List[Tree]): DefDef = {
-
val methodName = currentUnit.freshTermName("delayedEndpoint$" + clazz.fullNameAsName('$').toString + "$")
val methodSym = clazz.newMethod(methodName, impl.pos, SYNTHETIC | FINAL)
methodSym setInfoAndEnter MethodType(Nil, UnitTpe)
@@ -310,36 +300,30 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
satelliteClass.asInstanceOf[ClassDef]
}
- private def delayedInitCall(closure: Tree) = localTyper.typedPos(impl.pos) {
- gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(closure.symbol.tpe, This(clazz))))
- }
+ /** For a DelayedInit subclass, wrap remainingConstrStats into a DelayedInit closure. */
+ def delayedInitDefsAndConstrStats(defs: List[Tree], remainingConstrStats: List[Tree]): (List[Tree], List[Tree]) = {
+ val delayedHook = delayedEndpointDef(remainingConstrStats)
+ val delayedHookSym = delayedHook.symbol.asInstanceOf[MethodSymbol]
- def rewriteDelayedInit() {
- /* XXX This is not correct: remainingConstrStats.nonEmpty excludes too much,
- * but excluding it includes too much. The constructor sequence being mimicked
- * needs to be reproduced with total fidelity.
- *
- * See test case files/run/bug4680.scala, the output of which is wrong in many
- * particulars.
- */
- val needsDelayedInit = (isDelayedInitSubclass && remainingConstrStats.nonEmpty)
-
- if (needsDelayedInit) {
- val delayedHook: DefDef = delayedEndpointDef(remainingConstrStats)
- defBuf += delayedHook
- val hookCallerClass = {
- // transform to make the closure-class' default constructor assign the outer instance to its param-accessor field.
- val drillDown = new ConstructorTransformer(unit)
- drillDown transform delayedInitClosure(delayedHook.symbol.asInstanceOf[MethodSymbol])
- }
- defBuf += hookCallerClass
- remainingConstrStats = delayedInitCall(hookCallerClass) :: Nil
+ // transform to make the closure-class' default constructor assign the outer instance to its param-accessor field.
+ val hookCallerClass = (new ConstructorTransformer(unit)) transform delayedInitClosure(delayedHookSym)
+ val delayedInitCall = localTyper.typedPos(impl.pos) {
+ gen.mkMethodCall(This(clazz), delayedInitMethod, Nil, List(New(hookCallerClass.symbol.tpe, This(clazz))))
}
+
+ (List(delayedHook, hookCallerClass), List(delayedInitCall))
}
} // DelayedInitHelper
- private trait GuardianOfCtorStmts { self: TemplateTransformer =>
+ private trait GuardianOfCtorStmts extends ConstructorTransformerBase {
+ def primaryConstrParams: List[Symbol]
+ def usesSpecializedField: Boolean
+
+ lazy val hasSpecializedFieldsSym = clazz.info.decl(nme.SPECIALIZED_INSTANCE)
+ // The constructor of a non-specialized class that has specialized subclasses
+ // should use `q"${hasSpecializedFieldsSym}()"` to guard the initialization of specialized fields.
+ lazy val guardSpecializedFieldInit = (hasSpecializedFieldsSym != NoSymbol) && !clazz.hasFlag(SPECIALIZED)
/* Return a single list of statements, merging the generic class constructor with the
* specialized stats. The original statements are retyped in the current class, and
@@ -347,7 +331,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* `specializedStats` are replaced by the specialized assignment.
*/
private def mergeConstructors(genericClazz: Symbol, originalStats: List[Tree], specializedStats: List[Tree]): List[Tree] = {
- val specBuf = new ListBuffer[Tree]
+ val specBuf = new mutable.ListBuffer[Tree]
specBuf ++= specializedStats
def specializedAssignFor(sym: Symbol): Option[Tree] =
@@ -375,7 +359,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
}
log("merging: " + originalStats.mkString("\n") + "\nwith\n" + specializedStats.mkString("\n"))
- val res = for (s <- originalStats; stat = s.duplicate) yield {
+ for (s <- originalStats; stat = s.duplicate) yield {
log("merge: looking at " + stat)
val stat1 = stat match {
case Assign(sel @ Select(This(_), field), _) =>
@@ -388,9 +372,9 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
}
if (stat1 eq stat) {
- assert(ctorParams(genericClazz).length == constrInfo.constrParams.length)
+ assert(ctorParams(genericClazz).length == primaryConstrParams.length)
// this is just to make private fields public
- (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), constrInfo.constrParams, null, true))(stat1)
+ (new specializeTypes.ImplementationAdapter(ctorParams(genericClazz), primaryConstrParams, null, true))(stat1)
val stat2 = rewriteArrayUpdate(stat1)
// statements coming from the original class need retyping in the current context
@@ -405,9 +389,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
} else
stat1
}
- if (specBuf.nonEmpty)
- println("residual specialized constructor statements: " + specBuf)
- res
+// if (specBuf.nonEmpty)
+// println("residual specialized constructor statements: " + specBuf)
}
/* Add an 'if' around the statements coming after the super constructor. This
@@ -427,16 +410,16 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// postfix = postfix.tail
// }
- if (shouldGuard && usesSpecializedField && stats.nonEmpty) {
+ if (guardSpecializedFieldInit && usesSpecializedField && stats.nonEmpty) {
// save them for duplication in the specialized subclass
guardedCtorStats(clazz) = stats
- ctorParams(clazz) = constrInfo.constrParams
+ ctorParams(clazz) = primaryConstrParams
val tree =
If(
Apply(
CODE.NOT (
- Apply(gen.mkAttributedRef(specializedFlag), List())),
+ Apply(gen.mkAttributedRef(hasSpecializedFieldsSym), List())),
List()),
Block(stats, Literal(Constant(()))),
EmptyTree)
@@ -464,34 +447,23 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
with OmittablesHelper
with GuardianOfCtorStmts {
- val clazz = impl.symbol.owner // the transformed class
- val stats = impl.body // the transformed template body
- val localTyper = typer.atOwner(impl, clazz)
-
- val specializedFlag: Symbol = clazz.info.decl(nme.SPECIALIZED_INSTANCE)
- val shouldGuard = (specializedFlag != NoSymbol) && !clazz.hasFlag(SPECIALIZED)
-
- val isDelayedInitSubclass = (clazz isSubClass DelayedInitClass)
-
- case class ConstrInfo(
- constr: DefDef, // The primary constructor
- constrParams: List[Symbol], // ... and its parameters
- constrBody: Block // ... and its body
- )
- // decompose primary constructor into the three entities above.
- val constrInfo: ConstrInfo = {
- val ddef = (stats find (_.symbol.isPrimaryConstructor))
- ddef match {
- case Some(ddef @ DefDef(_, _, _, List(vparams), _, rhs @ Block(_, _))) =>
- ConstrInfo(ddef, vparams map (_.symbol), rhs)
- case x =>
- abort("no constructor in template: impl = " + impl)
- }
+ val clazz = impl.symbol.owner // the transformed class
+ val localTyper = typer.atOwner(impl, clazz)
+
+ val isDelayedInitSubclass = clazz isSubClass DelayedInitClass
+
+ private val stats = impl.body // the transformed template body
+
+ // find and dissect primary constructor
+ private val (primaryConstr, _primaryConstrParams, primaryConstrBody) = stats collectFirst {
+ case dd@DefDef(_, _, _, vps :: Nil, _, rhs: Block) if dd.symbol.isPrimaryConstructor || dd.symbol.isMixinConstructor => (dd, vps map (_.symbol), rhs)
+ } getOrElse {
+ abort("no constructor in template: impl = " + impl)
}
- import constrInfo._
- // The parameter accessor fields which are members of the class
- val paramAccessors = clazz.constrParamAccessors
+
+ def primaryConstrParams = _primaryConstrParams
+ def usesSpecializedField = intoConstructor.usesSpecializedField
// The constructor parameter corresponding to an accessor
def parameter(acc: Symbol): Symbol = parameterNamed(acc.unexpandedName.getterName)
@@ -501,27 +473,26 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
def parameterNamed(name: Name): Symbol = {
def matchesName(param: Symbol) = param.name == name || param.name.startsWith(name + nme.NAME_JOIN_STRING)
- (constrParams filter matchesName) match {
- case Nil => abort(name + " not in " + constrParams)
+ primaryConstrParams filter matchesName match {
+ case Nil => abort(name + " not in " + primaryConstrParams)
case p :: _ => p
}
}
- /*
- * `usesSpecializedField` makes a difference in deciding whether constructor-statements
- * should be guarded in a `shouldGuard` class, ie in a class that's the generic super-class of
- * one or more specialized sub-classes.
- *
- * Given that `usesSpecializedField` isn't read for any other purpose than the one described above,
- * we skip setting `usesSpecializedField` in case the current class isn't `shouldGuard` to start with.
- * That way, trips to a map in `specializeTypes` are saved.
- */
- var usesSpecializedField: Boolean = false
-
// A transformer for expressions that go into the constructor
- private class IntoCtorTransformer extends Transformer {
-
- private def isParamRef(sym: Symbol) = (sym.isParamAccessor && sym.owner == clazz)
+ object intoConstructor extends Transformer {
+ /*
+ * `usesSpecializedField` makes a difference in deciding whether constructor-statements
+ * should be guarded in a `guardSpecializedFieldInit` class, ie in a class that's the generic super-class of
+ * one or more specialized sub-classes.
+ *
+ * Given that `usesSpecializedField` isn't read for any other purpose than the one described above,
+ * we skip setting `usesSpecializedField` in case the current class isn't `guardSpecializedFieldInit` to start with.
+ * That way, trips to a map in `specializeTypes` are saved.
+ */
+ var usesSpecializedField: Boolean = false
+
+ private def isParamRef(sym: Symbol) = sym.isParamAccessor && sym.owner == clazz
// Terminology: a stationary location is never written after being read.
private def isStationaryParamRef(sym: Symbol) = (
@@ -530,8 +501,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
!sym.isSetter
)
- private def possiblySpecialized(s: Symbol) = specializeTypes.specializedTypeVars(s).nonEmpty
-
/*
* whether `sym` denotes a param-accessor (ie a field) that fulfills all of:
* (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and
@@ -540,16 +509,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
* (b.2) the constructor in the specialized (sub-)class.
* (c) isn't part of a DelayedInit subclass.
*/
- private def canBeSupplanted(sym: Symbol) = (!isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym))
+ private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !specializeTypes.possiblySpecialized(sym)
override def transform(tree: Tree): Tree = tree match {
-
case Apply(Select(This(_), _), List()) =>
// references to parameter accessor methods of own class become references to parameters
// outer accessors become references to $outer parameter
- if (canBeSupplanted(tree.symbol))
+ if (clazz.isTrait)
+ super.transform(tree)
+ else if (canBeSupplanted(tree.symbol))
gen.mkAttributedIdent(parameter(tree.symbol.accessed)) setPos tree.pos
- else if (tree.symbol.outerSource == clazz && !clazz.isImplClass)
+ else if (tree.symbol.outerSource == clazz)
gen.mkAttributedIdent(parameterNamed(nme.OUTER)) setPos tree.pos
else
super.transform(tree)
@@ -558,8 +528,8 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
// references to parameter accessor field of own class become references to parameters
gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos
- case Select(_, _) if shouldGuard => // reasoning behind this guard in the docu of `usesSpecializedField`
- if (possiblySpecialized(tree.symbol)) {
+ case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField`
+ if (specializeTypes.possiblySpecialized(tree.symbol)) {
usesSpecializedField = true
}
super.transform(tree)
@@ -568,23 +538,17 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
super.transform(tree)
}
- }
-
- private val intoConstructorTransformer = new IntoCtorTransformer
-
- // Move tree into constructor, take care of changing owner from `oldowner` to constructor symbol
- def intoConstructor(oldowner: Symbol, tree: Tree) =
- intoConstructorTransformer transform tree.changeOwner(oldowner -> constr.symbol)
-
- // Should tree be moved in front of super constructor call?
- def canBeMoved(tree: Tree) = tree match {
- case ValDef(mods, _, _, _) => (mods hasFlag PRESUPER | PARAMACCESSOR)
- case _ => false
+ // Move tree into constructor, take care of changing owner from `oldOwner` to `newOwner` (the primary constructor symbol)
+ def apply(oldOwner: Symbol, newOwner: Symbol)(tree: Tree) =
+ if (tree eq EmptyTree) tree
+ else transform(tree.changeOwner(oldOwner -> newOwner))
}
// Create an assignment to class field `to` with rhs `from`
def mkAssign(to: Symbol, from: Tree): Tree =
- localTyper.typedPos(to.pos) { Assign(Select(This(clazz), to), from) }
+ localTyper.typedPos(to.pos) {
+ Assign(Select(This(clazz), to), from)
+ }
// Create code to copy parameter to parameter accessor field.
// If parameter is $outer, check that it is not null so that we NPE
@@ -594,139 +558,180 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL {
val result = mkAssign(to, Ident(from))
if (from.name != nme.OUTER ||
- from.tpe.typeSymbol.isPrimitiveValueClass) result
+ from.tpe.typeSymbol.isPrimitiveValueClass) result
else localTyper.typedPos(to.pos) {
// `throw null` has the same effect as `throw new NullPointerException`, see JVM spec on instruction `athrow`
- IF (from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result
+ IF(from OBJ_EQ NULL) THEN Throw(gen.mkZero(ThrowableTpe)) ELSE result
}
}
- // The list of definitions that go into class
- val defBuf = new ListBuffer[Tree]
-
- // The auxiliary constructors, separate from the defBuf since they should
- // follow the primary constructor
- val auxConstructorBuf = new ListBuffer[Tree]
-
- // The list of statements that go into the constructor after and including the superclass constructor call
- val constrStatBuf = new ListBuffer[Tree]
-
- // The list of early initializer statements that go into constructor before the superclass constructor call
- val constrPrefixBuf = new ListBuffer[Tree]
-
- // The early initialized field definitions of the class (these are the class members)
- val presupers = treeInfo.preSuperFields(stats)
-
- // The list of statements that go into the class initializer
- val classInitStatBuf = new ListBuffer[Tree]
-
- // generate code to copy pre-initialized fields
- for (stat <- constrBody.stats) {
- constrStatBuf += stat
- stat match {
- case ValDef(mods, name, _, _) if (mods hasFlag PRESUPER) =>
- // stat is the constructor-local definition of the field value
- val fields = presupers filter (_.getterName == name)
- assert(fields.length == 1)
- val to = fields.head.symbol
- if (!to.tpe.isInstanceOf[ConstantType])
- constrStatBuf += mkAssign(to, Ident(stat.symbol))
- case _ =>
- }
- }
+ // Constant typed vals are not memoized.
+ def memoizeValue(sym: Symbol) = !sym.info.resultType.isInstanceOf[ConstantType]
+
+ /** Triage definitions and statements in this template into the following categories.
+ * The primary constructor is treated separately, as it is assembled in part from these pieces.
+ *
+ * - `defs`: definitions that go into class
+ * - `auxConstrs`: auxiliary constructors, separate from the defs as they should follow the primary constructor
+ * - `constrPrefix`: early initializer statements that go into constructor before the superclass constructor call
+ * - `constrStats`: statements that go into the constructor after and including the superclass constructor call
+ * - `classInitStats`: statements that go into the class initializer
+ */
+ def triageStats = {
+ val defBuf, auxConstructorBuf, constrPrefixBuf, constrStatBuf, classInitStatBuf = new mutable.ListBuffer[Tree]
+
+ // The early initialized field definitions of the class (these are the class members)
+ val presupers = treeInfo.preSuperFields(stats)
+
+ // generate code to copy pre-initialized fields
+ for (stat <- primaryConstrBody.stats) {
+ constrStatBuf += stat
+ stat match {
+ case ValDef(mods, name, _, _) if mods.hasFlag(PRESUPER) =>
+ // stat is the constructor-local definition of the field value
+ val fields = presupers filter (_.getterName == name)
+ assert(fields.length == 1, s"expected exactly one field by name $name in $presupers of $clazz's early initializers")
+ val to = fields.head.symbol
- // Triage all template definitions to go into defBuf/auxConstructorBuf, constrStatBuf, or constrPrefixBuf.
- for (stat <- stats) stat match {
- case DefDef(_,_,_,_,_,rhs) =>
- // methods with constant result type get literals as their body
- // all methods except the primary constructor go into template
- stat.symbol.tpe match {
- case MethodType(List(), tp @ ConstantType(c)) =>
- defBuf += deriveDefDef(stat)(Literal(c) setPos _.pos setType tp)
+ if (memoizeValue(to)) constrStatBuf += mkAssign(to, Ident(stat.symbol))
case _ =>
- if (stat.symbol.isPrimaryConstructor) ()
- else if (stat.symbol.isConstructor) auxConstructorBuf += stat
- else defBuf += stat
}
- case ValDef(mods, _, _, rhs) if !mods.hasStaticFlag =>
- // val defs with constant right-hand sides are eliminated.
- // for all other val defs, an empty valdef goes into the template and
- // the initializer goes as an assignment into the constructor
- // if the val def is an early initialized or a parameter accessor, it goes
- // before the superclass constructor call, otherwise it goes after.
- // Lazy vals don't get the assignment in the constructor.
- if (!stat.symbol.tpe.isInstanceOf[ConstantType]) {
- if (rhs != EmptyTree && !stat.symbol.isLazy) {
- val rhs1 = intoConstructor(stat.symbol, rhs)
- (if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign(
- stat.symbol, rhs1)
+ }
+
+ for (stat <- stats) {
+ val statSym = stat.symbol
+
+ // Move the RHS of a ValDef to the appropriate part of the ctor.
+ // If the val is an early initialized or a parameter accessor,
+ // it goes before the superclass constructor call, otherwise it goes after.
+ // A lazy val's effect is not moved to the constructor, as it is delayed.
+ // Returns `true` when a `ValDef` is needed.
+ def moveEffectToCtor(mods: Modifiers, rhs: Tree, assignSym: Symbol): Unit = {
+ val initializingRhs =
+ if ((assignSym eq NoSymbol) || statSym.isLazy) EmptyTree // not memoized, or effect delayed (for lazy val)
+ else if (!mods.hasStaticFlag) intoConstructor(statSym, primaryConstr.symbol)(rhs)
+ else rhs
+
+ if (initializingRhs ne EmptyTree) {
+ val initPhase =
+ if (mods hasFlag STATIC) classInitStatBuf
+ else if (mods hasFlag PRESUPER | PARAMACCESSOR) constrPrefixBuf
+ else constrStatBuf
+
+ initPhase += mkAssign(assignSym, initializingRhs)
}
- defBuf += deriveValDef(stat)(_ => EmptyTree)
}
- case ValDef(_, _, _, rhs) =>
- // Add static initializer statements to classInitStatBuf and remove the rhs from the val def.
- classInitStatBuf += mkAssign(stat.symbol, rhs)
- defBuf += deriveValDef(stat)(_ => EmptyTree)
-
- case ClassDef(_, _, _, _) =>
- // classes are treated recursively, and left in the template
- defBuf += new ConstructorTransformer(unit).transform(stat)
- case _ =>
- // all other statements go into the constructor
- constrStatBuf += intoConstructor(impl.symbol, stat)
- }
- populateOmittables()
-
- // Initialize all parameters fields that must be kept.
- val paramInits = paramAccessors filter mustBeKept map { acc =>
- // Check for conflicting symbol amongst parents: see bug #1960.
- // It would be better to mangle the constructor parameter name since
- // it can only be used internally, but I think we need more robust name
- // mangling before we introduce more of it.
- val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait)
- if (conflict ne NoSymbol)
- reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString))
+ stat match {
+ // recurse on class definition, store in defBuf
+ case _: ClassDef if !stat.symbol.isInterface => defBuf += new ConstructorTransformer(unit).transform(stat)
+
+ // Triage methods -- they all end up in the template --
+ // regular ones go to `defBuf`, secondary contructors go to `auxConstructorBuf`.
+ // The primary constructor is dealt with separately (we're massaging it here).
+ case _: DefDef if statSym.isPrimaryConstructor || statSym.isMixinConstructor => ()
+ case _: DefDef if statSym.isConstructor => auxConstructorBuf += stat
+ case _: DefDef => defBuf += stat
+
+ // If a val needs a field, an empty valdef goes into the template.
+ // Except for lazy and ConstantTyped vals, the field is initialized by an assignment in:
+ // - the class initializer (static),
+ // - the constructor, before the super call (early initialized or a parameter accessor),
+ // - the constructor, after the super call (regular val).
+ case ValDef(mods, _, _, rhs) =>
+ if (rhs ne EmptyTree) {
+ val emitField = memoizeValue(statSym)
+ moveEffectToCtor(mods, rhs, if (emitField) statSym else NoSymbol)
+ if (emitField) defBuf += deriveValDef(stat)(_ => EmptyTree)
+ } else defBuf += stat
+
+ // all other statements go into the constructor
+ case _ => constrStatBuf += intoConstructor(impl.symbol, primaryConstr.symbol)(stat)
+ }
+ }
- copyParam(acc, parameter(acc))
+ (defBuf.toList, auxConstructorBuf.toList, constrPrefixBuf.toList, constrStatBuf.toList, classInitStatBuf.toList)
}
- /* Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest) */
- def splitAtSuper(stats: List[Tree]) = {
- def isConstr(tree: Tree): Boolean = tree match {
- case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks
- case _ => (tree.symbol ne null) && tree.symbol.isConstructor
+ def transformed = {
+ val (defs, auxConstructors, constructorPrefix, constructorStats, classInitStats) = triageStats
+
+ // omit unused outers
+ val omittableAccessor: Set[Symbol] =
+ if (isDelayedInitSubclass) Set.empty
+ else computeOmittableAccessors(clazz, defs, auxConstructors)
+
+ // TODO: this should omit fields for non-memoized (constant-typed, unit-typed vals need no storage --
+ // all the action is in the getter)
+ def omittableSym(sym: Symbol) = omittableAccessor(sym)
+ def omittableStat(stat: Tree) = omittableSym(stat.symbol)
+
+ // The parameter accessor fields which are members of the class
+ val paramAccessors = clazz.constrParamAccessors
+
+ // Initialize all parameters fields that must be kept.
+ val paramInits = paramAccessors filterNot omittableSym map { acc =>
+ // Check for conflicting symbol amongst parents: see bug #1960.
+ // It would be better to mangle the constructor parameter name since
+ // it can only be used internally, but I think we need more robust name
+ // mangling before we introduce more of it.
+ val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => s.isGetter && !s.isOuterField && s.enclClass.isTrait)
+ if (conflict ne NoSymbol)
+ reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString))
+
+ copyParam(acc, parameter(acc))
}
- val (pre, rest0) = stats span (!isConstr(_))
- val (supercalls, rest) = rest0 span (isConstr(_))
- (pre ::: supercalls, rest)
- }
-
- val (uptoSuperStats, remainingConstrStats0) = splitAtSuper(constrStatBuf.toList)
- var remainingConstrStats = remainingConstrStats0
- rewriteDelayedInit()
-
- // Assemble final constructor
- defBuf += deriveDefDef(constr)(_ =>
- treeCopy.Block(
- constrBody,
- paramInits ::: constrPrefixBuf.toList ::: uptoSuperStats :::
- guardSpecializedInitializer(remainingConstrStats),
- constrBody.expr))
-
- // Followed by any auxiliary constructors
- defBuf ++= auxConstructorBuf
-
- // Unlink all fields that can be dropped from class scope
- for (sym <- clazz.info.decls ; if !mustBeKept(sym))
- clazz.info.decls unlink sym
-
- // Eliminate all field definitions that can be dropped from template
- val templateWithoutOmittables: Template = deriveTemplate(impl)(_ => defBuf.toList filter (stat => mustBeKept(stat.symbol)))
- // Add the static initializers
- val transformed: Template = addStaticInits(templateWithoutOmittables, classInitStatBuf, localTyper)
+ // Return a pair consisting of (all statements up to and including superclass and trait constr calls, rest)
+ def splitAtSuper(stats: List[Tree]) = {
+ def isConstr(tree: Tree): Boolean = tree match {
+ case Block(_, expr) => isConstr(expr) // SI-6481 account for named argument blocks
+ case _ => (tree.symbol ne null) && tree.symbol.isConstructor
+ }
+ val (pre, rest0) = stats span (!isConstr(_))
+ val (supercalls, rest) = rest0 span (isConstr(_))
+ (pre ::: supercalls, rest)
+ }
+ val (uptoSuperStats, remainingConstrStats) = splitAtSuper(constructorStats)
+
+ /* TODO: XXX This condition (`isDelayedInitSubclass && remainingConstrStats.nonEmpty`) is not correct:
+ * remainingConstrStats.nonEmpty excludes too much,
+ * but excluding it includes too much. The constructor sequence being mimicked
+ * needs to be reproduced with total fidelity.
+ *
+ * See test case files/run/bug4680.scala, the output of which is wrong in many
+ * particulars.
+ */
+ val (delayedHookDefs, remainingConstrStatsDelayedInit) =
+ if (isDelayedInitSubclass && remainingConstrStats.nonEmpty) delayedInitDefsAndConstrStats(defs, remainingConstrStats)
+ else (Nil, remainingConstrStats)
+
+ // Assemble final constructor
+ val primaryConstructor = deriveDefDef(primaryConstr)(_ => {
+ treeCopy.Block(
+ primaryConstrBody,
+ paramInits ::: constructorPrefix ::: uptoSuperStats ::: guardSpecializedInitializer(remainingConstrStatsDelayedInit),
+ primaryConstrBody.expr)
+ })
+
+ val constructors = primaryConstructor :: auxConstructors
+
+ // Unlink all fields that can be dropped from class scope
+ // Iterating on toList is cheaper (decls.filter does a toList anyway)
+ val decls = clazz.info.decls
+ decls.toList.filter(omittableSym).foreach(decls.unlink)
+
+ // Eliminate all field/accessor definitions that can be dropped from template
+ // We never eliminate delayed hooks or the constructors, so, only filter `defs`.
+ val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors
+
+ // Add the static initializers
+ if (classInitStats.isEmpty) deriveTemplate(impl)(_ => prunedStats)
+ else {
+ val staticCtor = staticConstructor(prunedStats, localTyper, impl.pos)(classInitStats)
+ deriveTemplate(impl)(_ => staticCtor :: prunedStats)
+ }
+ }
} // TemplateTransformer
-
}
diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
index 8e323de623..d350ca8e17 100644
--- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
+++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala
@@ -4,35 +4,21 @@ package transform
import symtab._
import Flags._
import scala.collection._
-import scala.language.postfixOps
-import scala.reflect.internal.Symbols
-import scala.collection.mutable.LinkedHashMap
/**
- * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes
- * or to a tree that will be convereted to invokedynamic by the JVM 1.8+ backend.
- *
- * The main assumption it makes is that a lambda {args => body} has been turned into
- * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda.
- * Currently Uncurry is responsible for that transformation.
- *
- * From a lambda, Delambdafy will create:
- *
- * Under -target:jvm-1.7 and below:
- *
- * 1) a new top level class that
- a) has fields and a constructor taking the captured environment (including possibly the "this"
- * reference)
- * b) an apply method that calls the target method
- * c) if needed a bridge method for the apply method
- * 2) an instantiation of the newly created class which replaces the lambda
- *
- * Under -target:jvm-1.8 with GenBCode:
- *
- * 1) An application of the captured arguments to a fictional symbol representing the lambda factory.
- * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
- * The captured arguments include `this` if `liftedBody` is unable to be made STATIC.
- */
+ * This transformer is responsible for preparing Function nodes for runtime,
+ * by translating to a tree that will be converted to an invokedynamic by the backend.
+ *
+ * The main assumption it makes is that a Function {args => body} has been turned into
+ * {args => liftedBody()} where lifted body is a top level method that implements the body of the function.
+ * Currently Uncurry is responsible for that transformation.
+ *
+ * From this shape of Function, Delambdafy will create:
+ *
+ * An application of the captured arguments to a fictional symbol representing the lambda factory.
+ * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`.
+ * The captured arguments include `this` if `liftedBody` is unable to be made STATIC.
+ */
abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer {
import global._
import definitions._
@@ -42,6 +28,19 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
/** the following two members override abstract members in Transform */
val phaseName: String = "delambdafy"
+ final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol)
+
+ /**
+ * Get the symbol of the target lifted lambda body method from a function. I.e. if
+ * the function is {args => anonfun(args)} then this method returns anonfun's symbol
+ */
+ private def targetMethod(fun: Function): Symbol = fun match {
+ case Function(_, Apply(target, _)) => target.symbol
+ case _ =>
+ // any other shape of Function is unexpected at this point
+ abort(s"could not understand function with tree $fun")
+ }
+
override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = {
if (settings.Ydelambdafy.value == "method") new Phase(prev)
else new SkipPhase(prev)
@@ -54,433 +53,217 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
protected def newTransformer(unit: CompilationUnit): Transformer =
new DelambdafyTransformer(unit)
- class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter {
- private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil
+ class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
+ // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it
+ // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes?
+ private[this] lazy val methodReferencesThis: Set[Symbol] =
+ (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body)
+
+ private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = {
+ val pos = fun.pos
+ val allCapturedArgRefs = {
+ // find which variables are free in the lambda because those are captures that need to be
+ // passed into the constructor of the anonymous function class
+ val captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture =>
+ gen.mkAttributedRef(capture) setPos pos
+ ).toList
+
+ if (target hasFlag STATIC) captureArgs // no `this` reference needed
+ else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs
+ }
+ // Create a symbol representing a fictional lambda factory method that accepts the captured
+ // arguments and returns the SAM type.
+ val msym = {
+ val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, pos, ARTIFACT)
+ val capturedParams = meth.newSyntheticValueParams(allCapturedArgRefs.map(_.tpe))
+ meth.setInfo(MethodType(capturedParams, fun.tpe))
+ }
- val typer = localTyper
+ // We then apply this symbol to the captures.
+ val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs))
- // we need to know which methods refer to the 'this' reference so that we can determine
- // which lambdas need access to it
- val thisReferringMethods: Set[Symbol] = {
- val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser()
- thisReferringMethodsTraverser traverse unit.body
- val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences
- val referrers = thisReferringMethodsTraverser.thisReferringMethods
- // recursively find methods that refer to 'this' directly or indirectly via references to other methods
- // for each method found add it to the referrers set
- def refersToThis(symbol: Symbol): Boolean = {
- if (referrers contains symbol) true
- else if (methodReferringMap(symbol) exists refersToThis) {
- // add it early to memoize
- debuglog(s"$symbol indirectly refers to 'this'")
- referrers += symbol
- true
- } else false
+ // TODO: this is a bit gross
+ val sam = samUserDefined orElse {
+ if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get
+ else functionalInterface.info.member(nme.apply)
}
- methodReferringMap.keys foreach refersToThis
- referrers
+
+ // no need for adaptation when the implemented sam is of a specialized built-in function type
+ val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface, sam)
+
+ // The backend needs to know the target of the lambda and the functional interface in order
+ // to emit the invokedynamic instruction. We pass this information as tree attachment.
+ //
+ // see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
+ // instantiatedMethodType is derived from lambdaTarget's signature
+ // samMethodType is derived from samOf(functionalInterface)'s signature
+ apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface, sam))
+
+ apply
}
- // the result of the transformFunction method.
- sealed abstract class TransformedFunction
- // A class definition for the lambda, an expression instantiating the lambda class
- case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction
- case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction
private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]()
- // here's the main entry point of the transform
- override def transform(tree: Tree): Tree = tree match {
- // the main thing we care about is lambdas
- case fun @ Function(_, _) =>
- transformFunction(fun) match {
- case DelambdafyAnonClass(lambdaClassDef, newExpr) =>
- // a lambda becomes a new class, an instantiation expression
- val pkg = lambdaClassDef.symbol.owner
-
- // we'll add the lambda class to the package later
- lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg)
-
- super.transform(newExpr)
- case InvokeDynamicLambda(apply) =>
- // ... or an invokedynamic call
- super.transform(apply)
- }
- case Template(_, _, _) =>
- try {
- // during this call boxingBridgeMethods will be populated from the Function case
- val Template(parents, self, body) = super.transform(tree)
- Template(parents, self, body ++ boxingBridgeMethods)
- } finally boxingBridgeMethods.clear()
- case _ => super.transform(tree)
+ private def reboxValueClass(tp: Type) = tp match {
+ case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil)
+ case _ => tp
}
- // this entry point is aimed at the statements in the compilation unit.
- // after working on the entire compilation until we'll have a set of
- // new class definitions to add to the top level
- override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = {
- // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same
- // package when defining a package object. We only add the lambda class to one. See SI-9097.
- super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil)
+ // exclude primitives and value classes, which need special boxing
+ private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && {
+ val sym = tp.typeSymbol
+ !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass)
}
- private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None
+ // determine which lambda target to use with java's LMF -- create a new one if scala-specific boxing is required
+ def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol, sam: Symbol): Symbol = {
+ val oldClass = fun.symbol.enclClass
+ val pos = fun.pos
+
+ // At erasure, there won't be any captured arguments (they are added in constructors)
+ val functionParamTypes = exitingErasure(target.info.paramTypes)
+ val functionResultType = exitingErasure(target.info.resultType)
+
+ val samParamTypes = exitingErasure(sam.info.paramTypes)
+ val samResultType = exitingErasure(sam.info.resultType)
+
+ /** How to satisfy the linking invariants of https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html
+ *
+ * Given samMethodType: (U1..Un)Ru and function type T1,..., Tn => Rt (the target method created by uncurry)
+ *
+ * Do we need a bridge, or can we use the original lambda target for implMethod: (<captured args> A1..An)Ra
+ * (We can ignore capture here.)
+ *
+ * If, for i=1..N:
+ * Ai =:= Ui || (Ai <:< Ui <:< AnyRef)
+ * Ru =:= void || (Ra =:= Ru || (Ra <:< AnyRef, Ru <:< AnyRef))
+ *
+ * We can use the target method as-is -- if not, we create a bridging one that uses the types closest
+ * to the target method that still meet the above requirements.
+ */
+ val resTpOk = (
+ samResultType =:= UnitTpe
+ || functionResultType =:= samResultType
+ || (isReferenceType(samResultType) && isReferenceType(functionResultType))) // yes, this is what the spec says -- no further correspondance required
+ if (resTpOk && (samParamTypes corresponds functionParamTypes){ (samParamTp, funParamTp) =>
+ funParamTp =:= samParamTp || (isReferenceType(funParamTp) && isReferenceType(samParamTp) && funParamTp <:< samParamTp) }) target
+ else {
+ // We have to construct a new lambda target that bridges to the one created by uncurry.
+ // The bridge must satisfy the above invariants, while also minimizing adaptation on our end.
+ // LMF will insert runtime casts according to the spec at the above link.
+
+ // we use the more precise type between samParamTp and funParamTp to minimize boxing in the bridge method
+ // we are constructing a method whose signature matches the sam's signature (because the original target did not)
+ // whenever a type in the sam's signature is (erases to) a primitive type, we must pick the sam's version,
+ // as we don't implement the logic regarding widening that's performed by LMF -- we require =:= for primitives
+ //
+ // We use the sam's type for the check whether we're dealin with a reference type, as it could be a generic type,
+ // which means the function's parameter -- even if it expects a value class -- will need to be
+ // boxed on the generic call to the sam method.
- // turns a lambda into a new class def, a New expression instantiating that class
- private def transformFunction(originalFunction: Function): TransformedFunction = {
- val functionTpe = originalFunction.tpe
- val targs = functionTpe.typeArgs
- val formals :+ restpe = targs
- val oldClass = originalFunction.symbol.enclClass
+ val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) =>
+ if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp
+ else samParamTp
+ }
- // find which variables are free in the lambda because those are captures that need to be
- // passed into the constructor of the anonymous function class
- val captures = FreeVarTraverser.freeVarsOf(originalFunction)
+ val bridgeResultType =
+ if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType
+ else samResultType
- val target = targetMethod(originalFunction)
- target.makeNotPrivate(target.owner)
- if (!thisReferringMethods.contains(target))
- target setFlag STATIC
+ val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) }
+ import typeAdapter.{adaptToType, unboxValueClass}
- val isStatic = target.hasFlag(STATIC)
+ val targetParams = target.paramss.head
+ val numCaptures = targetParams.length - functionParamTypes.length
+ val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures)
- def createBoxingBridgeMethod(functionParamTypes: List[Type], functionResultType: Type): Tree = {
- // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required.
- // If we need to improve performance, we could check the types first before creating the
- // method and parameter symbols.
val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT)
- var neededAdaptation = false
- def boxedType(tpe: Type): Type = {
- if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe}
- else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe}
- else tpe
- }
- val targetParams: List[Symbol] = target.paramss.head
- val numCaptures = targetParams.length - functionParamTypes.length
- val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures)
- val bridgeParams: List[Symbol] =
- targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) :::
- map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName))
-
- val bridgeResultType: Type = {
- if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) {
- neededAdaptation = true
- ObjectTpe
- } else
- boxedType(functionResultType)
- }
- val methodType = MethodType(bridgeParams, bridgeResultType)
- methSym setInfo methodType
- if (!neededAdaptation)
- EmptyTree
- else {
- val bridgeParamTrees = bridgeParams.map(ValDef(_))
-
- oldClass.info.decls enter methSym
-
- val body = localTyper.typedPos(originalFunction.pos) {
- val newTarget = Select(gen.mkAttributedThis(oldClass), target)
- val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) =>
- if (i < numCaptures) {
- gen.mkAttributedRef(param)
- } else {
- val functionParam = functionParamTypes(i - numCaptures)
- val targetParam = targetParams(i)
- if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) {
- val casted = cast(gen.mkAttributedRef(param), functionParam)
- val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType)
- unboxed
- } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe)
- }
+ val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName))
+ val bridgeFunctionParams =
+ map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName))
+
+ val bridgeParams = bridgeCapturedParams ::: bridgeFunctionParams
+
+ methSym setInfo MethodType(bridgeParams, bridgeResultType)
+ oldClass.info.decls enter methSym
+
+ val forwarderCall = localTyper.typedPos(pos) {
+ val capturedArgRefs = bridgeCapturedParams map gen.mkAttributedRef
+ val functionArgRefs =
+ map3(bridgeFunctionParams, functionParamTypes, targetParams.drop(numCaptures)) { (bridgeParam, functionParamTp, targetParam) =>
+ val bridgeParamRef = gen.mkAttributedRef(bridgeParam)
+ val targetParamTp = targetParam.tpe
+
+ // TODO: can we simplify this to something like `adaptToType(adaptToType(bridgeParamRef, functionParamTp), targetParamTp)`?
+ val unboxed =
+ functionParamTp match {
+ case ErasedValueType(clazz, underlying) =>
+ // when the original function expected an argument of value class type,
+ // the original target will expect the unboxed underlying value,
+ // whereas the bridge will receive the boxed value (since the sam's argument type did not match and we had to adapt)
+ localTyper.typed(unboxValueClass(bridgeParamRef, clazz, underlying), targetParamTp)
+ case _ => bridgeParamRef
+ }
+
+ adaptToType(unboxed, targetParamTp)
}
- gen.mkMethodCall(newTarget, args)
- }
- val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass))
- adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType)
- else adaptToType(body, bridgeResultType)
- val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1)
- postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef]
- }
- }
- /**
- * Creates the apply method for the anonymous subclass of FunctionN
- */
- def createApplyMethod(newClass: Symbol, fun: Function, thisProxy: Symbol): DefDef = {
- val methSym = newClass.newMethod(nme.apply, fun.pos, FINAL | SYNTHETIC)
- val params = fun.vparams map (_.duplicate)
-
- val paramSyms = map2(formals, params) {
- (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name)
- }
- params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym }
- params foreach (_.symbol.owner = methSym)
-
- val methodType = MethodType(paramSyms, restpe)
- methSym setInfo methodType
-
- newClass.info.decls enter methSym
-
- val Apply(_, oldParams) = fun.body
- val qual = if (thisProxy.exists)
- Select(gen.mkAttributedThis(newClass), thisProxy)
- else
- gen.mkAttributedThis(oldClass) // sort of a lie, EmptyTree.<static method> would be more honest, but the backend chokes on that.
- val body = localTyper typed Apply(Select(qual, target), oldParams)
- body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol))
- body changeOwner (fun.symbol -> methSym)
-
- val methDef = DefDef(methSym, List(params), body)
+ gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs)
+ }
- // Have to repack the type to avoid mismatches when existentials
- // appear in the result - see SI-4869.
- // TODO probably don't need packedType
- methDef.tpt setType localTyper.packedType(body, methSym)
- methDef
- }
+ val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))),
+ adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef]
- /**
- * Creates the constructor on the newly created class. It will handle
- * initialization of members that represent the captured environment
- */
- def createConstructor(newClass: Symbol, members: List[ValDef]): DefDef = {
- val constrSym = newClass.newConstructor(originalFunction.pos, SYNTHETIC)
-
- val (paramSymbols, params, assigns) = (members map {member =>
- val paramSymbol = newClass.newVariable(member.symbol.name.toTermName, newClass.pos, 0)
- paramSymbol.setInfo(member.symbol.info)
- val paramVal = ValDef(paramSymbol)
- val paramIdent = Ident(paramSymbol)
- val assign = Assign(Select(gen.mkAttributedThis(newClass), member.symbol), paramIdent)
-
- (paramSymbol, paramVal, assign)
- }).unzip3
-
- val constrType = MethodType(paramSymbols, newClass.thisType)
- constrSym setInfoAndEnter constrType
-
- val body =
- Block(
- List(
- Apply(Select(Super(gen.mkAttributedThis(newClass), tpnme.EMPTY) setPos newClass.pos, nme.CONSTRUCTOR) setPos newClass.pos, Nil) setPos newClass.pos
- ) ++ assigns,
- Literal(Constant(())): Tree
- ) setPos newClass.pos
-
- (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef]
+ boxingBridgeMethods += bridge
+ bridge.symbol
}
+ }
- val pkg = oldClass.owner
-
- // Parent for anonymous class def
- val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe
-
- // anonymous subclass of FunctionN with an apply method
- def makeAnonymousClass: ClassDef = {
- val parents = addSerializable(abstractFunctionErasedType)
- val funOwner = originalFunction.symbol.owner
-
- // TODO harmonize the naming of delambdafy anon-fun classes with those spun up by Uncurry
- // - make `anonClass.isAnonymousClass` true.
- // - use `newAnonymousClassSymbol` or push the required variations into a similar factory method
- // - reinstate the assertion in `Erasure.resolveAnonymousBridgeClash`
- val suffix = nme.DELAMBDAFY_LAMBDA_CLASS_NAME + "$" + (
- if (funOwner.isPrimaryConstructor) ""
- else "$" + funOwner.name + "$"
- )
- val oldClassPart = oldClass.name.decode
- // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true
- val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon"))
-
- val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation
- lambdaClass.associatedFile = unit.source.file
- // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes)
- currentRun.symSource(lambdaClass) = funOwner.sourceFile
- lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass)
- assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name)
- assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name)
-
- val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol]
- captures foreach {capture =>
- val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC)
- sym setInfo capture.info
- captureProxies2 += ((capture, sym))
- }
+ private def transformFunction(originalFunction: Function): Tree = {
+ val target = targetMethod(originalFunction)
+ target.makeNotPrivate(target.owner)
- // the Optional proxy that will hold a reference to the 'this'
- // object used by the lambda, if any. NoSymbol if there is no this proxy
- val thisProxy = {
- if (isStatic)
- NoSymbol
- else {
- val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC)
- sym.setInfo(oldClass.tpe)
- }
- }
+ // must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall
+ if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC
- val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy)
+ val funSym = originalFunction.tpe.typeSymbolDirect
+ // The functional interface that can be used to adapt the lambda target method `target` to the given function type.
+ val (functionalInterface, isSpecialized) =
+ if (!isFunctionSymbol(funSym)) (funSym, false)
+ else {
+ val specializedName =
+ specializeTypes.specializedFunctionName(funSym,
+ exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName
- val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function]
+ val isSpecialized = specializedName != funSym.name
+ val functionalInterface = // TODO: this is no longer needed, right? we can just use the regular function classes
+ if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J"))
+ else FunctionClass(originalFunction.vparams.length)
- val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member =>
- lambdaClass.info.decls enter member
- ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos
+ (functionalInterface, isSpecialized)
}
- // constructor
- val constr = createConstructor(lambdaClass, members)
-
- // apply method with same arguments and return type as original lambda.
- val applyMethodDef = createApplyMethod(lambdaClass, decapturedFunction, thisProxy)
-
- val bridgeMethod = createBridgeMethod(lambdaClass, originalFunction, applyMethodDef)
-
- def fulldef(sym: Symbol) =
- if (sym == NoSymbol) sym.toString
- else s"$sym: ${sym.tpe} in ${sym.owner}"
-
- bridgeMethod foreach (bm =>
- // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases
- // rather than the method+bridge pair.
- if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe)
- erasure.resolveAnonymousBridgeClash(applyMethodDef.symbol, bm.symbol)
- )
-
- val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod
-
- // TODO if member fields are private this complains that they're not accessible
- localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef]
- }
-
- val allCaptureArgs: List[Tree] = {
- val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil
- val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList
- thisArg ::: captureArgs
- }
-
- val arity = originalFunction.vparams.length
-
- // Reconstruct the type of the function entering erasure.
- // We do this by taking the type after erasure, and re-boxing `ErasedValueType`.
- //
- // Unfortunately, the more obvious `enteringErasure(target.info)` doesn't work
- // as we would like, value classes in parameter position show up as the unboxed types.
- val (functionParamTypes, functionResultType) = exitingErasure {
- def boxed(tp: Type) = tp match {
- case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil)
- case _ => tp
- }
- // We don't need to deeply map `boxedValueClassType` over the infos as `ErasedValueType`
- // will only appear directly as a parameter type in a method signature, as shown
- // https://gist.github.com/retronym/ba81dbd462282c504ff8
- val info = target.info
- val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed)
- (boxedParamTypes, boxed(info.resultType))
- }
- val functionType = definitions.functionType(functionParamTypes, functionResultType)
-
- val (functionalInterface, isSpecialized) = java8CompatFunctionalInterface(target, functionType)
- if (functionalInterface.exists) {
- // Create a symbol representing a fictional lambda factory method that accepts the captured
- // arguments and returns a Function.
- val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT)
- val argTypes: List[Type] = allCaptureArgs.map(_.tpe)
- val params = msym.newSyntheticValueParams(argTypes)
- msym.setInfo(MethodType(params, functionType))
- val arity = originalFunction.vparams.length
-
- val lambdaTarget =
- if (isSpecialized)
- target
- else {
- createBoxingBridgeMethod(functionParamTypes, functionResultType) match {
- case EmptyTree =>
- target
- case bridge =>
- boxingBridgeMethods += bridge
- bridge.symbol
- }
- }
-
- // We then apply this symbol to the captures.
- val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply]
-
- // The backend needs to know the target of the lambda and the functional interface in order
- // to emit the invokedynamic instruction. We pass this information as tree attachment.
- apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface))
- InvokeDynamicLambda(apply)
- } else {
- val anonymousClassDef = makeAnonymousClass
- pkg.info.decls enter anonymousClassDef.symbol
- val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType))
- val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat)
- DelambdafyAnonClass(anonymousClassDef, typedNewStat)
- }
+ val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol)
+ mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized)
}
- /**
- * Creates a bridge method if needed. The bridge method forwards from apply(x1: Object, x2: Object...xn: Object): Object to
- * apply(x1: T1, x2: T2...xn: Tn): T0 using type adaptation on each input and output. The only time a bridge isn't needed
- * is when the original lambda is already erased to type Object, Object, Object... => Object
- */
- def createBridgeMethod(newClass:Symbol, originalFunction: Function, applyMethod: DefDef): Option[DefDef] = {
- val bridgeMethSym = newClass.newMethod(nme.apply, applyMethod.pos, FINAL | SYNTHETIC | BRIDGE)
- val originalParams = applyMethod.vparamss(0)
- val bridgeParams = originalParams map { originalParam =>
- val bridgeSym = bridgeMethSym.newSyntheticValueParam(ObjectTpe, originalParam.name)
- ValDef(bridgeSym)
- }
-
- val bridgeSyms = bridgeParams map (_.symbol)
-
- val methodType = MethodType(bridgeSyms, ObjectTpe)
- bridgeMethSym setInfo methodType
-
- def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = {
- if (tree.tpe =:= expectedTpe) (false, tree)
- else (true, adaptToType(tree, expectedTpe))
- }
-
- def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = {
- val (needsAdapt, adaptedTree) = adapt(tree, pt)
- val trans = postErasure.newTransformer(unit)
- val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes
- (needsAdapt, postErasedTree)
- }
-
- enteringPhase(currentRun.posterasurePhase) {
- // e.g, in:
- // class C(val a: Int) extends AnyVal; (x: Int) => new C(x)
- //
- // This type is:
- // (x: Int)ErasedValueType(class C, Int)
- val liftedBodyDefTpe: MethodType = {
- val liftedBodySymbol = {
- val Apply(method, _) = originalFunction.body
- method.symbol
- }
- liftedBodySymbol.info.asInstanceOf[MethodType]
- }
- val (paramNeedsAdaptation, adaptedParams) = (bridgeSyms zip liftedBodyDefTpe.params map {case (bridgeSym, param) => adapt(Ident(bridgeSym) setType bridgeSym.tpe, param.tpe)}).unzip
- // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`.
- // But that symbol doesn't have a type history that goes back before `delambdafy`,
- // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`.
- // This triggered primitive boxing, rather than value class boxing.
- val resTp = liftedBodyDefTpe.finalResultType
- val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp
- val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe)
-
- val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation
- if (needsBridge) {
- val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody)
- newClass.info.decls enter bridgeMethSym
- Some((localTyper typed methDef).asInstanceOf[DefDef])
- } else None
- }
+ // here's the main entry point of the transform
+ override def transform(tree: Tree): Tree = tree match {
+ // the main thing we care about is lambdas
+ case fun: Function => super.transform(transformFunction(fun))
+ case Template(_, _, _) =>
+ try {
+ // during this call boxingBridgeMethods will be populated from the Function case
+ val Template(parents, self, body) = super.transform(tree)
+ Template(parents, self, body ++ boxingBridgeMethods)
+ } finally boxingBridgeMethods.clear()
+ case _ => super.transform(tree)
}
} // DelambdafyTransformer
+
// A traverser that finds symbols used but not defined in the given Tree
// TODO freeVarTraverser in LambdaLift does a very similar task. With some
// analysis this could probably be unified with it
@@ -513,40 +296,36 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
}
}
- // A transformer that converts specified captured symbols into other symbols
- // TODO this transform could look more like ThisSubstituter and TreeSymSubstituter. It's not clear that it needs that level of sophistication since the types
- // at this point are always very simple flattened/erased types, but it would probably be more robust if it tried to take more complicated types into account
- class DeCapturifyTransformer(captureProxies: Map[Symbol, TermSymbol], unit: CompilationUnit, oldClass: Symbol, newClass:Symbol, pos: Position, thisProxy: Symbol) extends TypingTransformer(unit) {
- override def transform(tree: Tree) = tree match {
- case tree@This(encl) if tree.symbol == oldClass && thisProxy.exists =>
- gen mkAttributedSelect (gen mkAttributedThis newClass, thisProxy)
- case Ident(name) if (captureProxies contains tree.symbol) =>
- gen mkAttributedSelect (gen mkAttributedThis newClass, captureProxies(tree.symbol))
- case _ => super.transform(tree)
+ // finds all methods that reference 'this'
+ class ThisReferringMethodsTraverser extends Traverser {
+ // the set of methods that refer to this
+ private val thisReferringMethods = mutable.Set[Symbol]()
+
+ // the set of lifted lambda body methods that each method refers to
+ private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set())
+
+ def methodReferencesThisIn(tree: Tree) = {
+ traverse(tree)
+ liftedMethodReferences.keys foreach refersToThis
+
+ thisReferringMethods
}
- }
- /**
- * Get the symbol of the target lifted lambda body method from a function. I.e. if
- * the function is {args => anonfun(args)} then this method returns anonfun's symbol
- */
- private def targetMethod(fun: Function): Symbol = fun match {
- case Function(_, Apply(target, _)) =>
- target.symbol
- case _ =>
- // any other shape of Function is unexpected at this point
- abort(s"could not understand function with tree $fun")
- }
+ // recursively find methods that refer to 'this' directly or indirectly via references to other methods
+ // for each method found add it to the referrers set
+ private def refersToThis(symbol: Symbol): Boolean =
+ (thisReferringMethods contains symbol) ||
+ (liftedMethodReferences(symbol) exists refersToThis) && {
+ // add it early to memoize
+ debuglog(s"$symbol indirectly refers to 'this'")
+ thisReferringMethods += symbol
+ true
+ }
- // finds all methods that reference 'this'
- class ThisReferringMethodsTraverser() extends Traverser {
private var currentMethod: Symbol = NoSymbol
- // the set of methods that refer to this
- val thisReferringMethods = mutable.Set[Symbol]()
- // the set of lifted lambda body methods that each method refers to
- val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set())
+
override def traverse(tree: Tree) = tree match {
- case DefDef(_, _, _, _, _, _) =>
+ case DefDef(_, _, _, _, _, _) if tree.symbol.isDelambdafyTarget =>
// we don't expect defs within defs. At this phase trees should be very flat
if (currentMethod.exists) devWarning("Found a def within a def at a phase where defs are expected to be flattened out.")
currentMethod = tree.symbol
@@ -562,32 +341,10 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre
debuglog(s"$currentMethod directly refers to 'this'")
thisReferringMethods add currentMethod
}
+ case _: ClassDef if !tree.symbol.isTopLevel =>
+ case _: DefDef =>
case _ =>
super.traverse(tree)
}
}
-
- final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol)
-
- // The functional interface that can be used to adapt the lambda target method `target` to the
- // given function type. Returns `NoSymbol` if the compiler settings are unsuitable.
- private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = {
- val canUseLambdaMetafactory: Boolean = {
- val isTarget18 = settings.target.value.contains("jvm-1.8")
- settings.isBCodeActive && isTarget18
- }
-
- val sym = functionType.typeSymbol
- val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage
- val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs)
- val paramTps :+ restpe = functionType.typeArgs
- val arity = paramTps.length
- val isSpecialized = name1.toTypeName != sym.name
- val functionalInterface = if (!isSpecialized) {
- currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity)
- } else {
- pack.info.decl(name1.toTypeName.prepend("J"))
- }
- (if (canUseLambdaMetafactory) functionalInterface else NoSymbol, isSpecialized)
- }
}
diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala
index a04625c9c5..0301e06c87 100644
--- a/src/compiler/scala/tools/nsc/transform/Erasure.scala
+++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala
@@ -71,7 +71,9 @@ abstract class Erasure extends AddInterfaces
}
override protected def verifyJavaErasure = settings.Xverify || settings.debug
- def needsJavaSig(tp: Type) = !settings.Ynogenericsig && NeedsSigCollector.collect(tp)
+ def needsJavaSig(tp: Type, throwsArgs: List[Type]) = !settings.Ynogenericsig && {
+ NeedsSigCollector.collect(tp) || throwsArgs.exists(NeedsSigCollector.collect)
+ }
// only refer to type params that will actually make it into the sig, this excludes:
// * higher-order type parameters
@@ -251,7 +253,7 @@ abstract class Erasure extends AddInterfaces
// Anything which could conceivably be a module (i.e. isn't known to be
// a type parameter or similar) must go through here or the signature is
// likely to end up with Foo<T>.Empty where it needs Foo<T>.Empty$.
- def fullNameInSig(sym: Symbol) = "L" + enteringIcode(sym.javaBinaryName)
+ def fullNameInSig(sym: Symbol) = "L" + enteringJVM(sym.javaBinaryName)
def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, toplevel: Boolean = false, primitiveOK: Boolean = true): String = {
val tp = tp0.dealias
@@ -277,7 +279,7 @@ abstract class Erasure extends AddInterfaces
val preRebound = pre.baseType(sym.owner) // #2585
dotCleanup(
(
- if (needsJavaSig(preRebound)) {
+ if (needsJavaSig(preRebound, Nil)) {
val s = jsig(preRebound, existentiallyBound)
if (s.charAt(0) == 'L') s.substring(0, s.length - 1) + "." + sym.javaSimpleName
else fullNameInSig(sym)
@@ -341,8 +343,8 @@ abstract class Erasure extends AddInterfaces
buf append (if (restpe.typeSymbol == UnitClass || sym0.isConstructor) VOID_TAG.toString else jsig(restpe))
buf.toString
- case RefinedType(parent :: _, decls) =>
- boxedSig(parent)
+ case RefinedType(parents, decls) =>
+ boxedSig(intersectionDominator(parents))
case ClassInfoType(parents, _, _) =>
superSig(parents)
case AnnotatedType(_, atp) =>
@@ -356,8 +358,9 @@ abstract class Erasure extends AddInterfaces
else jsig(etp)
}
}
- if (needsJavaSig(info)) {
- try Some(jsig(info, toplevel = true))
+ val throwsArgs = sym0.annotations flatMap ThrownException.unapply
+ if (needsJavaSig(info, throwsArgs)) {
+ try Some(jsig(info, toplevel = true) + throwsArgs.map("^" + jsig(_, toplevel = true)).mkString(""))
catch { case ex: UnknownSig => None }
}
else None
@@ -575,8 +578,9 @@ abstract class Erasure extends AddInterfaces
}
/** The modifier typer which retypes with erased types. */
- class Eraser(_context: Context) extends Typer(_context) with TypeAdapter {
- val typer = this.asInstanceOf[analyzer.Typer]
+ class Eraser(_context: Context) extends Typer(_context) {
+ val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = Eraser.this.typedPos(pos)(tree) }
+ import typeAdapter._
override protected def stabilize(tree: Tree, pre: Type, mode: Mode, pt: Type): Tree = tree
@@ -644,7 +648,7 @@ abstract class Erasure extends AddInterfaces
var qual1 = typedQualifier(qual)
if ((isPrimitiveValueType(qual1.tpe) && !isPrimitiveValueMember(tree.symbol)) ||
isErasedValueType(qual1.tpe))
- qual1 = box(qual1, "owner "+tree.symbol.owner)
+ qual1 = box(qual1)
else if (!isPrimitiveValueType(qual1.tpe) && isPrimitiveValueMember(tree.symbol))
qual1 = unbox(qual1, tree.symbol.owner.tpe)
@@ -653,13 +657,12 @@ abstract class Erasure extends AddInterfaces
if (isPrimitiveValueMember(tree.symbol) && !isPrimitiveValueType(qual1.tpe)) {
tree.symbol = NoSymbol
selectFrom(qual1)
- } else if (isMethodTypeWithEmptyParams(qual1.tpe)) {
+ } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { // see also adaptToType in TypeAdapter
assert(qual1.symbol.isStable, qual1.symbol)
- val applied = Apply(qual1, List()) setPos qual1.pos setType qual1.tpe.resultType
- adaptMember(selectFrom(applied))
+ adaptMember(selectFrom(applyMethodWithEmptyParams(qual1)))
} else if (!(qual1.isInstanceOf[Super] || (qual1.tpe.typeSymbol isSubClass tree.symbol.owner))) {
assert(tree.symbol.owner != ArrayClass)
- selectFrom(cast(qual1, tree.symbol.owner.tpe))
+ selectFrom(cast(qual1, tree.symbol.owner.tpe.resultType))
} else {
selectFrom(qual1)
}
@@ -718,6 +721,12 @@ abstract class Erasure extends AddInterfaces
if (branch == EmptyTree) branch else adaptToType(branch, tree1.tpe)
tree1 match {
+ case fun: Function =>
+ fun.attachments.get[SAMFunction] match {
+ case Some(SAMFunction(samTp, _)) => fun setType specialScalaErasure(samTp)
+ case _ => fun
+ }
+
case If(cond, thenp, elsep) =>
treeCopy.If(tree1, cond, adaptBranch(thenp), adaptBranch(elsep))
case Match(selector, cases) =>
@@ -1004,24 +1013,20 @@ abstract class Erasure extends AddInterfaces
// erasure the ScalaRunTime.hash overload goes from Unit => Int to BoxedUnit => Int.
// This must be because some earlier transformation is being skipped on ##, but so
// far I don't know what. For null we now define null.## == 0.
+ def staticsCall(methodName: TermName): Tree = {
+ val newTree = gen.mkMethodCall(RuntimeStaticsModule, methodName, qual :: Nil)
+ global.typer.typed(newTree)
+ }
+
qual.tpe.typeSymbol match {
case UnitClass | NullClass => LIT(0)
case IntClass => qual
case s @ (ShortClass | ByteClass | CharClass) => numericConversion(qual, s)
case BooleanClass => If(qual, LIT(true.##), LIT(false.##))
- case _ =>
- // Since we are past typer, we need to avoid creating trees carrying
- // overloaded types. This logic is custom (and technically incomplete,
- // although serviceable) for def hash. What is really needed is for
- // the overloading logic presently hidden away in a few different
- // places to be properly exposed so we can just call "resolveOverload"
- // after typer. Until then:
- val alts = ScalaRunTimeModule.info.member(nme.hash_).alternatives
- def alt1 = alts find (_.info.paramTypes.head =:= qual.tpe)
- def alt2 = ScalaRunTimeModule.info.member(nme.hash_) suchThat (_.info.paramTypes.head.typeSymbol == AnyClass)
- val newTree = gen.mkRuntimeCall(nme.hash_, qual :: Nil) setSymbol (alt1 getOrElse alt2)
-
- global.typer.typed(newTree)
+ case LongClass => staticsCall(nme.longHash)
+ case FloatClass => staticsCall(nme.floatHash)
+ case DoubleClass => staticsCall(nme.doubleHash)
+ case _ => staticsCall(nme.anyHash)
}
} else if (isPrimitiveValueClass(qual.tpe.typeSymbol)) {
// Rewrite 5.getClass to ScalaRunTime.anyValClass(5)
@@ -1100,7 +1105,6 @@ abstract class Erasure extends AddInterfaces
}
} else tree
case Template(parents, self, body) =>
- assert(!currentOwner.isImplClass)
//Console.println("checking no dble defs " + tree)//DEBUG
checkNoDoubleDefs(tree.symbol.owner)
treeCopy.Template(tree, parents, noSelfType, addBridges(body, currentOwner))
@@ -1110,7 +1114,7 @@ abstract class Erasure extends AddInterfaces
case Literal(ct) if ct.tag == ClazzTag
&& ct.typeValue.typeSymbol != definitions.UnitClass =>
- val erased = ct.typeValue match {
+ val erased = ct.typeValue.dealiasWiden match {
case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr)
case tpe => specialScalaErasure(tpe)
}
@@ -1147,6 +1151,8 @@ abstract class Erasure extends AddInterfaces
case DefDef(_, _, _, _, tpt, _) =>
try super.transform(tree1).clearType()
finally tpt setType specialErasure(tree1.symbol)(tree1.symbol.tpe).resultType
+ case ApplyDynamic(qual, Literal(Constant(boostrapMethodRef: Symbol)) :: _) =>
+ tree
case _ =>
super.transform(tree1).clearType()
}
@@ -1177,5 +1183,41 @@ abstract class Erasure extends AddInterfaces
bridge.resetFlag(BRIDGE)
}
+ /** Does this symbol compile to the underlying platform's notion of an interface,
+ * without requiring compiler magic before it can be instantiated?
+ *
+ * More specifically, we're interested in whether LambdaMetaFactory can instantiate this type,
+ * assuming it has a single abstract method. In other words, if we were to mix this
+ * trait into a class, it should not result in any compiler-generated members having to be
+ * implemented in ("mixed in to") this class (except for the SAM).
+ *
+ * Thus, the type must erase to a java interface, either by virtue of being defined as one,
+ * or by being a trait that:
+ * - is static (explicitouter or lambdalift may add disqualifying members)
+ * - extends only other traits that compile to pure interfaces (except for Any)
+ * - has no val/var members
+ *
+ * TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction?
+ */
+ final def compilesToPureInterface(tpSym: Symbol): Boolean = {
+ def ok(sym: Symbol) =
+ sym.isJavaInterface ||
+ sym.isTrait &&
+ // Unless sym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift.
+ // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry
+ // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures).
+ // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether
+ // to expand sam at compile time or use LMF, and this implementation restriction could be lifted.
+ sym.isStatic &&
+ // HACK: this is to rule out traits with an effectful initializer.
+ // The constructor only exists if the trait's template has statements.
+ // Sadly, we can't be more precise without access to the tree that defines the SAM's owner.
+ !sym.primaryConstructor.exists &&
+ (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true})
+
+ // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account
+ ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym))
+ }
+
private class TypeRefAttachment(val tpe: TypeRef)
}
diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
index 540de2cfe1..3d6fad4238 100644
--- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala
@@ -9,9 +9,7 @@ package transform
import symtab._
import Flags.{ CASE => _, _ }
-import scala.collection.mutable
import scala.collection.mutable.ListBuffer
-import scala.tools.nsc.settings.ScalaVersion
/** This class ...
*
@@ -159,19 +157,24 @@ abstract class ExplicitOuter extends InfoTransform
* elides outer pointers.
*/
def transformInfo(sym: Symbol, tp: Type): Type = tp match {
- case MethodType(params, restpe1) =>
- val restpe = transformInfo(sym, restpe1)
- if (sym.owner.isTrait && ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule)) { // 5
- sym.makeNotPrivate(sym.owner)
+ case MethodType(params, resTp) =>
+ val resTpTransformed = transformInfo(sym, resTp)
+
+ // juggle flags (and mangle names) after transforming info
+ if (sym.owner.isTrait) {
+ // TODO: I don't believe any private accessors remain after the fields phase
+ if ((sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isModule) sym.makeNotPrivate(sym.owner) // 5
+ if (sym.isProtected) sym setFlag notPROTECTED // 6
}
- if (sym.owner.isTrait && sym.isProtected) sym setFlag notPROTECTED // 6
- if (sym.isClassConstructor && isInner(sym.owner)) { // 1
- val p = sym.newValueParameter(innerClassConstructorParamName, sym.pos)
- .setInfo(sym.owner.outerClass.thisType)
- MethodType(p :: params, restpe)
- } else if (restpe ne restpe1)
- MethodType(params, restpe)
+
+ val paramsWithOuter =
+ if (sym.isClassConstructor && isInner(sym.owner)) // 1
+ sym.newValueParameter(innerClassConstructorParamName, sym.pos).setInfo(sym.owner.outerClass.thisType) :: params
+ else params
+
+ if ((resTpTransformed ne resTp) || (paramsWithOuter ne params)) MethodType(paramsWithOuter, resTpTransformed)
else tp
+
case ClassInfoType(parents, decls, clazz) =>
var decls1 = decls
if (isInner(clazz) && !clazz.isInterface) {
@@ -207,7 +210,7 @@ abstract class ExplicitOuter extends InfoTransform
// class needs to have a common naming scheme, independently of whether
// the field was accessed from an inner class or not. See #2946
if (sym.owner.isTrait && sym.isLocalToThis &&
- (sym.getterIn(sym.owner.toInterface) == NoSymbol))
+ (sym.getterIn(sym.owner) == NoSymbol))
sym.makeNotPrivate(sym.owner)
tp
}
@@ -238,12 +241,17 @@ abstract class ExplicitOuter extends InfoTransform
* Will return `EmptyTree` if there is no outer accessor because of a premature self reference.
*/
private def outerSelect(base: Tree): Tree = {
- val baseSym = base.tpe.typeSymbol.toInterface
+ val baseSym = base.tpe.typeSymbol
val outerAcc = outerAccessor(baseSym)
- if (outerAcc == NoSymbol && baseSym.ownersIterator.exists(isUnderConstruction)) {
- // e.g neg/t6666.scala
- // The caller will report the error with more information.
- EmptyTree
+ if (outerAcc == NoSymbol) {
+ if (baseSym.ownersIterator.exists(isUnderConstruction)) {
+ // e.g neg/t6666.scala
+ // The caller will report the error with more information.
+ EmptyTree
+ } else {
+ globalError(currentOwner.pos, s"Internal error: unable to find the outer accessor symbol of $baseSym")
+ EmptyTree
+ }
} else {
val currentClass = this.currentClass //todo: !!! if this line is removed, we get a build failure that protected$currentClass need an override modifier
// outerFld is the $outer field of the current class, if the reference can
@@ -251,6 +259,7 @@ abstract class ExplicitOuter extends InfoTransform
// otherwise it is NoSymbol
val outerFld =
if (outerAcc.owner == currentClass &&
+ !outerAcc.owner.isTrait &&
base.tpe =:= currentClass.thisType &&
outerAcc.owner.isEffectivelyFinal)
outerField(currentClass) suchThat (_.owner == currentClass)
@@ -271,8 +280,7 @@ abstract class ExplicitOuter extends InfoTransform
*/
protected def outerPath(base: Tree, from: Symbol, to: Symbol): Tree = {
//Console.println("outerPath from "+from+" to "+to+" at "+base+":"+base.tpe)
- //assert(base.tpe.widen.baseType(from.toInterface) != NoType, ""+base.tpe.widen+" "+from.toInterface)//DEBUG
- if (from == to || from.isImplClass && from.toInterface == to) base
+ if (from == to) base
else outerPath(outerSelect(base), from.outerClass, to)
}
@@ -397,7 +405,7 @@ abstract class ExplicitOuter extends InfoTransform
case Template(parents, self, decls) =>
val newDefs = new ListBuffer[Tree]
atOwner(tree, currentOwner) {
- if (!currentClass.isInterface || (currentClass hasFlag lateINTERFACE)) {
+ if (!currentClass.isInterface) {
if (isInner(currentClass)) {
if (hasOuterField(currentClass))
newDefs += outerFieldDef // (1a)
@@ -446,8 +454,10 @@ abstract class ExplicitOuter extends InfoTransform
//
// See SI-6552 for an example of why `sym.owner.enclMethod hasAnnotation ScalaInlineClass`
// is not suitable; if we make a method-local class non-private, it mangles outer pointer names.
- if (currentClass != sym.owner ||
- (closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass))
+ def enclMethodIsInline = closestEnclMethod(currentOwner) hasAnnotation ScalaInlineClass
+ // SI-8710 The extension method condition reflects our knowledge that a call to `new Meter(12).privateMethod`
+ // with later be rewritten (in erasure) to `Meter.privateMethod$extension(12)`.
+ if ((currentClass != sym.owner || enclMethodIsInline) && !sym.isMethodWithExtension)
sym.makeNotPrivate(sym.owner)
val qsym = qual.tpe.widen.typeSymbol
@@ -474,14 +484,15 @@ abstract class ExplicitOuter extends InfoTransform
// base.<outer>.eq(o) --> base.$outer().eq(o) if there's an accessor, else the whole tree becomes TRUE
// TODO remove the synthetic `<outer>` method from outerFor??
case Apply(eqsel@Select(eqapp@Apply(sel@Select(base, nme.OUTER_SYNTH), Nil), eq), args) =>
- val outerFor = sel.symbol.owner.toInterface // TODO: toInterface necessary?
+ val outerFor = sel.symbol.owner
val acc = outerAccessor(outerFor)
if (acc == NoSymbol ||
// since we can't fix SI-4440 properly (we must drop the outer accessors of final classes when there's no immediate reference to them in sight)
// at least don't crash... this duplicates maybeOmittable from constructors
(acc.owner.isEffectivelyFinal && !acc.isOverridingSymbol)) {
- currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.")
+ if (!base.tpe.hasAnnotation(UncheckedClass))
+ currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.")
transform(TRUE) // urgh... drop condition if there's no accessor (or if it may disappear after constructors)
} else {
// println("(base, acc)= "+(base, acc))
diff --git a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
index 116047a2ad..9d02228ab5 100644
--- a/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
+++ b/src/compiler/scala/tools/nsc/transform/ExtensionMethods.scala
@@ -7,7 +7,7 @@ package transform
import symtab._
import Flags._
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
/**
* Perform Step 1 in the inline classes SIP: Creates extension methods for all
@@ -208,7 +208,7 @@ abstract class ExtensionMethods extends Transform with TypingTransformers {
def makeExtensionMethodSymbol = {
val extensionName = extensionNames(origMeth).head.toTermName
val extensionMeth = (
- companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~LOCAL | FINAL)
+ companion.moduleClass.newMethod(extensionName, tree.pos.focus, origMeth.flags & ~OVERRIDE & ~PROTECTED & ~PRIVATE & ~LOCAL | FINAL)
setAnnotations origMeth.annotations
)
origMeth.removeAnnotation(TailrecClass) // it's on the extension method, now.
@@ -244,7 +244,10 @@ abstract class ExtensionMethods extends Transform with TypingTransformers {
// These three lines are assembling Foo.bar$extension[T1, T2, ...]($this)
// which leaves the actual argument application for extensionCall.
- val sel = Select(gen.mkAttributedRef(companion), extensionMeth)
+ // SI-9542 We form the selection here from the thisType of the companion's owner. This is motivated
+ // by the test case, and is a valid way to construct the reference because we know that this
+ // method is also enclosed by that owner.
+ val sel = Select(gen.mkAttributedRef(companion.owner.thisType, companion), extensionMeth)
val targs = origTpeParams map (_.tpeHK)
val callPrefix = gen.mkMethodCall(sel, targs, This(origThis) :: Nil)
diff --git a/src/compiler/scala/tools/nsc/transform/Flatten.scala b/src/compiler/scala/tools/nsc/transform/Flatten.scala
index fbb0307773..0db9f19597 100644
--- a/src/compiler/scala/tools/nsc/transform/Flatten.scala
+++ b/src/compiler/scala/tools/nsc/transform/Flatten.scala
@@ -41,8 +41,6 @@ abstract class Flatten extends InfoTransform {
}
private def liftSymbol(sym: Symbol) {
liftClass(sym)
- if (sym.needsImplClass)
- liftClass(erasure implClass sym)
}
// This is a short-term measure partially working around objects being
// lifted out of parameterized classes, leaving them referencing
diff --git a/src/compiler/scala/tools/nsc/transform/InlineErasure.scala b/src/compiler/scala/tools/nsc/transform/InlineErasure.scala
deleted file mode 100644
index 1bbe1b8410..0000000000
--- a/src/compiler/scala/tools/nsc/transform/InlineErasure.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-package scala.tools.nsc
-package transform
-
-trait InlineErasure {
- self: Erasure =>
-
-/*
- import global._
- import definitions._
- */
-}
diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
index d1be1558b9..074acc1332 100644
--- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
+++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala
@@ -8,7 +8,7 @@ package transform
import symtab._
import Flags._
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
import scala.collection.mutable.{ LinkedHashMap, LinkedHashSet, TreeSet }
abstract class LambdaLift extends InfoTransform {
@@ -31,11 +31,6 @@ abstract class LambdaLift extends InfoTransform {
}
}
- /** scala.runtime.*Ref classes */
- private lazy val allRefClasses: Set[Symbol] = {
- refClass.values.toSet ++ volatileRefClass.values.toSet ++ Set(VolatileObjectRefClass, ObjectRefClass)
- }
-
/** Each scala.runtime.*Ref class has a static method `create(value)` that simply instantiates the Ref to carry that value. */
private lazy val refCreateMethod: Map[Symbol, Symbol] = {
mapFrom(allRefClasses.toList)(x => getMemberMethod(x.companionModule, nme.create))
@@ -103,11 +98,6 @@ abstract class LambdaLift extends InfoTransform {
*/
private val proxyNames = mutable.HashMap[Symbol, Name]()
- // (trait, name) -> owner
- private val localTraits = mutable.HashMap[(Symbol, Name), Symbol]()
- // (owner, name) -> implClass
- private val localImplClasses = mutable.HashMap[(Symbol, Name), Symbol]()
-
/** A flag to indicate whether new free variables have been found */
private var changedFreeVars: Boolean = _
@@ -148,7 +138,7 @@ abstract class LambdaLift extends InfoTransform {
* }
*/
private def markFree(sym: Symbol, enclosure: Symbol): Boolean = {
- debuglog("mark free: " + sym.fullLocationString + " marked free in " + enclosure)
+// println(s"mark free: ${sym.fullLocationString} marked free in $enclosure")
(enclosure == sym.owner.logicallyEnclosingMember) || {
debuglog("%s != %s".format(enclosure, sym.owner.logicallyEnclosingMember))
if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false
@@ -158,7 +148,7 @@ abstract class LambdaLift extends InfoTransform {
ss += sym
renamable += sym
changedFreeVars = true
- debuglog("" + sym + " is free in " + enclosure)
+ debuglog(s"$sym is free in $enclosure")
if (sym.isVariable) sym setFlag CAPTURED
}
!enclosure.isClass
@@ -167,7 +157,7 @@ abstract class LambdaLift extends InfoTransform {
}
private def markCalled(sym: Symbol, owner: Symbol) {
- debuglog("mark called: " + sym + " of " + sym.owner + " is called by " + owner)
+// println(s"mark called: $sym of ${sym.owner} is called by $owner")
symSet(called, owner) += sym
if (sym.enclClass != owner.enclClass) calledFromInner += sym
}
@@ -175,30 +165,13 @@ abstract class LambdaLift extends InfoTransform {
/** The traverse function */
private val freeVarTraverser = new Traverser {
override def traverse(tree: Tree) {
- try { //debug
+// try { //debug
val sym = tree.symbol
tree match {
case ClassDef(_, _, _, _) =>
liftedDefs(tree.symbol) = Nil
if (sym.isLocalToBlock) {
- // Don't rename implementation classes independently of their interfaces. If
- // the interface is to be renamed, then we will rename the implementation
- // class at that time. You'd think we could call ".implClass" on the trait
- // rather than collecting them in another map, but that seems to fail for
- // exactly the traits being renamed here (i.e. defined in methods.)
- //
- // !!! - it makes no sense to have methods like "implClass" and
- // "companionClass" which fail for an arbitrary subset of nesting
- // arrangements, and then have separate methods which attempt to compensate
- // for that failure. There should be exactly one method for any given
- // entity which always gives the right answer.
- if (sym.isImplClass)
- localImplClasses((sym.owner, tpnme.interfaceName(sym.name))) = sym
- else {
- renamable += sym
- if (sym.isTrait)
- localTraits((sym, sym.name)) = sym.owner
- }
+ renamable += sym
}
case DefDef(_, _, _, _, _, _) =>
if (sym.isLocalToBlock) {
@@ -222,11 +195,11 @@ abstract class LambdaLift extends InfoTransform {
case _ =>
}
super.traverse(tree)
- } catch {//debug
- case ex: Throwable =>
- Console.println(s"$ex while traversing $tree")
- throw ex
- }
+// } catch {//debug
+// case ex: Throwable =>
+// Console.println(s"$ex while traversing $tree")
+// throw ex
+// }
}
}
@@ -240,7 +213,7 @@ abstract class LambdaLift extends InfoTransform {
do {
changedFreeVars = false
- for (caller <- called.keys ; callee <- called(caller) ; fvs <- free get callee ; fv <- fvs)
+ for ((caller, callees) <- called ; callee <- callees ; fvs <- free get callee ; fv <- fvs)
markFree(fv, caller)
} while (changedFreeVars)
@@ -250,11 +223,6 @@ abstract class LambdaLift extends InfoTransform {
debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name))
}
- // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by
- // introducing `$anon` in its name. to be cautious, we don't make this change in the default
- // backend under 2.11.x, so only in GenBCode.
- def nonAnon(s: String) = if (settings.Ybackend.value == "GenBCode") nme.ensureNonAnon(s) else s
-
def newName(sym: Symbol): Name = {
val originalName = sym.name
def freshen(prefix: String): Name =
@@ -263,56 +231,37 @@ abstract class LambdaLift extends InfoTransform {
val join = nme.NAME_JOIN_STRING
if (sym.isAnonymousFunction && sym.owner.isMethod) {
- freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join)
+ freshen(sym.name + join + nme.ensureNonAnon(sym.owner.name.toString) + join)
} else {
val name = freshen(sym.name + join)
// SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?)
// Generating a unique name, mangled with the enclosing full class name (including
// package - subclass might have the same name), avoids a VerifyError in the case
// that a sub-class happens to lifts out a method with the *same* name.
- if (originalName.isTermName && !sym.enclClass.isImplClass && calledFromInner(sym))
- newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name)
+ if (originalName.isTermName && calledFromInner(sym))
+ newTermNameCached(nme.ensureNonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name)
else
name
}
}
- /* Rename a trait's interface and implementation class in coordinated fashion. */
- def renameTrait(traitSym: Symbol, implSym: Symbol) {
- val originalImplName = implSym.name
- renameSym(traitSym)
- implSym setName tpnme.implClassName(traitSym.name)
-
- debuglog("renaming impl class in step with %s: %s => %s".format(traitSym, originalImplName, implSym.name))
- }
-
val allFree: Set[Symbol] = free.values.flatMap(_.iterator).toSet
for (sym <- renamable) {
- // If we renamed a trait from Foo to Foo$1, we must rename the implementation
- // class from Foo$class to Foo$1$class. (Without special consideration it would
- // become Foo$class$1 instead.) Since the symbols are being renamed out from
- // under us, and there's no reliable link between trait symbol and impl symbol,
- // we have maps from ((trait, name)) -> owner and ((owner, name)) -> impl.
- localTraits remove ((sym, sym.name)) match {
- case None =>
- if (allFree(sym)) proxyNames(sym) = newName(sym)
- else renameSym(sym)
- case Some(owner) =>
- localImplClasses remove ((owner, sym.name)) match {
- case Some(implSym) => renameTrait(sym, implSym)
- case _ => renameSym(sym) // pure interface, no impl class
- }
- }
+ if (allFree(sym)) proxyNames(sym) = newName(sym)
+ else renameSym(sym)
}
afterOwnPhase {
for ((owner, freeValues) <- free.toList) {
- val newFlags = SYNTHETIC | ( if (owner.isClass) PARAMACCESSOR | PrivateLocal else PARAM )
- debuglog("free var proxy: %s, %s".format(owner.fullLocationString, freeValues.toList.mkString(", ")))
+ val newFlags = SYNTHETIC | (
+ if (owner.isClass) PARAMACCESSOR | PrivateLocal
+ else PARAM)
+
proxies(owner) =
for (fv <- freeValues.toList) yield {
val proxyName = proxyNames.getOrElse(fv, fv.name)
+ debuglog(s"new proxy ${proxyName} in ${owner.fullLocationString}")
val proxy = owner.newValue(proxyName.toTermName, owner.pos, newFlags.toLong) setInfo fv.info
if (owner.isClass) owner.info.decls enter proxy
proxy
@@ -342,31 +291,31 @@ abstract class LambdaLift extends InfoTransform {
private def memberRef(sym: Symbol): Tree = {
val clazz = sym.owner.enclClass
- //Console.println("memberRef from "+currentClass+" to "+sym+" in "+clazz)
- def prematureSelfReference() {
+ // println(s"memberRef from $currentClass to $sym in $clazz (currentClass=$currentClass)")
+ def prematureSelfReference(): Tree = {
val what =
if (clazz.isStaticOwner) clazz.fullLocationString
else s"the unconstructed `this` of ${clazz.fullLocationString}"
val msg = s"Implementation restriction: access of ${sym.fullLocationString} from ${currentClass.fullLocationString}, would require illegal premature access to $what"
reporter.error(curTree.pos, msg)
+ EmptyTree
}
- val qual =
+ def qual =
if (clazz == currentClass) gen.mkAttributedThis(clazz)
else {
sym resetFlag (LOCAL | PRIVATE)
- if (isUnderConstruction(clazz)) {
- prematureSelfReference()
- EmptyTree
- }
+ if (isUnderConstruction(clazz)) prematureSelfReference()
else if (clazz.isStaticOwner) gen.mkAttributedQualifier(clazz.thisType)
- else {
- outerValue match {
- case EmptyTree => prematureSelfReference(); return EmptyTree
- case o => outerPath(o, currentClass.outerClass, clazz)
- }
+ else outerValue match {
+ case EmptyTree => prematureSelfReference()
+ case o => outerPath(o, currentClass.outerClass, clazz)
}
}
- Select(qual, sym) setType sym.tpe
+
+ qual match {
+ case EmptyTree => EmptyTree
+ case qual => Select(qual, sym) setType sym.tpe
+ }
}
private def proxyRef(sym: Symbol) = {
@@ -374,41 +323,45 @@ abstract class LambdaLift extends InfoTransform {
if (psym.isLocalToBlock) gen.mkAttributedIdent(psym) else memberRef(psym)
}
- private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) = {
- free get sym match {
- case Some(fvs) => addFree(sym, free = fvs.toList map (fv => atPos(pos)(proxyRef(fv))), original = args)
- case _ => args
+ def freeArgsOrNil(sym: Symbol) = free.getOrElse(sym, Nil).toList
+
+ private def freeArgs(sym: Symbol): List[Symbol] =
+ freeArgsOrNil(sym)
+
+ private def addFreeArgs(pos: Position, sym: Symbol, args: List[Tree]) =
+ freeArgs(sym) match {
+ case Nil => args
+ case fvs => addFree(sym, free = fvs map (fv => atPos(pos)(proxyRef(fv))), original = args)
}
- }
- private def addFreeParams(tree: Tree, sym: Symbol): Tree = proxies.get(sym) match {
- case Some(ps) =>
- val freeParams = ps map (p => ValDef(p) setPos tree.pos setType NoType)
- tree match {
- case DefDef(_, _, _, vparams :: _, _, _) =>
- val addParams = cloneSymbols(ps).map(_.setFlag(PARAM))
- sym.updateInfo(
- lifted(MethodType(addFree(sym, free = addParams, original = sym.info.params), sym.info.resultType)))
+ def proxiesOrNil(sym: Symbol) = proxies.getOrElse(sym, Nil)
+
+ private def freeParams(sym: Symbol): List[Symbol] =
+ proxiesOrNil(sym)
+
+ private def addFreeParams(tree: Tree, sym: Symbol): Tree =
+ tree match {
+ case DefDef(_, _, _, vparams :: _, _, _) =>
+ val ps = freeParams(sym)
+
+ if (ps.isEmpty) tree
+ else {
+ val paramSyms = cloneSymbols(ps).map(_.setFlag(PARAM))
+ val paramDefs = ps map (p => ValDef(p) setPos tree.pos setType NoType)
+
+ sym.updateInfo(lifted(MethodType(addFree(sym, free = paramSyms, original = sym.info.params), sym.info.resultType)))
+ copyDefDef(tree)(vparamss = List(addFree(sym, free = paramDefs, original = vparams)))
+ }
+
+ case ClassDef(_, _, _, _) =>
+ val freeParamDefs = freeParams(sym) map (p => ValDef(p) setPos tree.pos setType NoType)
+
+ if (freeParamDefs.isEmpty) tree
+ else deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParamDefs))
+
+ case _ => tree
+ }
- copyDefDef(tree)(vparamss = List(addFree(sym, free = freeParams, original = vparams)))
- case ClassDef(_, _, _, _) =>
- // SI-6231
- // Disabled attempt to to add getters to freeParams
- // this does not work yet. Problem is that local symbols need local names
- // and references to local symbols need to be transformed into
- // method calls to setters.
- // def paramGetter(param: Symbol): Tree = {
- // val getter = param.newGetter setFlag TRANS_FLAG resetFlag PARAMACCESSOR // mark because we have to add them to interface
- // sym.info.decls.enter(getter)
- // val rhs = Select(gen.mkAttributedThis(sym), param) setType param.tpe
- // DefDef(getter, rhs) setPos tree.pos setType NoType
- // }
- // val newDefs = if (sym.isTrait) freeParams ::: (ps map paramGetter) else freeParams
- deriveClassDef(tree)(impl => deriveTemplate(impl)(_ ::: freeParams))
- }
- case None =>
- tree
- }
/* SI-6231: Something like this will be necessary to eliminate the implementation
* restriction from paramGetter above:
@@ -451,11 +404,10 @@ abstract class LambdaLift extends InfoTransform {
// See neg/t1909-object.scala
def msg = s"SI-1909 Unable to STATICally lift $sym, which is defined in the self- or super-constructor call of ${sym.owner.owner}. A VerifyError is likely."
devWarning(tree.pos, msg)
- } else sym setFlag STATIC
+ } else sym setFlag STATIC
}
sym.owner = sym.owner.enclClass
- if (sym.isClass) sym.owner = sym.owner.toInterface
if (sym.isMethod) sym setFlag LIFTED
liftedDefs(sym.owner) ::= tree
// TODO: this modifies the ClassInfotype of the enclosing class, which is associated with another phase (explicitouter).
@@ -468,12 +420,11 @@ abstract class LambdaLift extends InfoTransform {
private def postTransform(tree: Tree, isBoxedRef: Boolean = false): Tree = {
val sym = tree.symbol
tree match {
- case ClassDef(_, _, _, _) =>
- val tree1 = addFreeParams(tree, sym)
- if (sym.isLocalToBlock) liftDef(tree1) else tree1
- case DefDef(_, _, _, _, _, _) =>
- val tree1 = addFreeParams(tree, sym)
- if (sym.isLocalToBlock) liftDef(tree1) else tree1
+ case _: ClassDef | _: DefDef =>
+ val withFreeParams = addFreeParams(tree, sym)
+ if (sym.isLocalToBlock) liftDef(withFreeParams)
+ else withFreeParams
+
case ValDef(mods, name, tpt, rhs) =>
if (sym.isCapturedVariable) {
val tpt1 = TypeTree(sym.tpe) setPos tpt.pos
diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala
index b6695efb0b..bc9f70679c 100644
--- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala
+++ b/src/compiler/scala/tools/nsc/transform/LazyVals.scala
@@ -1,14 +1,14 @@
package scala.tools.nsc
package transform
-import scala.collection.{ mutable, immutable }
+import scala.collection.mutable
abstract class LazyVals extends Transform with TypingTransformers with ast.TreeDSL {
// inherits abstract value `global` and class `Phase` from Transform
import global._ // the global environment
import definitions._ // standard classes and methods
- import typer.{typed, atOwner} // methods to type trees
+ import typer.typed // methods to type trees
import CODE._
val phaseName: String = "lazyvals"
@@ -38,6 +38,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) =>
+ // Avoid adding bitmaps when they are fully overshadowed by those that are added inside loops
case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) =>
case _ =>
@@ -54,9 +55,28 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0
import symtab.Flags._
+ private def flattenThickets(stats: List[Tree]): List[Tree] = stats.flatMap(_ match {
+ case b @ Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if b.tpe == null && n1.endsWith(nme.LAZY_SLOW_SUFFIX) =>
+ List(d1, d2)
+ case stat =>
+ List(stat)
+ })
/** Perform the following transformations:
* - for a lazy accessor inside a method, make it check the initialization bitmap
+ * - implement double checked locking of member modules for non-trait owners (trait just have the abstract accessor)
+ * ```
+ * // typer
+ * class C { object x }
+ * // refchecks
+ * class C { var x$module; def x() = { x$module = new x; x$module }
+ * // lazyvals
+ * class C {
+ * var x$module // module var
+ * def x() = { if (x$module == null) x$lzycompute() else x$module // fast path
+ * def x$lzycompute() = { synchronized { if (x$module == null) x$module = new x }; x$module } // slow path
+ * }
+ * ```
* - for all methods, add enough int vars to allow one flag per lazy local value
* - blocks in template bodies behave almost like methods. A single bitmaps section is
* added in the first block, for all lazy values defined in such blocks.
@@ -72,20 +92,14 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
case Block(_, _) =>
val block1 = super.transform(tree)
val Block(stats, expr) = block1
- val stats1 = stats.flatMap(_ match {
- case Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if (nme.newLazyValSlowComputeName(n2) == n1) =>
- List(d1, d2)
- case stat =>
- List(stat)
- })
- treeCopy.Block(block1, stats1, expr)
+ treeCopy.Block(block1, flattenThickets(stats), expr)
case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) {
val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) {
val enclosingClassOrDummyOrMethod = {
val enclMethod = sym.enclMethod
- if (enclMethod != NoSymbol ) {
+ if (enclMethod != NoSymbol) {
val enclClass = sym.enclClass
if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod)
enclClass
@@ -100,30 +114,39 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym)
sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR)
(rhs1, sDef)
- } else
+ } else if (sym.hasAllFlags(MODULE | METHOD) && !sym.owner.isTrait) {
+ rhs match {
+ case b @ Block((assign @ Assign(moduleRef, _)) :: Nil, expr) =>
+ def cond = Apply(Select(moduleRef, Object_eq), List(Literal(Constant(null))))
+ val (fastPath, slowPath) = mkFastPathBody(sym.owner.enclClass, moduleRef.symbol, cond, transform(assign) :: Nil, Nil, transform(expr))
+ (localTyper.typedPos(tree.pos)(fastPath), localTyper.typedPos(tree.pos)(slowPath))
+ case rhs =>
+ global.reporter.error(tree.pos, "Unexpected tree on the RHS of a module accessor: " + rhs)
+ (rhs, EmptyTree)
+ }
+ } else {
(transform(rhs), EmptyTree)
+ }
val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res)
- if (slowPathDef != EmptyTree) Block(slowPathDef, ddef1) else ddef1
+ if (slowPathDef != EmptyTree) {
+ // The contents of this block are flattened into the enclosing statement sequence, see flattenThickets
+ // This is a poor man's version of dotty's Thicket: https://github.com/lampepfl/dotty/blob/d5280358d1/src/dotty/tools/dotc/ast/Trees.scala#L707
+ Block(slowPathDef, ddef1)
+ } else ddef1
}
case Template(_, _, body) => atOwner(currentOwner) {
- val body1 = super.transformTrees(body)
+ // TODO: shady business... can this logic be encapsulated in LocalLazyValFinder?
var added = false
- val stats =
- for (stat <- body1) yield stat match {
- case Block(_, _) | Apply(_, _) | If(_, _, _) | Try(_, _, _) if !added =>
- // Avoid adding bitmaps when they are fully overshadowed by those
- // that are added inside loops
- if (LocalLazyValFinder.find(stat)) {
- added = true
- typed(addBitmapDefs(sym, stat))
- } else stat
- case ValDef(_, _, _, _) =>
- typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _)))
- case _ =>
- stat
+ val stats = super.transformTrees(body) mapConserve {
+ case stat: ValDef => typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _)))
+ case stat: TermTree if !added && (LocalLazyValFinder find stat) =>
+ added = true
+ typed(addBitmapDefs(sym, stat))
+ case stat => stat
}
+
val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) {
// add bitmap to inner class if necessary
val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO)))
@@ -135,7 +158,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
})
toAdd0
} else List()
- deriveTemplate(tree)(_ => innerClassBitmaps ++ stats)
+ deriveTemplate(tree)(_ => innerClassBitmaps ++ flattenThickets(stats))
}
case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass =>
@@ -192,21 +215,23 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
stats: List[Tree], retVal: Tree): Tree = {
- // Q: is there a reason to first set owner to `clazz` (by using clazz.newMethod), and then
- // changing it to lzyVal.owner very soon after? Could we just do lzyVal.owner.newMethod?
- val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE)
+ val owner = lzyVal.owner
+ val defSym = owner.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE)
defSym setInfo MethodType(List(), lzyVal.tpe.resultType)
- defSym.owner = lzyVal.owner
+ if (owner.isClass) owner.info.decls.enter(defSym)
debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal")
- if (bitmaps.contains(lzyVal))
- bitmaps(lzyVal).map(_.owner = defSym)
+ // this is a hack i don't understand for lazy vals nested in a lazy val, introduced in 3769f4d,
+ // tested in pos/t3670 (add9be64). class A { val n = { lazy val b = { lazy val dd = 3; dd }; b } }
+ // bitmaps has an entry bMethodSym -> List(bitmap$0), where bitmap$0.owner == bMethodSym.
+ // now we set bitmap$0.owner = b$lzycomputeMethodSym.
+ for (bitmap <- bitmaps(lzyVal)) bitmap.owner = defSym
val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym)
DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal)))
}
- def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
+ def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree],
stats: List[Tree], retVal: Tree): (Tree, Tree) = {
val slowPathDef: Tree = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal)
(If(cond, Apply(Ident(slowPathDef.symbol), Nil), retVal), slowPathDef)
@@ -261,7 +286,7 @@ abstract class LazyVals extends Transform with TypingTransformers with ast.TreeD
(mkBlock(rhs), UNIT)
}
- val cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind)
+ def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind)
val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res)
(atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 }))
}
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index a079a76ce7..19ba9345fa 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -8,7 +8,8 @@ package transform
import symtab._
import Flags._
-import scala.collection.{ mutable, immutable }
+import scala.annotation.tailrec
+import scala.collection.mutable
abstract class Mixin extends InfoTransform with ast.TreeDSL {
import global._
@@ -32,8 +33,9 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// --------- helper functions -----------------------------------------------
/** A member of a trait is implemented statically if its implementation after the
- * mixin transform is in the static implementation module. To be statically
- * implemented, a member must be a method that belonged to the trait's implementation class
+ * mixin transform is RHS of the method body (destined to be in a interface default method)
+ *
+ * To be statically implemented, a member must be a method that belonged to the trait's implementation class
* before (i.e. it is not abstract). Not statically implemented are
* - non-private modules: these are implemented directly in the mixin composition class
* (private modules, on the other hand, are implemented statically, but their
@@ -43,33 +45,17 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
* methods in the impl class (because they can have arbitrary initializers)
*/
private def isImplementedStatically(sym: Symbol) = (
- sym.owner.isImplClass
- && sym.isMethod
+ sym.isMethod
+ && (!sym.hasFlag(DEFERRED | SUPERACCESSOR) || (sym hasFlag lateDEFERRED))
+ && sym.owner.isTrait
&& (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED))
&& (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy)
+ && !sym.isPrivate
+ && !sym.hasAllFlags(LIFTED | MODULE | METHOD)
+ && !sym.isConstructor
+ && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE))
)
- /** A member of a trait is static only if it belongs only to the
- * implementation class, not the interface, and it is implemented
- * statically.
- */
- private def isStaticOnly(sym: Symbol) =
- isImplementedStatically(sym) && sym.isImplOnly
-
- /** A member of a trait is forwarded if it is implemented statically and it
- * is also visible in the trait's interface. In that case, a forwarder to
- * the member's static implementation will be added to the class that
- * inherits the trait.
- */
- private def isForwarded(sym: Symbol) =
- isImplementedStatically(sym) && !sym.isImplOnly
-
- /** Maps the type of an implementation class to its interface;
- * maps all other types to themselves.
- */
- private def toInterface(tp: Type): Type =
- enteringMixin(tp.typeSymbol.toInterface).tpe
-
private def isFieldWithBitmap(field: Symbol) = {
field.info // ensure that nested objects are transformed
// For checkinit consider normal value getters
@@ -97,22 +83,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
&& !(sym.accessed hasAnnotation TransientAttr)
)
- /** Maps all parts of this type that refer to implementation classes to
- * their corresponding interfaces.
- */
- private val toInterfaceMap = new TypeMap {
- def apply(tp: Type): Type = mapOver( tp match {
- case TypeRef(pre, sym, args) if sym.isImplClass =>
- typeRef(pre, enteringMixin(sym.toInterface), args)
- case _ => tp
- })
- }
-
- /** The implementation class corresponding to a currently compiled interface.
- * todo: try to use Symbol.implClass instead?
- */
- private def implClass(iface: Symbol) = iface.implClass orElse (erasure implClass iface)
-
/** Returns the symbol that is accessed by a super-accessor in a mixin composition.
*
* @param base The class in which everything is mixed together
@@ -159,8 +129,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
/** Add given member to given class, and mark member as mixed-in.
*/
def addMember(clazz: Symbol, member: Symbol): Symbol = {
- debuglog("new member of " + clazz + ":" + member.defString)
- clazz.info.decls enter member setFlag MIXEDIN
+ debuglog(s"mixing into $clazz: ${member.defString}")
+ clazz.info.decls enter member setFlag MIXEDIN resetFlag JAVA_DEFAULTMETHOD
}
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz))
@@ -227,12 +197,11 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
}
clazz.info // make sure info is up to date, so that implClass is set.
- val impl = implClass(clazz) orElse abort("No impl class for " + clazz)
- for (member <- impl.info.decls) {
+ for (member <- clazz.info.decls) {
if (!member.isMethod && !member.isModule && !member.isModuleVar) {
assert(member.isTerm && !member.isDeferred, member)
- if (member.getterIn(impl).isPrivate) {
+ if (member.getterIn(clazz).isPrivate) {
member.makeNotPrivate(clazz) // this will also make getter&setter not private
}
val getter = member.getterIn(clazz)
@@ -241,6 +210,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val setter = member.setterIn(clazz)
if (setter == NoSymbol) addMember(clazz, newSetter(member))
}
+ clazz.info.decls.unlink(member)
}
}
debuglog("new defs of " + clazz + " = " + clazz.info.decls)
@@ -266,16 +236,51 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
)
/* Mix in members of implementation class mixinClass into class clazz */
- def mixinImplClassMembers(mixinClass: Symbol, mixinInterface: Symbol) {
- if (!mixinClass.isImplClass) devWarning ("Impl class flag is not set " +
- ((mixinClass.debugLocationString, mixinInterface.debugLocationString)))
-
- for (member <- mixinClass.info.decls ; if isForwarded(member)) {
- val imember = member overriddenSymbol mixinInterface
- imember overridingSymbol clazz match {
+ def mixinTraitForwarders(mixinClass: Symbol) {
+ for (member <- mixinClass.info.decls ; if isImplementedStatically(member)) {
+ member overridingSymbol clazz match {
case NoSymbol =>
- if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember)
- cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
+ val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member)
+ if (isMemberOfClazz) {
+ // `member` is a concrete method defined in `mixinClass`, which is a base class of
+ // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if:
+ //
+ // - A non-trait base class of `clazz` defines a matching method. Example:
+ // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T
+ // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would
+ // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual.
+ //
+ // - There exists another concrete, matching method in a parent interface `p` of
+ // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the
+ // forwarder is needed to disambiguate. Example:
+ // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2
+ // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves
+ // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method".
+ // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2
+ // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM
+ // level.
+
+ @tailrec
+ def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match {
+ case baseClass :: rest =>
+ if (baseClass ne mixinClass) {
+ val m = member.overriddenSymbol(baseClass)
+ val isCompeting = m.exists && {
+ !m.owner.isTraitOrInterface ||
+ (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner))
+ }
+ isCompeting || existsCompetingMethod(rest)
+ } else existsCompetingMethod(rest)
+
+ case _ => false
+ }
+
+ if (existsCompetingMethod(clazz.baseClasses))
+ cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member
+ else if (!settings.nowarnDefaultJunitMethods && JUnitTestClass.exists && member.hasAnnotation(JUnitTestClass))
+ warning(member.pos, "JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.")
+ }
+
case _ =>
}
}
@@ -296,7 +301,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember)
if (mixinMember.isLazy) {
initializer(mixedInAccessor) = (
- implClass(mixinClass).info.decl(mixinMember.name)
+ mixinClass.info.decl(mixinMember.name)
orElse abort("Could not find initializer for " + mixinMember.name)
)
}
@@ -358,67 +363,16 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// first complete the superclass with mixed in members
addMixedinMembers(clazz.superClass, unit)
- for (mc <- clazz.mixinClasses ; if mc hasFlag lateINTERFACE) {
+ for (mc <- clazz.mixinClasses ; if mc.isTrait) {
// @SEAN: adding trait tracking so we don't have to recompile transitive closures
unit.depends += mc
addLateInterfaceMembers(mc)
mixinTraitMembers(mc)
- mixinImplClassMembers(implClass(mc), mc)
+ mixinTraitForwarders(mc)
}
}
- /** The info transform for this phase does the following:
- * - The parents of every class are mapped from implementation class to interface
- * - Implementation classes become modules that inherit nothing
- * and that define all.
- */
- override def transformInfo(sym: Symbol, tp: Type): Type = tp match {
- case ClassInfoType(parents, decls, clazz) =>
- var parents1 = parents
- var decls1 = decls
- if (!clazz.isPackageClass) {
- exitingMixin(clazz.owner.info)
- if (clazz.isImplClass) {
- clazz setFlag lateMODULE
- var sourceModule = clazz.owner.info.decls.lookup(sym.name.toTermName)
- if (sourceModule == NoSymbol) {
- sourceModule = (
- clazz.owner.newModuleSymbol(sym.name.toTermName, sym.pos, MODULE)
- setModuleClass sym.asInstanceOf[ClassSymbol]
- )
- clazz.owner.info.decls enter sourceModule
- }
- else {
- sourceModule setPos sym.pos
- if (sourceModule.flags != MODULE) {
- log(s"!!! Directly setting sourceModule flags for $sourceModule from ${sourceModule.flagString} to MODULE")
- sourceModule.flags = MODULE
- }
- }
- sourceModule setInfo sym.tpe
- // Companion module isn't visible for anonymous class at this point anyway
- assert(clazz.sourceModule != NoSymbol || clazz.isAnonymousClass, s"$clazz has no sourceModule: $sym ${sym.tpe}")
- parents1 = List()
- decls1 = newScopeWith(decls.toList filter isImplementedStatically: _*)
- } else if (!parents.isEmpty) {
- parents1 = parents.head :: (parents.tail map toInterface)
- }
- }
- //decls1 = enteringPhase(phase.next)(newScopeWith(decls1.toList: _*))//debug
- if ((parents1 eq parents) && (decls1 eq decls)) tp
- else ClassInfoType(parents1, decls1, clazz)
-
- case MethodType(params, restp) =>
- toInterfaceMap(
- if (isImplementedStatically(sym)) {
- val ownerParam = sym.newSyntheticValueParam(toInterface(sym.owner.typeOfThis))
- MethodType(ownerParam :: params, restp)
- } else
- tp)
-
- case _ =>
- tp
- }
+ override def transformInfo(sym: Symbol, tp: Type): Type = tp
/** Return a map of single-use fields to the lazy value that uses them during initialization.
* Each field has to be private and defined in the enclosing class, and there must
@@ -466,10 +420,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
new MixinTransformer(unit)
class MixinTransformer(unit : CompilationUnit) extends Transformer {
- /** Within a static implementation method: the parameter referring to the
- * current object. Undefined everywhere else.
- */
- private var self: Symbol = _
/** The rootContext used for typing */
private val rootContext =
@@ -505,15 +455,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
* (that is, every node is processed before its children).
* What transform does:
* - For every non-trait class, add all mixed in members to the class info.
- * - For every trait, add all late interface members to the class info
- * - For every static implementation method:
- * - remove override flag
- * - create a new method definition that also has a `self` parameter
- * (which comes first) Iuli: this position is assumed by tail call elimination
- * on a different receiver. Storing a new 'this' assumes it is located at
- * index 0 in the local variable table. See 'STORE_THIS' and GenASM.
- * - Map implementation class types in type-apply's to their interfaces
- * - Remove all fields in implementation classes
*/
private def preTransform(tree: Tree): Tree = {
val sym = tree.symbol
@@ -524,87 +465,19 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
if (!currentOwner.isTrait && !isPrimitiveValueClass(currentOwner))
addMixedinMembers(currentOwner, unit)
- else if (currentOwner hasFlag lateINTERFACE)
+ else if (currentOwner.isTrait)
addLateInterfaceMembers(currentOwner)
tree
- case DefDef(_, _, _, vparams :: Nil, _, _) =>
- if (currentOwner.isImplClass) {
- if (isImplementedStatically(sym)) {
- sym setFlag notOVERRIDE
- self = sym.newValueParameter(nme.SELF, sym.pos) setInfo toInterface(currentOwner.typeOfThis)
- val selfdef = ValDef(self) setType NoType
- copyDefDef(tree)(vparamss = List(selfdef :: vparams))
- }
- else EmptyTree
- }
- else {
- if (currentOwner.isTrait && sym.isSetter && !enteringPickler(sym.isDeferred)) {
- sym.addAnnotation(TraitSetterAnnotationClass)
- }
- tree
- }
- // !!! What is this doing, and why is it only looking for exactly
- // one type parameter? It would seem to be
- // "Map implementation class types in type-apply's to their interfaces"
- // from the comment on preTransform, but is there some way we should know
- // that impl class types in type applies can only appear in single
- // type parameter type constructors?
- case Apply(tapp @ TypeApply(fn, List(arg)), List()) =>
- if (arg.tpe.typeSymbol.isImplClass) {
- val ifacetpe = toInterface(arg.tpe)
- arg setType ifacetpe
- tapp setType MethodType(Nil, ifacetpe)
- tree setType ifacetpe
- }
- tree
- case ValDef(_, _, _, _) if currentOwner.isImplClass =>
- EmptyTree
+
case _ =>
+ if (currentOwner.isTrait && sym.isSetter && !enteringPickler(sym.isDeferred)) {
+ sym.addAnnotation(TraitSetterAnnotationClass)
+ }
tree
}
}
- /** Create an identifier which references self parameter.
- */
- private def selfRef(pos: Position) =
- gen.mkAttributedIdent(self) setPos pos
-
- /** Replace a super reference by this or the self parameter, depending
- * on whether we are in an implementation class or not.
- * Leave all other trees unchanged.
- */
- private def transformSuper(tree: Tree) = tree match {
- case Super(qual, _) =>
- transformThis(qual)
- case _ =>
- tree
- }
-
- /** Replace a this reference to the current implementation class by the self
- * parameter. Leave all other trees unchanged.
- */
- private def transformThis(tree: Tree) = tree match {
- case This(_) if tree.symbol.isImplClass =>
- assert(tree.symbol == currentOwner.enclClass)
- selfRef(tree.pos)
- case _ =>
- tree
- }
-
- /** Create a static reference to given symbol `sym` of the
- * form `M.sym` where M is the symbol's implementation module.
- */
- private def staticRef(sym: Symbol): Tree = {
- sym.owner.info //todo: needed?
- sym.owner.owner.info //todo: needed?
-
- if (sym.owner.sourceModule eq NoSymbol)
- abort(s"Cannot create static reference to $sym because ${sym.safeOwner} has no source module")
- else
- REF(sym.owner.sourceModule) DOT sym
- }
-
def needsInitAndHasOffset(sym: Symbol) =
needsInitFlag(sym) && (fieldOffset contains sym)
@@ -643,6 +516,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
* - A super accessor for every super accessor in a mixin class
* - Forwarders for all methods that are implemented statically
* All superaccessors are completed with right-hand sides (@see completeSuperAccessor)
+ *
* @param clazz The class to which definitions are added
*/
private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = {
@@ -700,7 +574,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
*/
def completeSuperAccessor(stat: Tree) = stat match {
case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor =>
- val body = atPos(stat.pos)(Apply(Select(Super(clazz, tpnme.EMPTY), stat.symbol.alias), vparams map (v => Ident(v.symbol))))
+ val body = atPos(stat.pos)(Apply(SuperSelect(clazz, stat.symbol.alias), vparams map (v => Ident(v.symbol))))
val pt = stat.symbol.tpe.resultType
copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt))))
@@ -778,18 +652,18 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, PRIVATE)
val params = defSym newSyntheticValueParams args.map(_.symbol.tpe)
defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType)
- val rhs: Tree = (gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats)).changeOwner(currentOwner -> defSym)
+ val rhs: Tree = gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats).changeOwner(currentOwner -> defSym)
val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params)
addDef(position(defSym), DefDef(defSym, strictSubst(BLOCK(rhs, retVal))))
defSym
}
- def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
+ def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree],
stats: List[Tree], retVal: Tree): Tree = {
mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List())
}
- def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree],
+ def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree],
stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = {
val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args)
If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal)
@@ -861,16 +735,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal))
}
- def mkInnerClassAccessorDoubleChecked(attrThis: Tree, rhs: Tree, moduleSym: Symbol, args: List[Tree]): Tree =
- rhs match {
- case Block(List(assign), returnTree) =>
- val Assign(moduleVarRef, _) = assign
- val cond = Apply(Select(moduleVarRef, Object_eq), List(NULL))
- mkFastPathBody(clazz, moduleSym, cond, List(assign), List(NULL), returnTree, attrThis, args)
- case _ =>
- abort(s"Invalid getter $rhs for module in $clazz")
- }
-
def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = {
val sym = fieldSym.getterIn(fieldSym.owner)
val bitmapSym = bitmapFor(clazz, offset, sym)
@@ -897,7 +761,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
def isUnit = sym.tpe.resultType.typeSymbol == UnitClass
def isEmpty = stat.rhs == EmptyTree
- if (sym.isLazy && !isEmpty && !clazz.isImplClass) {
+ if (!clazz.isTrait && sym.isLazy && !isEmpty) {
assert(fieldOffset contains sym, sym)
deriveDefDef(stat) {
case t if isUnit => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym))
@@ -908,7 +772,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
case t => t // pass specialized lazy vals through
}
}
- else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(IMPLCLASS | TRAIT)) {
+ else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(TRAIT)) {
assert(fieldOffset contains sym, sym)
deriveDefDef(stat)(rhs =>
(mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))(
@@ -926,18 +790,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT))
else stat
}
- else if (sym.isModule && (!clazz.isTrait || clazz.isImplClass) && !sym.isBridge) {
- deriveDefDef(stat)(rhs =>
- typedPos(stat.pos)(
- mkInnerClassAccessorDoubleChecked(
- // Martin to Hubert: I think this can be replaced by selfRef(tree.pos)
- // @PP: It does not seem so, it crashes for me trying to bootstrap.
- if (clazz.isImplClass) gen.mkAttributedIdent(stat.vparamss.head.head.symbol) else gen.mkAttributedThis(clazz),
- rhs, sym, stat.vparamss.head
- )
- )
- )
- }
else stat
}
stats map {
@@ -1014,7 +866,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// if it is a mixed-in lazy value, complete the accessor
if (getter.isLazy) {
val isUnit = isUnitGetter(getter)
- val initCall = Apply(staticRef(initializer(getter)), gen.mkAttributedThis(clazz) :: Nil)
+ val initCall = Apply(SuperSelect(clazz, initializer(getter)), Nil)
val selection = fieldAccess(getter)
val init = if (isUnit) initCall else atPos(getter.pos)(Assign(selection, initCall))
val returns = if (isUnit) UNIT else selection
@@ -1062,12 +914,11 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// for all symbols `sym` in the class definition, which are mixed in:
for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) {
- // if current class is a trait interface, add an abstract method for accessor `sym`
- if (clazz hasFlag lateINTERFACE) {
+ // if current class is a trait, add an abstract method for accessor `sym`
+ if (clazz.isTrait) {
addDefDef(sym)
- }
- // if class is not a trait add accessor definitions
- else if (!clazz.isTrait) {
+ } else {
+ // if class is not a trait add accessor definitions
if (isConcreteAccessor(sym)) {
// add accessor definitions
addDefDef(sym, {
@@ -1082,18 +933,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
})
}
else if (sym.isModule && !(sym hasFlag LIFTED | BRIDGE)) {
- // add modules
- val vsym = sym.owner.newModuleVarSymbol(sym)
- addDef(position(sym), ValDef(vsym))
-
- // !!! TODO - unravel the enormous duplication between this code and
- // eliminateModuleDefs in RefChecks.
- val rhs = gen.newModule(sym, vsym.tpe)
- val assignAndRet = gen.mkAssignAndReturn(vsym, rhs)
- val attrThis = gen.mkAttributedThis(clazz)
- val rhs1 = mkInnerClassAccessorDoubleChecked(attrThis, assignAndRet, sym, List())
-
- addDefDef(sym, rhs1)
+ // Moved to Refchecks
}
else if (!sym.isMethod) {
// add fields
@@ -1105,13 +945,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
}
else {
// add forwarders
- assert(sym.alias != NoSymbol, sym)
+ assert(sym.alias != NoSymbol, (sym, sym.debugFlagString, clazz))
// debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString)
- if (!sym.isMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident)))
+ if (!sym.isMacro) addDefDef(sym, Apply(SuperSelect(clazz, sym.alias), sym.paramss.head.map(Ident(_))))
}
}
}
stats1 = add(stats1, newDefs.toList)
+ if (clazz.isTrait) stats1 =
+ stats1.filter {
+ case vd: ValDef =>
+ // TODO do we get here?
+ false
+ case _ => true
+ }
if (!clazz.isTrait) stats1 = stats1 map completeSuperAccessor
stats1
}
@@ -1146,14 +993,8 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
* - refer to fields in some implementation class via an abstract method in the interface.
*/
private def postTransform(tree: Tree): Tree = {
- def siteWithinImplClass = currentOwner.enclClass.isImplClass
val sym = tree.symbol
- // change every node type that refers to an implementation class to its
- // corresponding interface, unless the node's symbol is an implementation class.
- if (tree.tpe.typeSymbol.isImplClass && ((sym eq null) || !sym.isImplClass))
- tree modifyType toInterface
-
tree match {
case templ @ Template(parents, self, body) =>
// change parents of templates to conform to parents in the symbol info
@@ -1163,87 +1004,21 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
// add all new definitions to current class or interface
treeCopy.Template(tree, parents1, self, addNewDefs(currentOwner, body))
- // remove widening casts
- case Apply(TypeApply(Select(qual, _), targ :: _), _) if isCastSymbol(sym) && (qual.tpe <:< targ.tpe) =>
- qual
-
- case Apply(Select(qual, _), args) =>
- /* Changes `qual.m(args)` where m refers to an implementation
- * class method to Q.m(S, args) where Q is the implementation module of
- * `m` and S is the self parameter for the call, which
- * is determined as follows:
- * - if qual != super, qual itself
- * - if qual == super, and we are in an implementation class,
- * the current self parameter.
- * - if qual == super, and we are not in an implementation class, `this`
- */
- def staticCall(target: Symbol) = {
- def implSym = implClass(sym.owner).info.member(sym.name)
- assert(target ne NoSymbol,
- List(sym + ":", sym.tpe, sym.owner, implClass(sym.owner), implSym,
- enteringPrevPhase(implSym.tpe), phase) mkString " "
- )
- typedPos(tree.pos)(Apply(staticRef(target), transformSuper(qual) :: args))
- }
+ case Select(qual, name) if sym.owner.isTrait && !sym.isMethod =>
+ // refer to fields in some trait an abstract getter in the interface.
+ val ifaceGetter = sym getterIn sym.owner
- if (isStaticOnly(sym)) {
- // change calls to methods which are defined only in implementation
- // classes to static calls of methods in implementation modules
- staticCall(sym)
- }
- else qual match {
- case Super(_, mix) =>
- // change super calls to methods in implementation classes to static calls.
- // Transform references super.m(args) as follows:
- // - if `m` refers to a trait, insert a static call to the corresponding static
- // implementation
- // - otherwise return tree unchanged
- assert(
- !(mix == tpnme.EMPTY && siteWithinImplClass),
- "illegal super in trait: " + currentOwner.enclClass + " " + tree
- )
-
- if (sym.owner hasFlag lateINTERFACE) {
- if (sym.hasAccessorFlag) {
- assert(args.isEmpty, args)
- val sym1 = sym.overridingSymbol(currentOwner.enclClass)
- typedPos(tree.pos)((transformSuper(qual) DOT sym1)())
- }
- else {
- staticCall(enteringPrevPhase(sym.overridingSymbol(implClass(sym.owner))))
- }
- }
- else {
- assert(!siteWithinImplClass, currentOwner.enclClass)
- tree
- }
- case _ =>
- tree
- }
-
- case This(_) =>
- transformThis(tree)
-
- case Select(Super(_, _), name) =>
- tree
-
- case Select(qual, name) if sym.owner.isImplClass && !isStaticOnly(sym) =>
- assert(!sym.isMethod, "no method allowed here: %s%s %s".format(sym, sym.isImplOnly, sym.flagString))
- // refer to fields in some implementation class via an abstract
- // getter in the interface.
- val iface = toInterface(sym.owner.tpe).typeSymbol
- val ifaceGetter = sym getterIn iface
-
- if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + iface)
+ if (ifaceGetter == NoSymbol) abort("No getter for " + sym + " in " + sym.owner)
else typedPos(tree.pos)((qual DOT ifaceGetter)())
case Assign(Apply(lhs @ Select(qual, _), List()), rhs) =>
- // assign to fields in some implementation class via an abstract
- // setter in the interface.
- def setter = lhs.symbol.setterIn(toInterface(lhs.symbol.owner.tpe).typeSymbol) setPos lhs.pos
+ // assign to fields in some trait via an abstract setter in the interface.
+ // Note that the case above has added the empty application.
+ val setter = lhs.symbol.setterIn(lhs.symbol.owner.tpe.typeSymbol) setPos lhs.pos
typedPos(tree.pos)((qual DOT setter)(rhs))
+
case _ =>
tree
}
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 53a1347a48..4b1f1efee4 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -9,8 +9,6 @@ package transform
import scala.tools.nsc.symtab.Flags
import scala.collection.{ mutable, immutable }
-import scala.language.postfixOps
-import scala.language.existentials
import scala.annotation.tailrec
/** Specialize code on types.
@@ -168,7 +166,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
/** Reduce the given environment to contain mappings only for type variables in tps. */
def restrict(env: TypeEnv, tps: immutable.Set[Symbol]): TypeEnv =
- env filterKeys tps toMap
+ env.filterKeys(tps).toMap
/** Is the given environment a valid specialization for sym?
* It is valid if each binding is from a @specialized type parameter in sym (or its owner)
@@ -285,6 +283,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe))
yield tpe
+ /** Is `member` potentially affected by specialization? This is a gross overapproximation,
+ * but it should be okay for use outside of specialization.
+ */
+ def possiblySpecialized(sym: Symbol) = specializedTypeVars(sym).nonEmpty
+
+ /** Refines possiblySpecialized taking into account the instantiation of the specialized type variables at `site` */
+ def isSpecializedIn(sym: Symbol, site: Type) =
+ specializedTypeVars(sym) exists { tvar =>
+ val concretes = concreteTypes(tvar)
+ (concretes contains AnyRefClass) || (concretes contains site.memberType(tvar))
+ }
+
+
val specializedType = new TypeMap {
override def apply(tp: Type): Type = tp match {
case TypeRef(pre, sym, args) if args.nonEmpty =>
@@ -354,7 +365,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
)
- lazy val specializableTypes = ScalaValueClasses map (_.tpe) sorted
+ lazy val specializableTypes = ScalaValueClasses.map(_.tpe).sorted
/** If the symbol is the companion of a value class, the value class.
* Otherwise, AnyRef.
@@ -373,7 +384,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
val types = if (!sym.isSpecialized)
Nil // no @specialized Annotation
else
- specializedOn(sym) map (s => specializesClass(s).tpe) sorted
+ specializedOn(sym).map(s => specializesClass(s).tpe).sorted
if (isBoundedGeneric(sym.tpe) && (types contains AnyRefClass))
reporter.warning(sym.pos, sym + " is always a subtype of " + AnyRefTpe + ".")
@@ -461,7 +472,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
case ExistentialType(_, res) => specializedTypeVars(res)
case AnnotatedType(_, tp) => specializedTypeVars(tp)
case TypeBounds(lo, hi) => specializedTypeVars(lo :: hi :: Nil)
- case RefinedType(parents, _) => parents flatMap specializedTypeVars toSet
+ case RefinedType(parents, _) => parents.flatMap(specializedTypeVars).toSet
case _ => immutable.Set.empty
}
@@ -697,7 +708,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
else
debuglog("conflicting env for " + m + " env: " + env)
}
- else if (m.isDeferred) { // abstract methods
+ else if (m.isDeferred && m.isSpecialized) { // abstract methods
val specMember = enterMember(cloneInSpecializedClass(m, _ | DEFERRED))
// debuglog("deferred " + specMember.fullName + " remains abstract")
@@ -705,11 +716,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
// was: new Forward(specMember) {
// override def target = m.owner.info.member(specializedName(m, env))
// }
- } else if (m.isMethod && !m.hasAccessorFlag) { // other concrete methods
+ } else if (!sClass.isTrait && m.isMethod && !m.hasAccessorFlag) { // other concrete methods
// log("other concrete " + m)
forwardToOverload(m)
- } else if (m.isMethod && m.hasFlag(LAZY)) {
+ } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) {
forwardToOverload(m)
} else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition
@@ -848,7 +859,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
if (unusedStvars.length == 1) "is" else "are")
)
unusedStvars foreach (_ removeAnnotation SpecializedClass)
- specializingOn = specializingOn filterNot (unusedStvars contains)
+ specializingOn = specializingOn filterNot (unusedStvars contains _)
}
for (env0 <- specializations(specializingOn) if needsSpecialization(env0, sym)) yield {
// !!! Can't this logic be structured so that the new symbol's name is
@@ -1008,7 +1019,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
case (NoSymbol, _) =>
if (overriding.isSuperAccessor) {
val alias = overriding.alias
- debuglog("checking special overload for super accessor: %s, alias for %s".format(overriding.fullName, alias.fullName))
+ debuglog(s"checking special overload for super accessor: ${overriding.fullName}, alias for ${alias.fullName}")
needsSpecialOverride(alias) match {
case nope @ (NoSymbol, _) => None
case (overridden, env) =>
@@ -1030,7 +1041,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
param.name = overriding.paramss(i)(j).name // SI-6555 Retain the parameter names from the subclass.
}
}
- debuglog("specialized overload %s for %s in %s: %s".format(om, overriding.name.decode, pp(env), om.info))
+ debuglog(s"specialized overload $om for ${overriding.name.decode} in ${pp(env)}: ${om.info}")
if (overriding.isAbstractOverride) om.setFlag(ABSOVERRIDE)
typeEnv(om) = env
addConcreteSpecMethod(overriding)
@@ -1079,7 +1090,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
*/
private def unify(tp1: Type, tp2: Type, env: TypeEnv, strict: Boolean, tparams: Boolean = false): TypeEnv = (tp1, tp2) match {
case (TypeRef(_, sym1, _), _) if sym1.isSpecialized =>
- debuglog("Unify " + tp1 + ", " + tp2)
+ debuglog(s"Unify $tp1, $tp2")
if (isPrimitiveValueClass(tp2.typeSymbol) || isSpecializedAnyRefSubtype(tp2, sym1))
env + ((sym1, tp2))
else if (isSpecializedAnyRefSubtype(tp2, sym1))
@@ -1090,20 +1101,20 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
env
case (TypeRef(_, sym1, args1), TypeRef(_, sym2, args2)) =>
if (args1.nonEmpty || args2.nonEmpty)
- debuglog("Unify types " + tp1 + " and " + tp2)
+ debuglog(s"Unify types $tp1 and $tp2")
if (strict && args1.length != args2.length) unifyError(tp1, tp2)
val e = unify(args1, args2, env, strict)
- if (e.nonEmpty) debuglog("unified to: " + e)
+ if (e.nonEmpty) debuglog(s"unified to: $e")
e
case (TypeRef(_, sym1, _), _) if sym1.isTypeParameterOrSkolem =>
env
case (MethodType(params1, res1), MethodType(params2, res2)) =>
if (strict && params1.length != params2.length) unifyError(tp1, tp2)
- debuglog("Unify methods " + tp1 + " and " + tp2)
+ debuglog(s"Unify methods $tp1 and $tp2")
unify(res1 :: (params1 map (_.tpe)), res2 :: (params2 map (_.tpe)), env, strict)
case (PolyType(tparams1, res1), PolyType(tparams2, res2)) =>
- debuglog("Unify polytypes " + tp1 + " and " + tp2)
+ debuglog(s"Unify polytypes $tp1 and $tp2")
if (strict && tparams1.length != tparams2.length)
unifyError(tp1, tp2)
else if (tparams && tparams1.length == tparams2.length)
@@ -1121,7 +1132,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
case (ExistentialType(_, res1), _) => unify(tp2, res1, env, strict)
case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) => unify(List(lo1, hi1), List(lo2, hi2), env, strict)
case _ =>
- debuglog("don't know how to unify %s [%s] with %s [%s]".format(tp1, tp1.getClass, tp2, tp2.getClass))
+ debuglog(s"don't know how to unify $tp1 [${tp1.getClass}] with $tp2 [${tp2.getClass}]")
env
}
@@ -1131,9 +1142,9 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
if (!strict) unify(args._1, args._2, env, strict)
else {
val nenv = unify(args._1, args._2, emptyEnv, strict)
- if (env.keySet intersect nenv.keySet isEmpty) env ++ nenv
+ if (env.keySet.intersect(nenv.keySet).isEmpty) env ++ nenv
else {
- debuglog("could not unify: u(" + args._1 + ", " + args._2 + ") yields " + nenv + ", env: " + env)
+ debuglog(s"could not unify: u(${args._1}, ${args._2}) yields $nenv, env: $env")
unifyError(tp1, tp2)
}
}
@@ -1229,7 +1240,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
env forall { case (tvar, tpe) =>
matches(tvar.info.bounds.lo, tpe) && matches(tpe, tvar.info.bounds.hi) || {
if (warnings)
- reporter.warning(tvar.pos, "Bounds prevent specialization of " + tvar)
+ reporter.warning(tvar.pos, s"Bounds prevent specialization of $tvar")
debuglog("specvars: " +
tvar.info.bounds.lo + ": " +
@@ -1360,7 +1371,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
sym, currentClass, sym.owner.enclClass, isAccessible(sym), nme.isLocalName(sym.name))
)
if (shouldMakePublic(sym) && !isAccessible(sym)) {
- debuglog("changing private flag of " + sym)
+ debuglog(s"changing private flag of $sym")
sym.makeNotPrivate(sym.owner)
}
super.transform(tree)
@@ -1415,10 +1426,10 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
(treeType =:= memberType) || { // anyref specialization
memberType match {
case PolyType(_, resTpe) =>
- debuglog("Conformance for anyref - polytype with result type: " + resTpe + " and " + treeType + "\nOrig. sym.: " + origSymbol)
+ debuglog(s"Conformance for anyref - polytype with result type: $resTpe and $treeType\nOrig. sym.: $origSymbol")
try {
val e = unify(origSymbol.tpe, memberType, emptyEnv, true)
- debuglog("obtained env: " + e)
+ debuglog(s"obtained env: $e")
e.keySet == env.keySet
} catch {
case _: Throwable =>
@@ -1518,7 +1529,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
)
val tree1 = gen.mkTypeApply(specTree, residualTargs)
- debuglog("rewrote " + tree + " to " + tree1)
+ debuglog(s"rewrote $tree to $tree1")
localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method
}
@@ -1526,7 +1537,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
tree match {
case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) =>
def transformNew = {
- debuglog("Attempting to specialize new %s(%s)".format(tpt, args.mkString(", ")))
+ debuglog(s"Attempting to specialize new $tpt(${args.mkString(", ")})")
val found = specializedType(tpt.tpe)
if (found.typeSymbol ne tpt.tpe.typeSymbol) { // the ctor can be specialized
val inst = New(found, transformTrees(args): _*)
diff --git a/src/compiler/scala/tools/nsc/transform/Statics.scala b/src/compiler/scala/tools/nsc/transform/Statics.scala
index 4673be6de7..9ab00f1a83 100644
--- a/src/compiler/scala/tools/nsc/transform/Statics.scala
+++ b/src/compiler/scala/tools/nsc/transform/Statics.scala
@@ -1,49 +1,32 @@
package scala.tools.nsc
package transform
-import collection.mutable.Buffer
-
abstract class Statics extends Transform with ast.TreeDSL {
import global._
class StaticsTransformer extends Transformer {
-
- /** finds the static ctor DefDef tree within the template if it exists. */
- def findStaticCtor(template: Template): Option[Tree] =
- template.body find {
- case defdef @ DefDef(_, nme.CONSTRUCTOR, _, _, _, _) => defdef.symbol.hasStaticFlag
- case _ => false
- }
-
- /** changes the template for the class so that it contains a static constructor with symbol fields inits,
- * augments an existing static ctor if one already existed.
+ /** generate a static constructor with symbol fields inits, or an augmented existing static ctor
*/
- def addStaticInits(template: Template, newStaticInits: Buffer[Tree], localTyper: analyzer.Typer): Template = {
- if (newStaticInits.isEmpty)
- template
- else {
- val newCtor = findStaticCtor(template) match {
- // in case there already were static ctors - augment existing ones
- // currently, however, static ctors aren't being generated anywhere else
- case Some(ctor @ DefDef(_,_,_,_,_,_)) =>
- // modify existing static ctor
- deriveDefDef(ctor) {
- case block @ Block(stats, expr) =>
- // need to add inits to existing block
- treeCopy.Block(block, newStaticInits.toList ::: stats, expr)
- case term: TermTree =>
- // need to create a new block with inits and the old term
- treeCopy.Block(term, newStaticInits.toList, term)
- }
- case _ =>
- // create new static ctor
- val staticCtorSym = currentClass.newStaticConstructor(template.pos)
- val rhs = Block(newStaticInits.toList, Literal(Constant(())))
+ def staticConstructor(body: List[Tree], localTyper: analyzer.Typer, pos: Position)(newStaticInits: List[Tree]): Tree =
+ body.collectFirst {
+ // If there already was a static ctor - augment existing one
+ // currently, however, static ctors aren't being generated anywhere else (!!!)
+ case ctor@DefDef(_, nme.CONSTRUCTOR, _, _, _, _) if ctor.symbol.hasStaticFlag =>
+ // modify existing static ctor
+ deriveDefDef(ctor) {
+ case block@Block(stats, expr) =>
+ // need to add inits to existing block
+ treeCopy.Block(block, newStaticInits ::: stats, expr)
+ case term: TermTree =>
+ // need to create a new block with inits and the old term
+ treeCopy.Block(term, newStaticInits, term)
+ }
+ } getOrElse {
+ // create new static ctor
+ val staticCtorSym = currentClass.newStaticConstructor(pos)
+ val rhs = Block(newStaticInits, Literal(Constant(())))
- localTyper.typedPos(template.pos)(DefDef(staticCtorSym, rhs))
- }
- deriveTemplate(template)(newCtor :: _)
+ localTyper.typedPos(pos)(DefDef(staticCtorSym, rhs))
}
- }
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala
index 16ea3ea90f..fa7c503213 100644
--- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala
+++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala
@@ -69,7 +69,7 @@ abstract class TailCalls extends Transform {
* are optimized. Since 'this' is not a local variable, a dummy local val
* is added and used as a label parameter. The backend knows to load
* the corresponding argument in the 'this' (local at index 0). This dummy local
- * is never used and should be cleand up by dead code elimination (when enabled).
+ * is never used and should be cleaned up by dead code elimination (when enabled).
* </p>
* <p>
* This phase has been moved before pattern matching to catch more
diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
index 3b23306386..afafdedce7 100644
--- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala
@@ -1,90 +1,64 @@
package scala.tools.nsc
package transform
+import scala.annotation.tailrec
import scala.tools.nsc.ast.TreeDSL
-import scala.tools.nsc.Global
/**
* A trait usable by transforms that need to adapt trees of one type to another type
*/
-trait TypeAdaptingTransformer {
- self: TreeDSL =>
-
- val analyzer: typechecker.Analyzer { val global: self.global.type }
-
- trait TypeAdapter {
- val typer: analyzer.Typer
+trait TypeAdaptingTransformer { self: TreeDSL =>
+ abstract class TypeAdapter {
import global._
import definitions._
- import CODE._
- def isMethodTypeWithEmptyParams(tpe: Type) = tpe match {
- case MethodType(Nil, _) => true
- case _ => false
- }
+ def typedPos(pos: Position)(tree: Tree): Tree
private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = {
currentRun.runDefinitions.isUnbox(fn.symbol) && {
val cls = arg.tpe.typeSymbol
- (cls == definitions.NullClass) || isBoxedValueClass(cls)
+ (cls == NullClass) || isBoxedValueClass(cls)
}
}
- private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol)
-
- private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType]
+ private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol)
+ final def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner)
+ final def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty
+ final def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType
- private def isDifferentErasedValueType(tpe: Type, other: Type) =
- isErasedValueType(tpe) && (tpe ne other)
-
- def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner)
-
- @inline def box(tree: Tree, target: => String): Tree = {
- val result = box1(tree)
- if (tree.tpe =:= UnitTpe) ()
- else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}")
- result
- }
+ import CODE._
/** Box `tree` of unboxed type */
- private def box1(tree: Tree): Tree = tree match {
+ final def box(tree: Tree): Tree = tree match {
case LabelDef(_, _, _) =>
- val ldef = deriveLabelDef(tree)(box1)
+ val ldef = deriveLabelDef(tree)(box)
ldef setType ldef.rhs.tpe
case _ =>
val tree1 = tree.tpe match {
- case ErasedValueType(clazz, _) =>
- New(clazz, cast(tree, underlyingOfValueClass(clazz)))
- case _ =>
- tree.tpe.typeSymbol match {
- case UnitClass =>
- if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT)
- else BLOCK(tree, REF(BoxedUnit_UNIT))
- case NothingClass => tree // a non-terminating expression doesn't need boxing
- case x =>
- assert(x != ArrayClass)
- tree match {
- /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x
- * may lead to throwing an exception.
- *
- * This is important for specialization: calls to the super constructor should not box/unbox specialized
- * fields (see TupleX). (ID)
- */
- case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) =>
- log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}")
- arg
- case _ =>
- (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe
- }
- }
+ case ErasedValueType(clazz, _) => New(clazz, cast(tree, underlyingOfValueClass(clazz)))
+ case _ => tree.tpe.typeSymbol match {
+ case UnitClass =>
+ if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT)
+ else BLOCK(tree, REF(BoxedUnit_UNIT))
+ case NothingClass => tree // a non-terminating expression doesn't need boxing
+ case x =>
+ assert(x != ArrayClass)
+ tree match {
+ /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x
+ * may lead to throwing an exception.
+ *
+ * This is important for specialization: calls to the super constructor should not box/unbox specialized
+ * fields (see TupleX). (ID)
+ */
+ case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) =>
+ log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}")
+ arg
+ case _ =>
+ (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe
+ }
+ }
}
- typer.typedPos(tree.pos)(tree1)
- }
-
- def unbox(tree: Tree, pt: Type): Tree = {
- val result = unbox1(tree, pt)
- log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}")
- result
+ typedPos(tree.pos)(tree1)
}
/** Unbox `tree` of boxed type to expected type `pt`.
@@ -93,27 +67,13 @@ trait TypeAdaptingTransformer {
* @param pt the expected type.
* @return the unboxed tree
*/
- private def unbox1(tree: Tree, pt: Type): Tree = tree match {
-/*
- case Boxed(unboxed) =>
- println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled.
- adaptToType(unboxed, pt)
- */
+ final def unbox(tree: Tree, pt: Type): Tree = tree match {
case LabelDef(_, _, _) =>
val ldef = deriveLabelDef(tree)(unbox(_, pt))
ldef setType ldef.rhs.tpe
case _ =>
val tree1 = pt match {
- case ErasedValueType(clazz, underlying) =>
- val tree0 =
- if (tree.tpe.typeSymbol == NullClass &&
- isPrimitiveValueClass(underlying.typeSymbol)) {
- // convert `null` directly to underlying type, as going
- // via the unboxed type would yield a NPE (see SI-5866)
- unbox1(tree, underlying)
- } else
- Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List())
- cast(tree0, pt)
+ case ErasedValueType(clazz, underlying) => cast(unboxValueClass(tree, clazz, underlying), pt)
case _ =>
pt.typeSymbol match {
case UnitClass =>
@@ -125,21 +85,28 @@ trait TypeAdaptingTransformer {
Apply(currentRun.runDefinitions.unboxMethod(pt.typeSymbol), tree)
}
}
- typer.typedPos(tree.pos)(tree1)
+ typedPos(tree.pos)(tree1)
}
+ final def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree =
+ if (tree.tpe.typeSymbol == NullClass && isPrimitiveValueClass(underlying.typeSymbol)) {
+ // convert `null` directly to underlying type, as going via the unboxed type would yield a NPE (see SI-5866)
+ unbox(tree, underlying)
+ } else
+ Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List())
+
/** Generate a synthetic cast operation from tree.tpe to pt.
- * @pre pt eq pt.normalize
+ *
+ * @pre pt eq pt.normalize
*/
- def cast(tree: Tree, pt: Type): Tree = {
- if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) {
- def word = (
+ final def cast(tree: Tree, pt: Type): Tree = {
+ if (settings.debug && (tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) {
+ def word =
if (tree.tpe <:< pt) "upcast"
else if (pt <:< tree.tpe) "downcast"
else if (pt weak_<:< tree.tpe) "coerce"
else if (tree.tpe weak_<:< pt) "widen"
else "cast"
- )
log(s"erasure ${word}s from ${tree.tpe} to $pt")
}
if (pt =:= UnitTpe) {
@@ -160,27 +127,23 @@ trait TypeAdaptingTransformer {
* @param pt the expected type
* @return the adapted tree
*/
- def adaptToType(tree: Tree, pt: Type): Tree = {
- if (settings.debug && pt != WildcardType)
- log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug
- if (tree.tpe <:< pt)
- tree
- else if (isDifferentErasedValueType(tree.tpe, pt))
- adaptToType(box(tree, pt.toString), pt)
- else if (isDifferentErasedValueType(pt, tree.tpe))
- adaptToType(unbox(tree, pt), pt)
- else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) {
- adaptToType(box(tree, pt.toString), pt)
- } else if (isMethodTypeWithEmptyParams(tree.tpe)) {
- // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val
- //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt)
- adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt)
-// } else if (pt <:< tree.tpe)
-// cast(tree, pt)
- } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe))
- adaptToType(unbox(tree, pt), pt)
- else
- cast(tree, pt)
+ @tailrec final def adaptToType(tree: Tree, pt: Type): Tree = {
+ val tpe = tree.tpe
+
+ if ((tpe eq pt) || tpe <:< pt) tree
+ else if (tpe.isInstanceOf[ErasedValueType]) adaptToType(box(tree), pt) // what if pt is an erased value type?
+ else if (pt.isInstanceOf[ErasedValueType]) adaptToType(unbox(tree, pt), pt)
+ // See corresponding case in `Eraser`'s `adaptMember`
+ // [H] this does not hold here, however: `assert(tree.symbol.isStable)` (when typechecking !(SomeClass.this.bitmap) for single lazy val)
+ else if (isMethodTypeWithEmptyParams(tpe)) adaptToType(applyMethodWithEmptyParams(tree), pt)
+ else {
+ val gotPrimitiveVC = isPrimitiveValueType(tpe)
+ val expectedPrimitiveVC = isPrimitiveValueType(pt)
+
+ if (gotPrimitiveVC && !expectedPrimitiveVC) adaptToType(box(tree), pt)
+ else if (!gotPrimitiveVC && expectedPrimitiveVC) adaptToType(unbox(tree, pt), pt)
+ else cast(tree, pt)
+ }
}
}
}
diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
index dc3313e2e4..d5adfe12e9 100644
--- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
+++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala
@@ -26,7 +26,7 @@ trait TypingTransformers {
def atOwner[A](tree: Tree, owner: Symbol)(trans: => A): A = {
val savedLocalTyper = localTyper
- localTyper = localTyper.atOwner(tree, if (owner.isModule) owner.moduleClass else owner)
+ localTyper = localTyper.atOwner(tree, if (owner.isModuleNotMethod) owner.moduleClass else owner)
val result = super.atOwner(owner)(trans)
localTyper = savedLocalTyper
result
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 870c35338c..e0b1543f24 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> */
@@ -64,19 +65,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` */
@@ -87,25 +99,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
@@ -114,7 +118,7 @@ abstract class UnCurry extends InfoTransform
def isByNameRef(tree: Tree) = (
tree.isTerm
&& (tree.symbol ne null)
- && (isByName(tree.symbol))
+ && isByName(tree.symbol)
&& !byNameArgs(tree)
)
@@ -191,16 +195,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 {
@@ -209,66 +203,30 @@ 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 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)))
- }
- }
- }
+ 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 {
+ // 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)
+ ))
+ val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun)))
+ if (mustExpandFunction(fun)) {
+ 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
@@ -346,25 +304,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)
}
}
}
@@ -376,8 +331,9 @@ abstract class UnCurry extends InfoTransform
*/
private def replaceElidableTree(tree: Tree): Tree = {
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, gen.mkZero(rhs.tpe)) setType rhs.tpe
+ deriveDefDef(tree)(_ => rhs1) setSymbol tree.symbol setType tree.tpe
case _ =>
gen.mkZero(tree.tpe) setType tree.tpe
}
@@ -434,9 +390,10 @@ 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 = (
+ val result =
if ((sym ne null) && sym.elisionLevel.exists(_ < settings.elidebelow.value))
replaceElidableTree(tree)
else translateSynchronized(tree) match {
@@ -489,7 +446,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, _) =>
@@ -508,7 +465,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(_, _) =>
@@ -528,9 +485,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 {
@@ -549,7 +505,7 @@ abstract class UnCurry extends InfoTransform
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) =>
@@ -575,6 +531,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
@@ -586,11 +543,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
}
)
addJavaVarargsForwarders(dd, flatdd)
@@ -609,7 +577,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
@@ -684,7 +652,7 @@ abstract class UnCurry extends InfoTransform
// declared type and assign this to a synthetic val. Later, we'll patch
// the method body to refer to this, rather than the parameter.
val tempVal: ValDef = {
- // SI-9442: using the "uncurry-erased" type (the one after the uncurry phase) can lead to incorrect
+ // SI-9442: using the "uncurry-erased" type (the one after the uncurry phase) can lead to incorrect
// tree transformations. For example, compiling:
// ```
// def foo(c: Ctx)(l: c.Tree): Unit = {
@@ -713,7 +681,7 @@ abstract class UnCurry extends InfoTransform
// to redo this desugaring manually here
// 2. the type needs to be normalized, since `gen.mkCast` checks this (no HK here, just aliases have
// to be expanded before handing the type to `gen.mkAttributedCast`, which calls `gen.mkCast`)
- val info0 =
+ val info0 =
enteringUncurry(p.symbol.info) match {
case DesugaredParameterType(desugaredTpe) =>
desugaredTpe
@@ -734,7 +702,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))
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
index 91777b34d5..4ae97ce281 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/Logic.scala
@@ -8,9 +8,9 @@ package scala
package tools.nsc.transform.patmat
import scala.language.postfixOps
+
import scala.collection.mutable
import scala.reflect.internal.util.{NoPosition, Position, Statistics, HashSet}
-import scala.tools.nsc.Global
trait Logic extends Debugging {
import PatternMatchingStats._
@@ -646,7 +646,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
}
- import global.{ConstantType, Constant, EmptyScope, SingletonType, Literal, Ident, refinedType, singleType, TypeBounds, NoSymbol}
+ import global.{ConstantType, SingletonType, Literal, Ident, singleType, TypeBounds, NoSymbol}
import global.definitions._
@@ -691,7 +691,7 @@ trait ScalaLogic extends Interface with Logic with TreeAndTypeAnalysis {
// if X is mutable.
freshExistentialSubtype(t.tpe)
}
- else trees find (a => a.correspondsStructure(t)(sameValue)) match {
+ else trees find (a => equivalentTree(a, t)) match {
case Some(orig) =>
debug.patmat("unique tp for tree: " + ((orig, orig.tpe)))
orig.tpe
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
index 1331eb6993..ec493b9507 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala
@@ -6,9 +6,6 @@
package scala.tools.nsc.transform.patmat
-import scala.annotation.tailrec
-import scala.collection.immutable.{IndexedSeq, Iterable}
-import scala.language.postfixOps
import scala.collection.mutable
import scala.reflect.internal.util.Statistics
@@ -85,11 +82,15 @@ trait TreeAndTypeAnalysis extends Debugging {
tp <:< tpImpliedNormalizedToAny
}
- // TODO: improve, e.g., for constants
- def sameValue(a: Tree, b: Tree): Boolean = (a eq b) || ((a, b) match {
- case (_ : Ident, _ : Ident) => a.symbol eq b.symbol
- case _ => false
- })
+ def equivalentTree(a: Tree, b: Tree): Boolean = (a, b) match {
+ case (Select(qual1, _), Select(qual2, _)) => equivalentTree(qual1, qual2) && a.symbol == b.symbol
+ case (Ident(_), Ident(_)) => a.symbol == b.symbol
+ case (Literal(c1), Literal(c2)) => c1 == c2
+ case (This(_), This(_)) => a.symbol == b.symbol
+ case (Apply(fun1, args1), Apply(fun2, args2)) => equivalentTree(fun1, fun2) && args1.corresponds(args2)(equivalentTree)
+ // Those are the only cases we need to handle in the pattern matcher
+ case _ => false
+ }
trait CheckableTreeAndTypeAnalysis {
val typer: Typer
@@ -138,7 +139,7 @@ trait TreeAndTypeAnalysis extends Debugging {
if(grouped) {
def enumerateChildren(sym: Symbol) = {
- sym.children.toList
+ sym.sealedChildren.toList
.sortBy(_.sealedSortName)
.filterNot(x => x.isSealed && x.isAbstractClass && !isPrimitiveValueClass(x))
}
@@ -173,6 +174,8 @@ trait TreeAndTypeAnalysis extends Debugging {
filterChildren(subclasses)
})
}
+ case sym if sym.isCase =>
+ List(List(tp))
case sym =>
debug.patmat("enum unsealed "+ ((tp, sym, sym.isSealed, isPrimitiveValueClass(sym))))
@@ -277,7 +280,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT
// hashconsing trees (modulo value-equality)
def unique(t: Tree, tpOverride: Type = NoType): Tree =
- trees find (a => a.correspondsStructure(t)(sameValue)) match {
+ trees find (a => equivalentTree(a, t)) match {
case Some(orig) =>
// debug.patmat("unique: "+ (t eq orig, orig))
orig
@@ -346,7 +349,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT
object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy {
type Result = Prop
def and(a: Result, b: Result) = And(a, b)
- def outerTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType)
+ def withOuterTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType)
def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T)
val p = binderToUniqueTree(b); And(uniqueNonNullProp(p), uniqueTypeProp(p, uniqueTp(pt)))
}
@@ -707,9 +710,8 @@ trait MatchAnalysis extends MatchApproximation {
val (equal, notEqual) = varAssignment.getOrElse(variable, Nil -> Nil)
- def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) = {
- Map(variable ->(equal ++ equalTo, notEqual ++ notEqualTo))
- }
+ def addVarAssignment(equalTo: List[Const], notEqualTo: List[Const]) =
+ Map(variable ->((equal ++ equalTo, notEqual ++ notEqualTo)))
// this assignment is needed in case that
// there exists already an assign
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala
index 1642613b9b..03d0a28fb1 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchCodeGen.scala
@@ -6,9 +6,9 @@
package scala.tools.nsc.transform.patmat
-import scala.tools.nsc.symtab.Flags.SYNTHETIC
import scala.language.postfixOps
-import scala.reflect.internal.util.Statistics
+
+import scala.tools.nsc.symtab.Flags.SYNTHETIC
import scala.reflect.internal.util.Position
/** Factory methods used by TreeMakers to make the actual trees.
@@ -55,7 +55,15 @@ trait MatchCodeGen extends Interface {
def flatMap(prev: Tree, b: Symbol, next: Tree): Tree
def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree
def flatMapGuard(cond: Tree, next: Tree): Tree
- def ifThenElseZero(c: Tree, thenp: Tree): Tree = IF (c) THEN thenp ELSE zero
+ def ifThenElseZero(c: Tree, thenp: Tree): Tree = {
+ val z = zero
+ thenp match {
+ case If(c1, thenp1, elsep1) if z equalsStructure elsep1 =>
+ If(c AND c1, thenp1, elsep1) // cleaner, leaner trees
+ case _ =>
+ If(c, thenp, zero)
+ }
+ }
protected def zero: Tree
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala
index cca8d2dbb8..6fdaa79c10 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchOptimization.scala
@@ -6,10 +6,10 @@
package scala.tools.nsc.transform.patmat
-import scala.tools.nsc.symtab.Flags.MUTABLE
import scala.language.postfixOps
+
+import scala.tools.nsc.symtab.Flags.MUTABLE
import scala.collection.mutable
-import scala.reflect.internal.util.Statistics
import scala.reflect.internal.util.Position
/** Optimize and analyze matches based on their TreeMaker-representation.
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
index a2afb76b0e..e12b8548a8 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTranslation.scala
@@ -7,7 +7,7 @@
package scala.tools.nsc.transform.patmat
import scala.language.postfixOps
-import scala.collection.mutable
+
import scala.reflect.internal.util.Statistics
/** Translate typed Trees that represent pattern matches into the patternmatching IR, defined by TreeMakers.
@@ -18,8 +18,7 @@ trait MatchTranslation {
import PatternMatchingStats._
import global._
import definitions._
- import global.analyzer.{ErrorUtils, formalTypes}
- import treeInfo.{ WildcardStarArg, Unapplied, isStar, unbind }
+ import treeInfo.{ Unapplied, unbind }
import CODE._
// Always map repeated params to sequences
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
index e0fcc05de2..8c59ced28f 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
@@ -6,10 +6,10 @@
package scala.tools.nsc.transform.patmat
-import scala.tools.nsc.symtab.Flags.{SYNTHETIC, ARTIFACT}
import scala.language.postfixOps
+
+import scala.tools.nsc.symtab.Flags.{SYNTHETIC, ARTIFACT}
import scala.collection.mutable
-import scala.reflect.internal.util.Statistics
import scala.reflect.internal.util.Position
/** Translate our IR (TreeMakers) into actual Scala Trees using the factory methods in MatchCodeGen.
@@ -101,7 +101,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker {
val pos = NoPosition
- val localSubstitution = Substitution(prevBinder, CODE.REF(nextBinder))
+ val localSubstitution = Substitution(prevBinder, gen.mkAttributedStableRef(nextBinder))
def chainBefore(next: Tree)(casegen: Casegen): Tree = substitution(next)
override def toString = "S"+ localSubstitution
}
@@ -118,7 +118,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
val res: Tree
lazy val nextBinder = freshSym(pos, nextBinderTp)
- lazy val localSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder)))
+ lazy val localSubstitution = Substitution(List(prevBinder), List(gen.mkAttributedStableRef(nextBinder)))
def chainBefore(next: Tree)(casegen: Casegen): Tree =
atPos(pos)(casegen.flatMapCond(cond, res, nextBinder, substitution(next)))
@@ -316,7 +316,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
trait TypeTestCondStrategy {
type Result
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result
+ def withOuterTest(orig: Result)(testedBinder: Symbol, expectedTp: Type): Result = orig
// TODO: can probably always widen
def typeTest(testedBinder: Symbol, expectedTp: Type): Result
def nonNullTest(testedBinder: Symbol): Result
@@ -336,18 +336,34 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder)
def eqTest(pat: Tree, testedBinder: Symbol) = REF(testedBinder) OBJ_EQ pat
- def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = {
- val expectedOuter = expectedTp.prefix match {
- case ThisType(clazz) => This(clazz)
- case NoType => mkTRUE // fallback for SI-6183
- case pre => REF(pre.prefix, pre.termSymbol)
+ override def withOuterTest(orig: Tree)(testedBinder: Symbol, expectedTp: Type): Tree = {
+ val expectedPrefix = expectedTp.prefix
+ val testedPrefix = testedBinder.info.prefix
+
+ // Check if a type is defined in a static location. Unlike `tp.isStatic` before `flatten`,
+ // this also includes methods and (possibly nested) objects inside of methods.
+ def definedInStaticLocation(tp: Type): Boolean = {
+ def isStatic(tp: Type): Boolean =
+ if (tp == NoType || tp.typeSymbol.isPackageClass || tp == NoPrefix) true
+ else if (tp.typeSymbol.isModuleClass) isStatic(tp.prefix)
+ else false
+ tp.typeSymbol.owner == tp.prefix.typeSymbol && isStatic(tp.prefix)
}
- // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
- // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor?
- val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix
-
- (Select(codegen._asInstanceOf(testedBinder, expectedTp), outer)) OBJ_EQ expectedOuter
+ if ((expectedPrefix eq NoPrefix)
+ || definedInStaticLocation(expectedTp)
+ || testedPrefix =:= expectedPrefix) orig
+ else gen.mkAttributedQualifierIfPossible(expectedPrefix) match {
+ case None => orig
+ case Some(expectedOuterRef) =>
+ // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix`
+ // by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
+ // if there's an outer accessor, otherwise the condition becomes `true`
+ // TODO: centralize logic whether there's an outer accessor and use here?
+ val synthOuterGetter = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedPrefix
+ val outerTest = (Select(codegen._asInstanceOf(testedBinder, expectedTp), synthOuterGetter)) OBJ_EQ expectedOuterRef
+ and(orig, outerTest)
+ }
}
}
@@ -356,7 +372,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
def nonNullTest(testedBinder: Symbol): Result = false
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false
def eqTest(pat: Tree, testedBinder: Symbol): Result = false
@@ -368,7 +383,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
type Result = Boolean
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
@@ -405,12 +419,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
import TypeTestTreeMaker._
debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)))
- lazy val outerTestNeeded = (
- (expectedTp.prefix ne NoPrefix)
- && !expectedTp.prefix.typeSymbol.isPackageClass
- && needsOuterTest(expectedTp, testedBinder.info, matchOwner)
- )
-
// the logic to generate the run-time test that follows from the fact that
// a `prevBinder` is expected to have type `expectedTp`
// the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees
@@ -429,12 +437,11 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp)
def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe)
def mkNullTest = nonNullTest(testedBinder)
- def mkOuterTest = outerTest(testedBinder, expectedTp)
def mkTypeTest = typeTest(testedBinder, expectedWide)
def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder)
def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder)
- def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res
+ def addOuterTest(res: cs.Result): cs.Result = withOuterTest(res)(testedBinder, expectedTp)
// If we conform to expected primitive type:
// it cannot be null and cannot have an outer pointer. No further checking.
@@ -485,7 +492,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
// NOTE: generate `patTree == patBinder`, since the extractor must be in control of the equals method (also, patBinder may be null)
// equals need not be well-behaved, so don't intersect with pattern's (stabilized) type (unlike MaybeBoundTyped's accumType, where it's required)
val cond = codegen._equals(patTree, prevBinder)
- val res = CODE.REF(prevBinder)
+ val res = gen.mkAttributedStableRef(prevBinder)
override def toString = "ET"+((prevBinder.name, patTree))
}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala
index 8beb1837ad..3f27d18e64 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchWarnings.scala
@@ -6,10 +6,6 @@
package scala.tools.nsc.transform.patmat
-import scala.language.postfixOps
-import scala.collection.mutable
-import scala.reflect.internal.util.Statistics
-
trait MatchWarnings {
self: PatternMatching =>
@@ -83,4 +79,4 @@ trait MatchWarnings {
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
index b2f2516b5b..05f2d60be1 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/PatternMatching.scala
@@ -222,7 +222,7 @@ trait Interface extends ast.TreeDSL {
object substIdentsForTrees extends Transformer {
private def typedIfOrigTyped(to: Tree, origTp: Type): Tree =
if (origTp == null || origTp == NoType) to
- // important: only type when actually substing and when original tree was typed
+ // important: only type when actually substituting and when original tree was typed
// (don't need to use origTp as the expected type, though, and can't always do this anyway due to unknown type params stemming from polymorphic extractors)
else typer.typed(to)