/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Martin Odersky
*/
package scala.tools.nsc
package typechecker
import symtab.Flags._
/*
* @author Martin Odersky
* @version 1.0
*/
trait Unapplies extends ast.TreeDSL
{
self: Analyzer =>
import global._
import definitions._
import CODE.{ CASE => _, _ }
import treeInfo.{ isRepeatedParamType, isByNameParamType }
private val unapplyParamName = nme.x_0
// In the typeCompleter (templateSig) of a case class (resp it's module),
// synthetic `copy` (reps `apply`, `unapply`) methods are added. To compute
// their signatures, the corresponding ClassDef is needed. During naming (in
// `enterClassDef`), the case class ClassDef is added as an attachment to the
// moduleClass symbol of the companion module.
class ClassForCaseCompanionAttachment(val caseClass: ClassDef)
/** returns type list for return type of the extraction
* @see extractorFormalTypes
*/
def unapplyTypeList(pos: Position, ufn: Symbol, ufntpe: Type, args: List[Tree]) = {
assert(ufn.isMethod, ufn)
val nbSubPats = args.length
//Console.println("utl "+ufntpe+" "+ufntpe.typeSymbol)
ufn.name match {
case nme.unapply | nme.unapplySeq =>
val (formals, _) = extractorFormalTypes(pos, unapplyUnwrap(ufntpe), nbSubPats, ufn, treeInfo.effectivePatternArity(args))
if (formals == null) throw new TypeError(s"$ufn of type $ufntpe cannot extract $nbSubPats sub-patterns")
else formals
case _ => throw new TypeError(ufn+" is not an unapply or unapplySeq")
}
}
/** returns unapply or unapplySeq if available */
def unapplyMember(tp: Type): Symbol = (tp member nme.unapply) match {
case NoSymbol => tp member nme.unapplySeq
case unapp => unapp
}
/** returns unapply member's parameter type. */
def unapplyParameterType(extractor: Symbol) = extractor.tpe.params match {
case p :: Nil => p.tpe.typeSymbol
case _ => NoSymbol
}
def copyUntyped[T <: Tree](tree: T): T =
returning[T](tree.duplicate)(UnTyper traverse _)
def copyUntypedInvariant(td: TypeDef): TypeDef = {
val copy = treeCopy.TypeDef(td, td.mods &~ (COVARIANT | CONTRAVARIANT), td.name, td.tparams, td.rhs)
returning[TypeDef](copy.duplicate)(UnTyper traverse _)
}
private def toIdent(x: DefTree) = Ident(x.name) setPos x.pos.focus
private def classType(cdef: ClassDef, tparams: List[TypeDef]): Tree = {
// SI-7033 Unattributed to avoid forcing `cdef.symbol.info`.
val tycon = Ident(cdef.symbol)
if (tparams.isEmpty) tycon else AppliedTypeTree(tycon, tparams map toIdent)
}
private def constrParamss(cdef: ClassDef): List[List[ValDef]] = {
val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor cdef.impl.body
mmap(vparamss)(copyUntyped[ValDef])
}
/** The return value of an unapply method of a case class C[Ts]
* @param param The name of the parameter of the unapply method, assumed to be of type C[Ts]
* @param caseclazz The case class C[Ts]
*/
private def caseClassUnapplyReturnValue(param: Name, caseclazz: ClassDef) = {
def caseFieldAccessorValue(selector: ValDef): Tree = {
val accessorName = selector.name
val privateLocalParamAccessor = caseclazz.impl.body.collectFirst {
case dd: ValOrDefDef if dd.name == accessorName && dd.mods.isPrivateLocal => dd.symbol
}
privateLocalParamAccessor match {
case None =>
// Selecting by name seems to be the most straight forward way here to
// avoid forcing the symbol of the case class in order to list the accessors.
val maybeRenamedAccessorName = caseAccessorName(caseclazz.symbol, accessorName)
Ident(param) DOT maybeRenamedAccessorName
case Some(sym) =>
// But, that gives a misleading error message in neg/t1422.scala, where a case
// class has an illegal private[this] parameter. We can detect this by checking
// the modifiers on the param accessors.
//
// We just generate a call to that param accessor here, which gives us an inaccessible
// symbol error, as before.
Ident(param) DOT sym
}
}
// Working with trees, rather than symbols, to avoid cycles like SI-5082
constrParamss(caseclazz).take(1).flatten match {
case Nil => TRUE
case xs => SOME(xs map caseFieldAccessorValue: _*)
}
}
/** The module corresponding to a case class; overrides toString to show the module's name
*/
def caseModuleDef(cdef: ClassDef): ModuleDef = {
val params = constrParamss(cdef)
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && (params match {
case List(ps) if ps.length <= MaxFunctionArity => true
case _ => false
})
def createFun = {
def primaries = params.head map (_.tpt)
gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
}
def parents = if (inheritFromFun) List(createFun) else Nil
def toString = DefDef(
Modifiers(OVERRIDE | FINAL | SYNTHETIC),
nme.toString_,
Nil,
ListOfNil,
TypeTree(),
Literal(Constant(cdef.name.decode)))
companionModuleDef(cdef, parents, List(toString))
}
def companionModuleDef(cdef: ClassDef, parents: List[Tree] = Nil, body: List[Tree] = Nil): ModuleDef = atPos(cdef.pos.focus) {
ModuleDef(
Modifiers(cdef.mods.flags & AccessFlags | SYNTHETIC, cdef.mods.privateWithin),
cdef.name.toTermName,
gen.mkTemplate(parents, emptyValDef, NoMods, Nil, body, cdef.impl.pos.focus))
}
private val caseMods = Modifiers(SYNTHETIC | CASE)
/** The apply method corresponding to a case class
*/
def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = {
val tparams = cdef.tparams map copyUntypedInvariant
val cparamss = constrParamss(cdef)
def classtpe = classType(cdef, tparams)
atPos(cdef.pos.focus)(
DefDef(mods, name, tparams, cparamss, classtpe,
New(classtpe, mmap(cparamss)(gen.paramToArg)))
)
}
/** The apply method corresponding to a case class
*/
def caseModuleApplyMeth(cdef: ClassDef): DefDef = factoryMeth(caseMods, nme.apply, cdef)
/** The unapply method corresponding to a case class
*/
def caseModuleUnapplyMeth(cdef: ClassDef): DefDef = {
val tparams = cdef.tparams map copyUntypedInvariant
val method = constrParamss(cdef) match {
case xs :: _ if xs.nonEmpty && isRepeatedParamType(xs.last.tpt) => nme.unapplySeq
case _ => nme.unapply
}
val cparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), unapplyParamName, classType(cdef, tparams), EmptyTree))
val ifNull = if (constrParamss(cdef).head.isEmpty) FALSE else REF(NoneModule)
val body = nullSafe({ case Ident(x) => caseClassUnapplyReturnValue(x, cdef) }, ifNull)(Ident(unapplyParamName))
atPos(cdef.pos.focus)(
DefDef(caseMods, method, tparams, List(cparams), TypeTree(), body)
)
}
/**
* Generates copy methods for case classes. Copy only has defaults on the first
* parameter list, as of SI-5009.
*
* The parameter types of the copy method need to be exactly the same as the parameter
* types of the primary constructor. Just copying the TypeTree is not enough: a type `C`
* might refer to something else *inside* the class (i.e. as parameter type of `copy`)
* than *outside* the class (i.e. in the class parameter list).
*
* One such example is t0054.scala:
* class A {
* case class B(x: C) extends A { def copy(x: C = x) = ... }
* class C {} ^ ^
* } (1) (2)
*
* The reference (1) to C is `A.this.C`. The reference (2) is `B.this.C` - not the same.
*
* This is fixed with a hack currently. `Unapplies.caseClassCopyMeth`, which creates the
* copy method, uses empty `TypeTree()` nodes for parameter types.
*
* In `Namers.enterDefDef`, the copy method gets a special type completer (`enterCopyMethod`).
* Before computing the body type of `copy`, the class parameter types are assigned the copy
* method parameters.
*
* This attachment class stores the copy method parameter ValDefs as an attachment in the
* ClassDef of the case class.
*/
def caseClassCopyMeth(cdef: ClassDef): Option[DefDef] = {
def isDisallowed(vd: ValDef) = isRepeatedParamType(vd.tpt) || isByNameParamType(vd.tpt)
val classParamss = constrParamss(cdef)
if (cdef.symbol.hasAbstractFlag || mexists(classParamss)(isDisallowed)) None
else {
def makeCopyParam(vd: ValDef, putDefault: Boolean) = {
val rhs = if (putDefault) toIdent(vd) else EmptyTree
val flags = PARAM | (vd.mods.flags & IMPLICIT) | (if (putDefault) DEFAULTPARAM else 0)
// empty tpt: see comment above
val tpt = atPos(vd.pos.focus)(TypeTree() setOriginal vd.tpt)
treeCopy.ValDef(vd, Modifiers(flags), vd.name, tpt, rhs)
}
val tparams = cdef.tparams map copyUntypedInvariant
val paramss = classParamss match {
case Nil => Nil
case ps :: pss =>
ps.map(makeCopyParam(_, putDefault = true)) :: mmap(pss)(makeCopyParam(_, putDefault = false))
}
val classTpe = classType(cdef, tparams)
val argss = mmap(paramss)(toIdent)
val body: Tree = New(classTpe, argss)
val copyDefDef = atPos(cdef.pos.focus)(
DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, TypeTree(), body)
)
Some(copyDefDef)
}
}
}