/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author
 */
// $Id: Constructors.scala 18387 2009-07-24 15:28:37Z odersky $

package scala.tools.nsc
package transform

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

/** This phase converts classes with parameters into Java-like classes with 
 *  fields, which are assigned to from constructors.
 */  
abstract class Constructors extends Transform with ast.TreeDSL {
  import global._
  import definitions._

  /** the following two members override abstract members in Transform */
  val phaseName: String = "constructors"

  protected def newTransformer(unit: CompilationUnit): Transformer =
    new ConstructorTransformer(unit)

  class ConstructorTransformer(unit: CompilationUnit) extends Transformer {

    def transformClassTemplate(impl: Template): Template = {
      val clazz = impl.symbol.owner  // the transformed class
      val stats = impl.body          // the transformed template body
      val localTyper = typer.atOwner(impl, clazz) 

      var constr: DefDef = null      // The primary constructor
      var constrParams: List[Symbol] = null // ... and its parameters
      var constrBody: Block = null   // ... and its body

      // decompose primary constructor into the three entities above.
      for (stat <- stats) {
        stat match {
          case ddef @ DefDef(_, _, _, List(vparams), _, rhs @ Block(_, Literal(_))) =>
            if (ddef.symbol.isPrimaryConstructor) {
              constr = ddef
              constrParams = vparams map (_.symbol)
              constrBody = rhs
            }
          case _ =>
        }
      }
      assert((constr ne null) && (constrBody ne null), impl)

      // The parameter accessor fields which are members of the class
      val paramAccessors = clazz.constrParamAccessors

      // The constructor parameter corresponding to an accessor
      def parameter(acc: Symbol): Symbol = 
        parameterNamed(nme.getterName(acc.originalName))

      // The constructor parameter with given name. This means the parameter
      // has given name, or starts with given name, and continues with a `$' afterwards.
      def parameterNamed(name: Name): Symbol = {
        def matchesName(param: Symbol) = 
          param.name == name ||
          param.name.startsWith(name) && param.name(name.length) == '$'
        val ps = constrParams filter matchesName
        if (ps.isEmpty) assert(false, "" + name + " not in " + constrParams)
        ps.head
      }

      var thisRefSeen: Boolean = false

      // A transformer for expressions that go into the constructor
      val intoConstructorTransformer = new Transformer {
        override def transform(tree: Tree): Tree = tree match {
          case Apply(Select(This(_), _), List()) =>
            // references to parameter accessor methods of own class become references to parameters
            // outer accessors become references to $outer parameter 
            if ((tree.symbol hasFlag PARAMACCESSOR) && tree.symbol.owner == clazz)
              gen.mkAttributedIdent(parameter(tree.symbol.accessed)) setPos tree.pos
            else if (tree.symbol.outerSource == clazz && !clazz.isImplClass)
              gen.mkAttributedIdent(parameterNamed(nme.OUTER)) setPos tree.pos
            else 
              super.transform(tree)
          case Select(This(_), _)
          if ((tree.symbol hasFlag PARAMACCESSOR) && !tree.symbol.isSetter && tree.symbol.owner == clazz) =>
            // references to parameter accessor field of own class become references to parameters
            gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos
          case Select(_, _) =>
            thisRefSeen = true
            super.transform(tree)
          case This(_) =>
            thisRefSeen = true
            super.transform(tree)
          case Super(_, _) =>
            thisRefSeen = true
            super.transform(tree)
          case _ =>
            super.transform(tree)
        }
      }

      // Move tree into constructor, take care of changing owner from `oldowner' to constructor symbol
      def intoConstructor(oldowner: Symbol, tree: Tree) =
        intoConstructorTransformer.transform(
          new ChangeOwnerTraverser(oldowner, constr.symbol)(tree))

      // Should tree be moved in front of super constructor call?
      def canBeMoved(tree: Tree) = tree match {
        //todo: eliminate thisRefSeen
        case ValDef(mods, _, _, _) => 
          if (settings.Xwarninit.value)
            if (!(mods hasFlag PRESUPER | PARAMACCESSOR) && !thisRefSeen &&
                { val g = tree.symbol.getter(tree.symbol.owner);
                 g != NoSymbol && !g.allOverriddenSymbols.isEmpty 
               })
              unit.warning(tree.pos, "the semantics of this definition has changed;\nthe initialization is no longer be executed before the superclass is called")
          (mods hasFlag PRESUPER | PARAMACCESSOR)// || !thisRefSeen && (!settings.future.value && !settings.checkInit.value)
        case _ => false
      }

      // Create an assignment to class field `to' with rhs `from'
      def mkAssign(to: Symbol, from: Tree): Tree =
        localTyper.typed {
          //util.trace("compiling "+unit+" ") {
            atPos(to.pos) {
              Assign(Select(This(clazz), to), from)
            }
          //}
        }

      // Create code to copy parameter to parameter accessor field. 
      // If parameter is $outer, check that it is not null.
      def copyParam(to: Symbol, from: Symbol): Tree = {
        import CODE._
        var result = mkAssign(to, Ident(from))
        if (from.name == nme.OUTER)
          result =
            atPos(to.pos) {
              localTyper.typed {
                IF (from ANY_EQ NULL) THEN THROW(NullPointerExceptionClass) ELSE result
              }
            }
            
        result
      }

      // The list of definitions that go into class
      val defBuf = new ListBuffer[Tree]

      // The list of statements that go into constructor after superclass constructor call
      val constrStatBuf = new ListBuffer[Tree]

      // The list of statements that go into constructor before superclass constructor call
      val constrPrefixBuf = new ListBuffer[Tree]

      // The early initialized field definitions of the class (these are the class members)
      val presupers = treeInfo.preSuperFields(stats)

      // generate code to copy pre-initialized fields
      for (stat <- constrBody.stats) {
        constrStatBuf += stat
        stat match {
          case ValDef(mods, name, _, _) if (mods hasFlag PRESUPER) =>
            // stat is the constructor-local definition of the field value
            val fields = presupers filter (
              vdef => nme.localToGetter(vdef.name) == name)
            assert(fields.length == 1)
            constrStatBuf += mkAssign(fields.head.symbol, Ident(stat.symbol))
          case _ =>
        }
      }

      // Triage all template definitions to go into defBuf, constrStatBuf, or constrPrefixBuf.
      for (stat <- stats) stat match {
        case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
          // methods with constant result type get literals as their body
          // all methods except the primary constructor go into template
          stat.symbol.tpe match {
            case MethodType(List(), tp @ ConstantType(c)) =>
              defBuf += treeCopy.DefDef(
                stat, mods, name, tparams, vparamss, tpt,
                Literal(c) setPos rhs.pos setType tp)
            case _ =>
              if (!stat.symbol.isPrimaryConstructor) defBuf += stat
          }
        case ValDef(mods, name, tpt, rhs) =>
          // val defs with constant right-hand sides are eliminated.
          // for all other val defs, an empty valdef goes into the template and 
          // the initializer goes as an assignment into the constructor
          // if the val def is an early initialized or a parameter accessor, it goes
          // before the superclass constructor call, otherwise it goes after.
          // Lazy vals don't get the assignment in the constructor.
          if (!stat.symbol.tpe.isInstanceOf[ConstantType]) {
            if (rhs != EmptyTree && !stat.symbol.hasFlag(LAZY)) {
              val rhs1 = intoConstructor(stat.symbol, rhs);
              (if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign(
                stat.symbol, rhs1)
            }
            defBuf += treeCopy.ValDef(stat, mods, name, tpt, EmptyTree)
          }
        case ClassDef(_, _, _, _) =>
          // classes are treated recursively, and left in the template
          defBuf += new ConstructorTransformer(unit).transform(stat)
        case _ =>
          // all other statements go into the constructor
          constrStatBuf += intoConstructor(impl.symbol, stat)
      }

      // ----------- avoid making fields for symbols that are not accessed --------------

      // A sorted set of symbols that are known to be accessed outside the primary constructor.
      val accessedSyms = new TreeSet[Symbol]((x, y) => x isLess y)

      // a list of outer accessor symbols and their bodies
      var outerAccessors: List[(Symbol, Tree)] = List()

      // Could symbol's definition be omitted, provided it is not accessed?
      // This is the case if the symbol is defined in the current class, and
      // ( the symbol is an object private parameter accessor field, or
      //   the symbol is an outer accessor of a final class which does not override another outer accesser. )
      def maybeOmittable(sym: Symbol) = 
        (sym.owner == clazz &&
         ((sym hasFlag PARAMACCESSOR) && sym.isPrivateLocal ||
          sym.isOuterAccessor && sym.owner.isFinal && sym.allOverriddenSymbols.isEmpty))

      // Is symbol known to be accessed outside of the primary constructor,
      // or is it a symbol whose definition cannot be omitted anyway? 
      def mustbeKept(sym: Symbol) =
        !maybeOmittable(sym) || (accessedSyms contains sym)

      // A traverser to set accessedSyms and outerAccessors
      val accessTraverser = new Traverser {
        override def traverse(tree: Tree) = {
          tree match {
            case DefDef(_, _, _, _, _, body) 
            if (tree.symbol.isOuterAccessor && tree.symbol.owner == clazz && clazz.isFinal) =>
              outerAccessors ::= (tree.symbol, body)
            case Select(_, _) =>
              if (!mustbeKept(tree.symbol)) accessedSyms addEntry tree.symbol
              super.traverse(tree)
            case _ =>
              super.traverse(tree)
          }
        }
      }

      // first traverse all definitions except outeraccesors 
      // (outeraccessors are avoided in accessTraverser)
      for (stat <- defBuf.iterator) accessTraverser.traverse(stat) 

      // then traverse all bodies of outeraccessors which are accessed themselves
      // note: this relies on the fact that an outer accessor never calls another
      // outer accessor in the same class.
      for ((accSym, accBody) <- outerAccessors) 
        if (mustbeKept(accSym)) accessTraverser.traverse(accBody)

      // Conflicting symbol list from parents: see bug #1960.
      // It would be better to mangle the constructor parameter name since
      // it can only be used internally, but I think we need more robust name
      // mangling before we introduce more of it.
      val parentSymbols = Map((for {
        p <- impl.parents
        if p.symbol.isTrait
        sym <- p.symbol.info.nonPrivateMembers
        if sym.isGetter && !sym.isOuterField
      } yield sym.name -> p): _*)

      // Initialize all parameters fields that must be kept.
      val paramInits = 
        for (acc <- paramAccessors if mustbeKept(acc)) yield {          
          if (parentSymbols contains acc.name)
            unit.error(acc.pos, "parameter '%s' requires field but conflicts with %s in '%s'".format(
              acc.name, acc.name, parentSymbols(acc.name)))
          
          copyParam(acc, parameter(acc))
        }
      
      // Assemble final constructor
      defBuf += treeCopy.DefDef(
        constr, constr.mods, constr.name, constr.tparams, constr.vparamss, constr.tpt,
        treeCopy.Block(
          constrBody,
          paramInits ::: constrPrefixBuf.toList ::: constrStatBuf.toList,
          constrBody.expr));

      // Unlink all fields that can be dropped from class scope
      for (sym <- clazz.info.decls.toList) 
        if (!mustbeKept(sym)) clazz.info.decls unlink sym

      // Eliminate all field definitions that can be dropped from template
      treeCopy.Template(impl, impl.parents, impl.self, 
        defBuf.toList filter (stat => mustbeKept(stat.symbol)))
    } // transformClassTemplate

    override def transform(tree: Tree): Tree = 
      tree match {
        case ClassDef(mods, name, tparams, impl) if !tree.symbol.hasFlag(INTERFACE) =>
          treeCopy.ClassDef(tree, mods, name, tparams, transformClassTemplate(impl))
        case _ =>
          super.transform(tree)
      }
  } // ConstructorTransformer
}