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

       
 


                                   
                                          
 

          
                                                                             













                                                                             
                                          

                                                                  




                                                                        
 





                      
                                                                                             
 

                                                                                     
 
                                                                       
                                                 
                                           

     
                                                                                   

                                                      
                                                                             

                                                                                

     

                                                            
 
                                                                 




                                                                      


            
      



                                                                           
      

                                                                                               
                                                                                   
     
 
                                                
                                                                                           
                                                                       


                                                          
                                                     
                                                                                                                     
                                                                               
                                                   




                                                                                      
          

     
                                      
                                                                                             
                                                                                   


                           
                                                                                    
                                                                           


                                              



                                                            
                                   
                                                               
                                            
                                                                                      

     







                                                             
                              
                                   
                                                                         
                     




                                                                          
                                                                                                                
                                          
                           













                                                                                                                 
                                                                                
                                                                     


                                  
                                                                    



















                                                                                     
                                 

     
                                                
                                                                
 


                                                                  
                                                                 
                                                                          
                                                                                      

     

                                                          

                                                                                         
                                                                               

                                                                           
                                            


              
                                                 
                                  
                                                                                                                      

                

                                                           

                                                                     
                                              
                                                                                  
                                         
                                                                      
                                                                                               







                                                               
                                 

                                            
                                          
                             
                                 
                 
                                                                                         


                                            
                                          
                             
                                 


                                               
                                                                                   

     


                                                                        
                             

                                 
                                                                            

                                                                                                     
 





                                                                                                                      
             
                                                                                         
           





                                                                                                    
           
 





                                                                                            
         
 


















                                                                                                   
       
     
                              
                  
                                                                                                          

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

package scala.tools.nsc.typechecker

import symtab.Flags._
import scala.collection.mutable.ListBuffer

/** <ul>
 *    <li>
 *      <code>productArity</code>, <code>element</code> implementations added
 *      to case classes
 *    </li>
 *    <li>
 *      <code>equals</code>, <code>hashCode</code> and </code>toString</code>
 *      methods are added to case classes, unless they are defined in the
 *      class or a baseclass different from <code>java.lang.Object</code>
 *    </li>
 *    <li>
 *      <code>toString</code> method is added to case objects, unless they
 *      are defined in the class or a baseclass different from
 *      <code>java.lang.Object</code>
 *    </li>
 *  </ul>
 */
trait SyntheticMethods { self: Analyzer =>
  import global._                  // the global environment
  import definitions._             // standard classes and methods
  //import global.typer.{typed}      // methods to type trees
  // @S: type hack: by default, we are used from global.analyzer context
  // so this cast won't fail. If we aren't in global.analyzer, we have
  // to override this method anyways.
  protected def typer : Typer = global.typer.asInstanceOf[Typer]

