/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Martin Odersky
*/
package dotty.tools.dotc
package transform
import dotty.tools.dotc.transform.TreeTransforms._
import ValueClasses._
import dotty.tools.dotc.ast.{Trees, tpd}
import scala.collection.{ mutable, immutable }
import mutable.ListBuffer
import core._
import dotty.tools.dotc.core.Phases.{NeedsCompanions, Phase}
import Types._, Contexts._, Constants._, Names._, NameOps._, Flags._, DenotTransformers._
import SymDenotations._, Symbols._, StdNames._, Annotations._, Trees._, Scopes._, Denotations._
import TypeErasure.{ valueErasure, ErasedValueType }
import TypeUtils._
import NameKinds.{ExtMethName, UniqueExtMethName}
import util.Positions._
import Decorators._
import SymUtils._
/**
* Perform Step 1 in the inline classes SIP: Creates extension methods for all
* methods in a value class, except parameter or super accessors, or constructors.
*
* Additionally, for a value class V, let U be the underlying type after erasure. We add
* to the companion module of V two cast methods:
* def u2evt$(x0: U): ErasedValueType(V, U)
* def evt2u$(x0: ErasedValueType(V, U)): U
* The casts are used in [[Erasure]] to make it typecheck, they are then removed
* in [[ElimErasedValueType]].
* This is different from the implementation of value classes in Scala 2
* (see SIP-15) which uses `asInstanceOf` which does not typecheck.
*
* Finally, if the constructor of a value class is private pr protected
* it is widened to public.
*/
class ExtensionMethods extends MiniPhaseTransform with DenotTransformer with FullParameterization { thisTransformer =>
import tpd._
import ExtensionMethods._
/** the following two members override abstract members in Transform */
override def phaseName: String = "extmethods"
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimRepeated])
override def runsAfterGroupsOf = Set(classOf[FirstTransform]) // need companion objects to exist
override def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
case moduleClassSym: ClassDenotation if moduleClassSym is ModuleClass =>
moduleClassSym.linkedClass match {
case valueClass: ClassSymbol if isDerivedValueClass(valueClass) =>
val cinfo = moduleClassSym.classInfo
val decls1 = cinfo.decls.cloneScope
val moduleSym = moduleClassSym.symbol.asClass
var newSuperClass: Type = null
ctx.atPhase(thisTransformer.next) { implicit ctx =>
// In Scala 2, extension methods are added before pickling so we should
// not generate them again.
if (!(valueClass is Scala2x)) ctx.atPhase(thisTransformer) { implicit ctx =>
for (decl <- valueClass.classInfo.decls) {
if (isMethodWithExtension(decl)) {
val meth = createExtensionMethod(decl, moduleClassSym.symbol)
decls1.enter(meth)
// Workaround #1895: force denotation of `meth` to be
// at phase where `meth` is entered into the decls of a class
meth.denot(ctx.withPhase(thisTransformer.next))
}
}
}
val underlying = valueErasure(underlyingOfValueClass(valueClass))
val evt = ErasedValueType(valueClass.typeRef, underlying)
val u2evtSym = ctx.newSymbol(moduleSym, nme.U2EVT, Synthetic | Method,
MethodType(List(nme.x_0), List(underlying), evt))
val evt2uSym = ctx.newSymbol(moduleSym, nme.EVT2U, Synthetic | Method,
MethodType(List(nme.x_0), List(evt), underlying))
val defn = ctx.definitions
val underlyingCls = underlying.classSymbol
val underlyingClsName =
if (underlyingCls.isNumericValueClass || underlyingCls == defn.BooleanClass) underlyingCls.name
else nme.Object
val syp = ctx.requiredClass(s"dotty.runtime.vc.VC${underlyingClsName}Companion").asClass
newSuperClass = tpd.ref(syp).select(nme.CONSTRUCTOR).appliedToType(valueClass.typeRef).tpe.resultType
decls1.enter(u2evtSym)
decls1.enter(evt2uSym)
}
// Add the extension methods, the cast methods u2evt$ and evt2u$, and a VC*Companion superclass
moduleClassSym.copySymDenotation(info =
cinfo.derivedClassInfo(
// FIXME: use of VC*Companion superclasses is disabled until the conflicts with SyntheticMethods are solved.
//classParents = ctx.normalizeToClassRefs(List(newSuperClass), moduleSym, decls1),
decls = decls1))
case _ =>
moduleClassSym
}
case ref: SymDenotation =>
if (isMethodWithExtension(ref) && ref.hasAnnotation(defn.TailrecAnnot)) {
val ref1 = ref.copySymDenotation()
ref1.removeAnnotation(defn.TailrecAnnot)
ref1
}
else if (ref.isConstructor && isDerivedValueClass(ref.owner) && ref.is(AccessFlags)) {
val ref1 = ref.copySymDenotation()
ref1.resetFlag(AccessFlags)
ref1
}
else ref
case _ =>
ref
}
protected def rewiredTarget(target: Symbol, derived: Symbol)(implicit ctx: Context): Symbol =
if (isMethodWithExtension(target) &&
target.owner.linkedClass == derived.owner) extensionMethod(target)
else NoSymbol
private def createExtensionMethod(imeth: Symbol, staticClass: Symbol)(implicit ctx: Context): TermSymbol = {
val extensionName = extensionNames(imeth).head.toTermName
val extensionMeth = ctx.newSymbol(staticClass, extensionName,
imeth.flags | Final &~ (Override | Protected | AbsOverride),
fullyParameterizedType(imeth.info, imeth.owner.asClass),
privateWithin = imeth.privateWithin, coord = imeth.coord)
extensionMeth.addAnnotations(imeth.annotations)(ctx.withPhase(thisTransformer))
// need to change phase to add tailrec annotation which gets removed from original method in the same phase.
extensionMeth
}
private val extensionDefs = mutable.Map[Symbol, mutable.ListBuffer[Tree]]()
// TODO: this is state and should be per-run
// todo: check that when transformation finished map is empty
override def transformTemplate(tree: tpd.Template)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
if (isDerivedValueClass(ctx.owner)) {
/* This is currently redundant since value classes may not
wrap over other value classes anyway.
checkNonCyclic(ctx.owner.pos, Set(), ctx.owner) */
tree
} else if (ctx.owner.isStaticOwner) {
extensionDefs remove tree.symbol.owner match {
case Some(defns) if defns.nonEmpty =>
cpy.Template(tree)(body = tree.body ++
defns.map(transformFollowing(_)))
case _ =>
tree
}
} else tree
}
override def transformDefDef(tree: tpd.DefDef)(implicit ctx: Context, info: TransformerInfo): tpd.Tree = {
if (isMethodWithExtension(tree.symbol)) {
val origMeth = tree.symbol
val origClass = ctx.owner.asClass
val staticClass = origClass.linkedClass
assert(staticClass.exists, s"$origClass lacks companion, ${origClass.owner.definedPeriodsString} ${origClass.owner.info.decls} ${origClass.owner.info.decls}")
val extensionMeth = extensionMethod(origMeth)
ctx.log(s"Value class $origClass spawns extension method.\n Old: ${origMeth.showDcl}\n New: ${extensionMeth.showDcl}")
val store: ListBuffer[Tree] = extensionDefs.get(staticClass) match {
case Some(x) => x
case None =>
val newC = new ListBuffer[Tree]()
extensionDefs(staticClass) = newC
newC
}
store += atGroupEnd(fullyParameterizedDef(extensionMeth, tree)(_))
cpy.DefDef(tree)(rhs = atGroupEnd(forwarder(extensionMeth, tree)(_)))
} else tree
}
}
object ExtensionMethods {
/** Generate stream of possible names for the extension version of given instance method `imeth`.
* If the method is not overloaded, this stream consists of just "imeth$extension".
* If the method is overloaded, the stream has as first element "imeth$extenionX", where X is the
* index of imeth in the sequence of overloaded alternatives with the same name. This choice will
* always be picked as the name of the generated extension method.
* After this first choice, all other possible indices in the range of 0 until the number
* of overloaded alternatives are returned. The secondary choices are used to find a matching method
* in `extensionMethod` if the first name has the wrong type. We thereby gain a level of insensitivity
* of how overloaded types are ordered between phases and picklings.
*/
private def extensionNames(imeth: Symbol)(implicit ctx: Context): Stream[Name] = {
val decl = imeth.owner.info.decl(imeth.name)
/** No longer needed for Dotty, as we are more disciplined with scopes now.
// Bridge generation is done at phase `erasure`, but new scopes are only generated
// for the phase after that. So bridges are visible in earlier phases.
//
// `info.member(imeth.name)` filters these out, but we need to use `decl`
// to restrict ourselves to members defined in the current class, so we
// must do the filtering here.
val declTypeNoBridge = decl.filter(sym => !sym.isBridge).tpe
*/
decl match {
case decl: MultiDenotation =>
val alts = decl.alternatives
val index = alts indexOf imeth.denot
assert(index >= 0, alts + " does not contain " + imeth)
def altName(index: Int) = UniqueExtMethName(imeth.name.asTermName, index)
altName(index) #:: ((0 until alts.length).toStream filter (index != _) map altName)
case decl =>
assert(decl.exists, imeth.name + " not found in " + imeth.owner + "'s decls: " + imeth.owner.info.decls)
Stream(ExtMethName(imeth.name.asTermName))
}
}
/** Return the extension method that corresponds to given instance method `meth`. */
def extensionMethod(imeth: Symbol)(implicit ctx: Context): TermSymbol =
ctx.atPhase(ctx.extensionMethodsPhase.next) { implicit ctx =>
// FIXME use toStatic instead?
val companionInfo = imeth.owner.companionModule.info
val candidates = extensionNames(imeth) map (companionInfo.decl(_).symbol) filter (_.exists)
val matching = candidates filter (c => FullParameterization.memberSignature(c.info) == imeth.signature)
assert(matching.nonEmpty,
i"""no extension method found for:
|
| $imeth:${imeth.info.show} with signature ${imeth.signature}
|
| Candidates:
|
| ${candidates.map(c => c.name + ":" + c.info.show).mkString("\n")}
|
| Candidates (signatures normalized):
|
| ${candidates.map(c => c.name + ":" + c.info.signature + ":" + FullParameterization.memberSignature(c.info)).mkString("\n")}
|
| Eligible Names: ${extensionNames(imeth).mkString(",")}""")
matching.head.asTerm
}
}