summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala
blob: 2b8837c0842ed4aa07410c359381ba3eefd355fb (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                            
                                

                          
       


                                   

                     








































































                                                                                                         











                                                                                
 
                                            
                                       



                           

                                                                            
                                                                                            



                           




                                                                                            
                                                                   













                                                                                                







                                                                            



                                                                              









                                                                                                   








                                                                                    
                                                       
                                      




                                    
                 
                                 
                                                                           





                                                       
                                                       
                                             
                                               
                                                                              

                     


                                    
                                                       






                                                                        























                                                                                                        
 
/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author  Martin Odersky
 */
// $Id$

package scala.tools.nsc.typechecker

import symtab.Flags._

/*
 *  @author  Martin Odersky
 *  @version 1.0
 */
trait Unapplies { self: Analyzer =>

  import global._
  import definitions._

  /** returns type list for return type of the extraction */
  def unapplyTypeList(ufn: Symbol, ufntpe: Type) = {
    assert(ufn.isMethod)
    //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] =  { // rename: unapplyTypeListFromReturnType
    val tp = unapplyUnwrap(tp1)
    val B = BooleanClass
    val O = OptionClass
    val S = SomeClass
    tp.typeSymbol match { // unapplySeqResultToMethodSig
      case  B => Nil
      case  O | S =>
        val prod = tp.typeArgs.head
        getProductArgs(prod)  match {
          case Some(all @ (x1::x2::xs)) => all       // n >= 2
          case _                        => prod::Nil // special n == 0 ||  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)
    val   O = OptionClass; val S = SomeClass; tp.typeSymbol match {
    case  O                  | S =>
      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 ::: List(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 unapplyReturnType(elems: List[Type], useWildCards: Boolean) =
    if (elems.isEmpty)
      BooleanClass.tpe
    else if (elems.length == 1)
      optionType(if(useWildCards) WildcardType else elems(0))
    else
      productType({val es = elems; if(useWildCards) elems map { x => WildcardType} else elems})
   */
  def unapplyReturnTypeExpected(argsLength: Int) = argsLength match {
    case 0 => BooleanClass.tpe
    case 1 => optionType(WildcardType)
    case n => optionType(productType(List.range(0,n).map (arg => WildcardType)))
  }

  /** returns unapply or unapplySeq if available */
  def unapplyMember(tp: Type): Symbol = {
    var unapp = tp.member(nme.unapply)
    if (unapp == NoSymbol) unapp = tp.member(nme.unapplySeq)
    unapp
  }

  def copyUntyped[T <: Tree](tree: T): T = {
    val tree1 = tree.syntheticDuplicate
    UnTyper.traverse(tree1)
    tree1
  }

  def copyUntypedInvariant(td: TypeDef): TypeDef = {
    val tree1 = treeCopy.TypeDef(td, td.mods &~ (COVARIANT | CONTRAVARIANT),
                  td.name, td.tparams map (_.syntheticDuplicate), td.rhs.syntheticDuplicate)
    UnTyper.traverse(tree1)
    tree1
  }

  private def classType(cdef: ClassDef, tparams: List[TypeDef]): Tree = {
    val tycon = gen.mkAttributedRef(cdef.symbol)
    if (tparams.isEmpty) tycon else AppliedTypeTree(tycon, tparams map (x => Ident(x.name)))
  }

  private def constrParamss(cdef: ClassDef): List[List[ValDef]] = {
    val constr = treeInfo.firstConstructor(cdef.impl.body)
    (constr: @unchecked) match {
      case DefDef(_, _, _, vparamss, _, _) => vparamss map (_ map 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) = Select(Ident(param), selector)
    val accessors = caseclazz.caseFieldAccessors
    if (accessors.isEmpty) Literal(true)
    else
      Apply(
        gen.scalaDot(nme.Some),
        List(
          if (accessors.tail.isEmpty) caseFieldAccessorValue(accessors.head)
          else Apply(
            gen.scalaDot(newTermName("Tuple" + accessors.length)),
            accessors map caseFieldAccessorValue)))
  }

  /** The module corresponding to a case class; without any member definitions
   */
  def caseModuleDef(cdef: ClassDef): ModuleDef =
    companionModuleDef(
      cdef,
      if (!(cdef.mods hasFlag ABSTRACT) && cdef.tparams.isEmpty && constrParamss(cdef).length == 1)
        List(gen.scalaFunctionConstr(constrParamss(cdef).head map (_.tpt), Ident(cdef.name)),
             gen.scalaScalaObjectConstr)
      else
        List(gen.scalaScalaObjectConstr))

  def companionModuleDef(cdef: ClassDef, parents: List[Tree]): ModuleDef = atPos(cdef.pos) {
    ModuleDef(
      Modifiers(cdef.mods.flags & AccessFlags | SYNTHETIC, cdef.mods.privateWithin),
      cdef.name.toTermName,
      Template(parents, emptyValDef, Modifiers(0), List(), List(List()), List()))
  }

  /** 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) {
      DefDef(
        Modifiers(SYNTHETIC | CASE),
        nme.apply,
        tparams,
        cparamss,
        classType(cdef, tparams),
        New(classType(cdef, tparams), cparamss map (_ map gen.paramToArg)))
    }
  }

  /** The unapply method corresponding to a case class
   */
  def caseModuleUnapplyMeth(cdef: ClassDef): DefDef = {
    val tparams = cdef.tparams map copyUntypedInvariant
    val unapplyParamName = newTermName("x$0")
    val hasVarArg = constrParamss(cdef) match {
      case (cps @ (_ :: _)) :: _ => treeInfo.isRepeatedParamType(cps.last.tpt)
      case _ => false
    }
    atPos(cdef.pos) {
      DefDef(
        Modifiers(SYNTHETIC | CASE),
        if (hasVarArg) nme.unapplySeq else nme.unapply,
        tparams,
        List(List(ValDef(Modifiers(PARAM | SYNTHETIC), unapplyParamName,
                         classType(cdef, tparams), EmptyTree))),
        TypeTree(),
        caseClassUnapplyReturnValue(unapplyParamName, cdef.symbol))
    }
  }

  def caseClassCopyMeth(cdef: ClassDef): Option[DefDef] = {
    val cparamss = constrParamss(cdef)
    if (cparamss.length == 1 && cparamss.head.isEmpty ||  // no copy method if there are no arguments
        cdef.symbol.hasFlag(ABSTRACT) ||
        cparamss.exists(_.exists(vd =>
          treeInfo.isRepeatedParamType(vd.tpt) ||
          treeInfo.isByNameParamType(vd.tpt))))
      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.
      val paramss = cparamss map (_.map(vd =>
        treeCopy.ValDef(vd, vd.mods | DEFAULTPARAM, vd.name,
                        TypeTree().setOriginal(vd.tpt), Ident(vd.name))))
      val classTpe = classType(cdef, tparams)
      Some(atPos(cdef.pos) {
        DefDef(Modifiers(SYNTHETIC), nme.copy, tparams, paramss, classTpe,
               New(classTpe, paramss map (_ map (p => Ident(p.name)))))
      })
    }
  }
}