  /**
   *  @param templ ...
   *  @param clazz ...
   *  @param unit  ...
   *  @return      ...
   */
  def addSyntheticMethods(templ: Template, clazz: Symbol, context: Context): Template = try {

    val localContext = if (reporter.hasErrors) context.makeSilent(false) else context
    val localTyper = newTyper(localContext)

    def hasImplementation(name: Name): Boolean = if (inIDE) true else {
      val sym = clazz.info.nonPrivateMember(name)
      sym.isTerm && !(sym hasFlag DEFERRED)
    }

    def hasOverridingImplementation(meth: Symbol): Boolean = if (inIDE) true else {
      val sym = clazz.info.nonPrivateMember(meth.name)
      sym.alternatives exists { sym =>
        sym != meth && !(sym hasFlag DEFERRED) && !(sym hasFlag SYNTHETIC) &&
        (clazz.thisType.memberType(sym) matches clazz.thisType.memberType(meth))
      }
    }

    def syntheticMethod(name: Name, flags: Int, tpe: Type) =
      newSyntheticMethod(name, flags | OVERRIDE, tpe)

    def newSyntheticMethod(name: Name, flags: Int, tpe: Type) = {
      var method = clazz.newMethod(clazz.pos, name) setFlag ({
        if (inIDE) flags | SYNTHETIC
        else flags
      }) setInfo tpe
      method = clazz.info.decls.enter(method).asInstanceOf[TermSymbol]
      method
    }

    /*
    def productSelectorMethod(n: int, accessor: Symbol): Tree = {
      val method = syntheticMethod(newTermName("_"+n), FINAL, accessor.tpe)
      typed(DefDef(method, vparamss => gen.mkAttributedRef(accessor)))
    }
    */
    def productPrefixMethod: Tree = {
      val method = syntheticMethod(nme.productPrefix, FINAL, PolyType(List(), StringClass.tpe))
      typer.typed(DefDef(method, vparamss => Literal(Constant(clazz.name.decode))))
    }

    def productArityMethod(nargs:Int ): Tree = {
      val method = syntheticMethod(nme.productArity, FINAL, PolyType(List(), IntClass.tpe))
      typer.typed(DefDef(method, vparamss => Literal(Constant(nargs))))
    }

    def productElementMethod(accs: List[Symbol]): Tree = {
      //val retTpe = lub(accs map (_.tpe.resultType))
      val method = syntheticMethod(nme.productElement, FINAL, MethodType(List(IntClass.tpe), AnyClass.tpe/*retTpe*/))
      typer.typed(DefDef(method, vparamss => Match(Ident(vparamss.head.head), {
	(for ((sym,i) <- accs.zipWithIndex) yield {
	  CaseDef(Literal(Constant(i)),EmptyTree, Ident(sym))
	}):::List(CaseDef(Ident(nme.WILDCARD), EmptyTree,
		    Throw(New(TypeTree(IndexOutOfBoundsExceptionClass.tpe), List(List(
		      Select(Ident(vparamss.head.head), nme.toString_)
		    ))))))
      })))
    }

    def moduleToStringMethod: Tree = {
      val method = syntheticMethod(nme.toString_, FINAL, MethodType(List(), StringClass.tpe))
      typer.typed(DefDef(method, vparamss => Literal(Constant(clazz.name.decode))))
    }

    def tagMethod: Tree = {
      val method = syntheticMethod(nme.tag, FINAL, MethodType(List(), IntClass.tpe))
      typer.typed(DefDef(method, vparamss => Literal(Constant(clazz.tag))))
    }

    def forwardingMethod(name: Name): Tree = {
      val target = getMember(ScalaRunTimeModule, "_" + name)
      val paramtypes =
        if (target.tpe.paramTypes.isEmpty) List()
        else target.tpe.paramTypes.tail
      val method = syntheticMethod(
        name, 0, MethodType(paramtypes, target.tpe.resultType))
      typer.typed(DefDef(method, vparamss =>
        Apply(gen.mkAttributedRef(target), This(clazz) :: (vparamss.head map Ident))))
    }

    /** The equality method for case classes and modules:
     *   def equals(that: Any) =
     *     (this eq that) ||
     *     (that match {
     *       case this.C(this.arg_1, ..., this.arg_n) => true
     *       case _ => false
     *     })
     */
    def equalsMethod: Tree = {
      val method = syntheticMethod(
        nme.equals_, 0, MethodType(List(AnyClass.tpe), BooleanClass.tpe))
      val methodDef =
        DefDef(
          method,
          { vparamss =>
            val that = Ident(vparamss.head.head)
            val constrParamTypes = clazz.primaryConstructor.tpe.paramTypes
            val hasVarArgs = !constrParamTypes.isEmpty && constrParamTypes.last.typeSymbol == RepeatedParamClass
            if (false && clazz.isStatic) {
              // todo: elim
              val target = getMember(ScalaRunTimeModule, if (hasVarArgs) nme._equalsWithVarArgs else nme._equals)
              Apply(
                Select(
                  TypeApply(
                    Select(that, Any_isInstanceOf),
                    List(TypeTree(clazz.tpe))),
                  Boolean_and),
                List(
                  Apply(gen.mkAttributedRef(target),
                        This(clazz) :: (vparamss.head map Ident))))
            } else {
              val (pat, guard) = {
                val guards = new ListBuffer[Tree]
                val params = for ((acc, cpt) <- clazz.caseFieldAccessors zip constrParamTypes) yield {
                  val name = context.unit.fresh.newName(clazz.pos, acc.name+"$")
                  val isVarArg = cpt.typeSymbol == RepeatedParamClass
                  guards += Apply(
                    Select(
                      Ident(name),
                      if (isVarArg) nme.sameElements else nme.EQEQ),
                    List(Ident(acc)))
                  Bind(name,
                       if (isVarArg) Star(Ident(nme.WILDCARD))
                       else Ident(nme.WILDCARD))
                }
                ( Apply(Ident(clazz.name.toTermName), params),
                  if (guards.isEmpty) EmptyTree
                  else guards reduceLeft { (g1: Tree, g2: Tree) =>
                    Apply(Select(g1, nme.AMPAMP), List(g2))
                  }
                )
              }
              Match(
                that,
                List(
                  CaseDef(pat, guard, Literal(Constant(true))),
                  CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false)))))
            }
          }
        )
      localTyper.typed(methodDef)
    }

    def isSerializable(clazz: Symbol): Boolean =
      !clazz.getAttributes(definitions.SerializableAttr).isEmpty

    def readResolveMethod: Tree = {
      // !!! the synthetic method "readResolve" should be private,
      // but then it is renamed !!!
      val method = newSyntheticMethod(nme.readResolve, PROTECTED,
                                      MethodType(List(), ObjectClass.tpe))
      typer.typed(DefDef(method, vparamss => gen.mkAttributedRef(clazz.sourceModule)))
    }

    def newAccessorMethod(tree: Tree): Tree = tree match {
      case DefDef(_, _, _, _, _, rhs) =>
        var newAcc = tree.symbol.cloneSymbol
        newAcc.name = context.unit.fresh.newName(tree.symbol.pos, tree.symbol.name + "$")
        newAcc.setFlag(SYNTHETIC).resetFlag(ACCESSOR | PARAMACCESSOR | PRIVATE)
        newAcc = newAcc.owner.info.decls enter newAcc
        val result = typer.typed(DefDef(newAcc, vparamss => rhs.duplicate))
        log("new accessor method " + result)
        result
    }

    def beanSetterOrGetter(sym: Symbol): Symbol =
      if (!sym.name(0).isLetter) {
        context.unit.error(sym.pos, "attribute `BeanProperty' can be applied only to fields that start with a letter")
        NoSymbol
      } else {
        var name0 = sym.name
        if (sym.isSetter) name0 = nme.setterToGetter(name0)
        val prefix = if (sym.isSetter) "set" else
          if (sym.tpe.resultType == BooleanClass.tpe) "is" else "get"
        val arity = if (sym.isSetter) 1 else 0
        val name1 = prefix + name0(0).toUpperCase + name0.subName(1, name0.length)
        val sym1 = clazz.info.decl(name1)
        if (sym1 != NoSymbol && sym1.tpe.paramTypes.length == arity) {
          context.unit.error(sym.pos, "a definition of `"+name1+"' already exists in " + clazz)
          NoSymbol
        } else {
          clazz.newMethod(sym.pos, name1)
            .setInfo(sym.info)
            .setFlag(sym.getFlag(DEFERRED | OVERRIDE | STATIC))
        }
      }

    val ts = new ListBuffer[Tree]

    def addBeanGetterMethod(sym: Symbol) = {
      val getter = beanSetterOrGetter(sym)
      if (getter != NoSymbol)
        ts += typer.typed(DefDef(
          getter,
          vparamss => if (sym hasFlag DEFERRED) EmptyTree else gen.mkAttributedRef(sym)))
    }

    def addBeanSetterMethod(sym: Symbol) = {
      val setter = beanSetterOrGetter(sym)
      if (setter != NoSymbol)
        ts += typer.typed(DefDef(
          setter,
          vparamss =>
            if (sym hasFlag DEFERRED) EmptyTree
            else Apply(gen.mkAttributedRef(sym), List(Ident(vparamss.head.head)))))
    }

    def isPublic(sym: Symbol) =
      !sym.hasFlag(PRIVATE | PROTECTED) && sym.privateWithin == NoSymbol

    if (!phase.erasedTypes) {
      try {
        if (clazz hasFlag CASE) {
          val isTop = !(clazz.info.baseClasses.tail exists (_ hasFlag CASE))
          // case classes are implicitly declared serializable
          clazz.attributes = AnnotationInfo(SerializableAttr.tpe, List(), List()) :: clazz.attributes

          if (isTop) {
            for (stat <- templ.body) {
              if (stat.isDef && stat.symbol.isMethod && stat.symbol.hasFlag(CASEACCESSOR) && !isPublic(stat.symbol)) {
                ts += newAccessorMethod(stat)
                stat.symbol.resetFlag(CASEACCESSOR)
              }
            }
            if (!inIDE && clazz.info.nonPrivateDecl(nme.tag) == NoSymbol) ts += tagMethod
          }
          if (clazz.isModuleClass) {
            if (!hasOverridingImplementation(Object_toString)) ts += moduleToStringMethod
          } else {
            if (!hasOverridingImplementation(Object_hashCode)) ts += forwardingMethod(nme.hashCode_)
            if (!hasOverridingImplementation(Object_toString)) ts += forwardingMethod(nme.toString_)
            if (!hasOverridingImplementation(Object_equals)) ts += equalsMethod
          }

          if (!hasOverridingImplementation(Product_productPrefix)) ts += productPrefixMethod
          val accessors = clazz.caseFieldAccessors
          if (!hasOverridingImplementation(Product_productArity))
            ts += productArityMethod(accessors.length)
          if (!hasOverridingImplementation(Product_productElement))
            ts += productElementMethod(accessors)
        }

        if (clazz.isModuleClass && isSerializable(clazz)) {
          // If you serialize a singleton and then deserialize it twice,
          // you will have two instances of your singleton, unless you implement
          // the readResolve() method (see http://www.javaworld.com/javaworld/
          // jw-04-2003/jw-0425-designpatterns_p.html)
          if (!hasImplementation(nme.readResolve)) ts += readResolveMethod
        }
        if (!forCLDC && !forMSIL)
          for (sym <- clazz.info.decls.toList)
            if (!sym.getAttributes(BeanPropertyAttr).isEmpty)
              if (sym.isGetter)
                addBeanGetterMethod(sym)
              else if (sym.isSetter)
                addBeanSetterMethod(sym)
              else if (sym.isMethod || sym.isType)
                context.unit.error(sym.pos, "attribute `BeanProperty' is not applicable to " + sym)
      } catch {
        case ex: TypeError =>
          if (!reporter.hasErrors) throw ex
      }
    }
    val synthetics = ts.toList
    copy.Template(
      templ, templ.parents, templ.self, if (synthetics.isEmpty) templ.body else templ.body ::: synthetics)
  }
}