/* NSC -- new Scala compiler * Copyright 2005-2013 LAMP/EPFL * @author Martin Odersky */ package scala.tools.nsc package typechecker import symtab.Flags._ import scala.reflect.internal.util.ListOfNil /* * @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 def unapplyParamName = nme.x_0 private def caseMods = Modifiers(SYNTHETIC | CASE) // 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 unapply or unapplySeq if available, without further checks. */ def directUnapplyMember(tp: Type): Symbol = (tp member nme.unapply) orElse (tp member nme.unapplySeq) /** Filters out unapplies with multiple (non-implicit) parameter lists, * as they cannot be used as extractors */ def unapplyMember(tp: Type): Symbol = directUnapplyMember(tp) filter (sym => !hasMultipleNonImplicitParamLists(sym)) object HasUnapply { def unapply(tp: Type): Option[Symbol] = unapplyMember(tp).toOption } 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 ClassDef(_, _, _, Template(_, _, body)) = resetAttrs(cdef.duplicate) val DefDef(_, _, _, vparamss, _, _) = treeInfo firstConstructor body vparamss } private def constrTparamsInvariant(cdef: ClassDef): List[TypeDef] = { val ClassDef(_, _, tparams, _) = resetAttrs(cdef.duplicate) val tparamsInvariant = tparams.map(tparam => copyTypeDef(tparam)(mods = tparam.mods &~ (COVARIANT | CONTRAVARIANT))) tparamsInvariant } /** 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 = { // 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. def selectByName = Ident(param) DOT caseAccessorName(caseclazz.symbol, selector.name) // 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. def localAccessor = caseclazz.impl.body find { case t @ ValOrDefDef(mods, selector.name, _, _) => mods.isPrivateLocal case _ => false } localAccessor.fold(selectByName)(Ident(param) DOT _.symbol) } // 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, noSelfType, NoMods, Nil, body, cdef.impl.pos.focus)) } /** The apply method corresponding to a case class */ def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = { val tparams = constrTparamsInvariant(cdef) 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 = constrTparamsInvariant(cdef) 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 resultType = if (!settings.isScala212) TypeTree() else { // fix for SI-6541 under -Xsource:2.12 def repeatedToSeq(tp: Tree) = tp match { case AppliedTypeTree(Select(_, tpnme.REPEATED_PARAM_CLASS_NAME), tps) => AppliedTypeTree(gen.rootScalaDot(tpnme.Seq), tps) case _ => tp } constrParamss(cdef) match { case Nil | Nil :: _ => gen.rootScalaDot(tpnme.Boolean) case params :: _ => val constrParamTypes = params.map(param => repeatedToSeq(param.tpt)) AppliedTypeTree(gen.rootScalaDot(tpnme.Option), List(treeBuilder.makeTupleType(constrParamTypes))) } } 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), resultType, 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 = constrTparamsInvariant(cdef) 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) } } }