/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author  Martin Odersky
 */

package scala.tools.nsc
package ast

/** This class ...
 *
 *  @author Martin Odersky
 *  @version 1.0
 */
abstract class TreeInfo extends scala.reflect.internal.TreeInfo {
  val global: Global
  import global._
  import definitions._

  // arg1.op(arg2) returns (arg1, op.symbol, arg2)
  object BinaryOp {
    def unapply(t: Tree): Option[(Tree, Symbol, Tree)] = t match {
      case Apply(sel @ Select(arg1, _), arg2 :: Nil) => Some((arg1, sel.symbol, arg2))
      case _                                         => None
    }
  }
  // recv.op[T1, ...] returns (recv, op.symbol, type argument types)
  object TypeApplyOp {
    def unapply(t: Tree): Option[(Tree, Symbol, List[Type])] = t match {
      case TypeApply(sel @ Select(recv, _), targs) => Some((recv, sel.symbol, targs map (_.tpe)))
      case _                                       => None
    }
  }

  // x.asInstanceOf[T] returns (x, typeOf[T])
  object AsInstanceOf {
    def unapply(t: Tree): Option[(Tree, Type)] = t match {
      case Apply(TypeApplyOp(recv, Object_asInstanceOf, tpe :: Nil), Nil) => Some((recv, tpe))
      case _                                                              => None
    }
  }

  // Extractors for value classes.
  object ValueClass {
    def isValueClass(tpe: Type)                  = enteringErasure(tpe.typeSymbol.isDerivedValueClass)
    def valueUnbox(tpe: Type)                    = enteringErasure(tpe.typeSymbol.derivedValueClassUnbox)

    // B.unbox. Returns B.
    object Unbox {
      def unapply(t: Tree): Option[Tree] = t match {
        case Apply(sel @ Select(ref, _), Nil) if valueUnbox(ref.tpe) == sel.symbol => Some(ref)
        case _                                                                     => None
      }
    }
    // new B(v). Returns B and v.
    object Box {
      def unapply(t: Tree): Option[(Tree, Type)] = t match {
        case Apply(sel @ Select(New(tpt), nme.CONSTRUCTOR), v :: Nil) => Some((v, tpt.tpe.finalResultType))
        case _                                                        => None
      }
    }
    // (new B(v)).unbox. returns v.
    object BoxAndUnbox {
      def unapply(t: Tree): Option[Tree] = t match {
        case Unbox(Box(v, tpe)) if isValueClass(tpe) => Some(v)
        case _                                       => None
      }
    }
    // new B(v1) op new B(v2) where op is == or !=. Returns v1, op, v2.
    object BoxAndCompare {
      def unapply(t: Tree): Option[(Tree, Symbol, Tree)] = t match {
        case BinaryOp(Box(v1, tpe1), op @ (Object_== | Object_!=), Box(v2, tpe2)) if isValueClass(tpe1) && tpe1 =:= tpe2 => Some((v1, op, v2))
        case _                                                                                                           => None
      }
    }
  }

  // TODO these overrides, and the slow trickle of bugs that they solve (e.g. scala/bug#8479),
  //      suggest that we should pursue an alternative design in which the DocDef nodes
  //      are eliminated from the tree before typer, and instead are modelled as tree
  //      attachments.

  /** Is tree legal as a member definition of an interface?
   */
  override def isInterfaceMember(tree: Tree): Boolean = tree match {
    case DocDef(_, definition)         => isInterfaceMember(definition)
    case _ => super.isInterfaceMember(tree)
  }

  override def isConstructorWithDefault(t: Tree) = t match {
    case DocDef(_, definition) => isConstructorWithDefault(definition)
    case _ => super.isConstructorWithDefault(t)
  }

  /** Is tree a pure (i.e. non-side-effecting) definition?
   */
  override def isPureDef(tree: Tree): Boolean = tree match {
    case DocDef(_, definition) => isPureDef(definition)
    case _ => super.isPureDef(tree)
  }

  override def firstConstructor(stats: List[Tree]): Tree = {
    def unwrap(stat: Tree): Tree = stat match {
      case DocDef(_, defn) => unwrap(defn)
      case tree => tree
    }
    super.firstConstructor(stats map unwrap)
  }
}
