/* NSC -- new Scala compiler
* Copyright 2005-2011 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
/** returns type list for return type of the extraction */
def unapplyTypeList(ufn: Symbol, ufntpe: Type) = {
assert(ufn.isMethod, ufn)
//Console.println("utl "+ufntpe+" "+ufntpe.typeSymbol)
ufn.name match {
case nme.unapply => unapplyTypeListFromReturnType(ufntpe)
case nme.unapplySeq => unapplyTypeListFromReturnTypeSeq(ufntpe)
case _ => throw new TypeError(ufn+" is not an unapply or unapplySeq")
}
}
/** (the inverse of unapplyReturnTypeSeq)
* for type Boolean, returns Nil
* for type Option[T] or Some[T]:
* - returns T0...Tn if n>0 and T <: Product[T0...Tn]]
* - returns T otherwise
*/
def unapplyTypeListFromReturnType(tp1: Type): List[Type] = {
val tp = unapplyUnwrap(tp1)
tp.typeSymbol match { // unapplySeqResultToMethodSig
case BooleanClass => Nil
case OptionClass | SomeClass =>
val prod = tp.typeArgs.head
// the spec doesn't allow just any subtype of Product, it *must* be TupleN[...] -- see run/virtpatmat_extends_product.scala
// this breaks plenty of stuff, though...
// val targs =
// if (isTupleType(prod)) getProductArgs(prod)
// else List(prod)
val targs = getProductArgs(prod)
if (targs.isEmpty || targs.tail.isEmpty) List(prod) // special n == 0 || n == 1
else targs // n > 1
case _ =>
throw new TypeError("result type "+tp+" of unapply not in {Boolean, Option[_], Some[_]}")
}
}
/** let type be the result type of the (possibly polymorphic) unapply method
* for type Option[T] or Some[T]
* -returns T0...Tn-1,Tn* if n>0 and T <: Product[T0...Tn-1,Seq[Tn]]],
* -returns R* if T = Seq[R]
*/
def unapplyTypeListFromReturnTypeSeq(tp1: Type): List[Type] = {
val tp = unapplyUnwrap(tp1)
tp.typeSymbol match {
case OptionClass | SomeClass =>
val ts = unapplyTypeListFromReturnType(tp1)
val last1 = (ts.last baseType SeqClass) match {
case TypeRef(pre, SeqClass, args) => typeRef(pre, RepeatedParamClass, args)
case _ => throw new TypeError("last not seq")
}
ts.init :+ last1
case _ =>
throw new TypeError("result type "+tp+" of unapply not in {Option[_], Some[_]}")
}
}
/** returns type of the unapply method returning T_0...T_n
* for n == 0, boolean
* for n == 1, Some[T0]
* else Some[Product[Ti]]
*/
def unapplyReturnTypeExpected(argsLength: Int) = argsLength match {
case 0 => BooleanClass.tpe
case 1 => optionType(WildcardType)
case n => optionType(productType((List fill n)(WildcardType)))
}
/** 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 = {
val tycon = REF(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: Symbol) = {
def caseFieldAccessorValue(selector: Symbol): Tree = Ident(param) DOT selector
caseclazz.caseFieldAccessors 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 = {
// > MaxFunctionArity is caught in Namers, but for nice error reporting instead of
// an abrupt crash we trim the list here.
def primaries = constrParamss(cdef).head take MaxFunctionArity map (_.tpt)
def inheritFromFun = !cdef.mods.hasAbstractFlag && cdef.tparams.isEmpty && constrParamss(cdef).length == 1
def createFun = gen.scalaFunctionConstr(primaries, toIdent(cdef), abstractFun = true)
def parents = if (inheritFromFun) List(createFun) else Nil
def toString = DefDef(
Modifiers(OVERRIDE | FINAL),
nme.toString_,
Nil,
List(Nil),
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,
Template(parents, emptyValDef, NoMods, Nil, List(Nil), body, cdef.impl.pos.focus))
}
private val caseMods = Modifiers(SYNTHETIC | CASE)
/** The apply method corresponding to a case class
*/
def caseModuleApplyMeth(cdef: ClassDef): DefDef = {
val tparams = cdef.tparams map copyUntypedInvariant
val cparamss = constrParamss(cdef)
atPos(cdef.pos.focus)(
DefDef(caseMods, nme.apply, tparams, cparamss, classType(cdef, tparams),
New(classType(cdef, tparams), mmap(cparamss)(gen.paramToArg)))
)
}
/** 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.symbol) }, ifNull)(Ident(unapplyParamName))
atPos(cdef.pos.focus)(
DefDef(caseMods, method, tparams, List(cparams), TypeTree(), body)
)
}
def caseClassCopyMeth(cdef: ClassDef): Option[DefDef] = {
def isDisallowed(vd: ValDef) = isRepeatedParamType(vd.tpt) || isByNameParamType(vd.tpt)
val cparamss = constrParamss(cdef)
val flat = cparamss flatten
if (cdef.symbol.hasAbstractFlag || (flat exists isDisallowed)) None
else {
val tparams = cdef.tparams map copyUntypedInvariant
// the parameter types have to be exactly the same as the constructor's parameter types; so it's
// not good enough to just duplicated the (untyped) tpt tree; the parameter types are removed here
// and re-added in ``finishWith'' in the namer.
def paramWithDefault(vd: ValDef) =
treeCopy.ValDef(vd, vd.mods | DEFAULTPARAM, vd.name, atPos(vd.pos.focus)(TypeTree() setOriginal vd.tpt), toIdent(vd))
val paramss = mmap(cparamss)(paramWithDefault)
val classTpe = classType(cdef, tparams)
Some(atPos(cdef.pos.focus)(
DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, classTpe,
New(classTpe, mmap(paramss)(toIdent)))
))
}
}
}