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

package scala.reflect
package internal

/** Additions to the type checker that can be added at
 *  run time.  Typically these are added by
 *  compiler plugins. */
trait AnnotationCheckers {
  self: SymbolTable =>


  /** An additional checker for annotations on types.
   *  Typically these are registered by compiler plugins
   *  with the addAnnotationChecker method. */
  abstract class AnnotationChecker {
    /** Check the annotations on two types conform. */
    def annotationsConform(tpe1: Type, tpe2: Type): Boolean

    /** Refine the computed least upper bound of a list of types.
     *  All this should do is add annotations. */
    def annotationsLub(tp: Type, ts: List[Type]): Type = tp

    /** Refine the computed greatest lower bound of a list of types.
     *  All this should do is add annotations. */
    def annotationsGlb(tp: Type, ts: List[Type]): Type = tp

    /** Refine the bounds on type parameters to the given type arguments. */
    def adaptBoundsToAnnotations(bounds: List[TypeBounds],
      tparams: List[Symbol], targs: List[Type]): List[TypeBounds] = bounds

    /** Modify the type that has thus far been inferred
     *  for a tree.  All this should do is add annotations. */
    def addAnnotations(tree: Tree, tpe: Type): Type = tpe

    /** Decide whether this annotation checker can adapt a tree
     *  that has an annotated type to the given type tp, taking
     *  into account the given mode (see method adapt in trait Typers).*/
    def canAdaptAnnotations(tree: Tree, mode: Int, pt: Type): Boolean = false

    /** Adapt a tree that has an annotated type to the given type tp,
     *  taking into account the given mode (see method adapt in trait Typers).
     *  An implementation cannot rely on canAdaptAnnotations being called
     *  before. If the implementing class cannot do the adaptiong, it
     *  should return the tree unchanged.*/
    def adaptAnnotations(tree: Tree, mode: Int, pt: Type): Tree = tree

    /** Adapt the type of a return expression. The decision of an annotation checker
     *  whether the type should be adapted is based on the type of the expression
     *  which is returned, as well as the result type of the method (pt).
     *  By default, this method simply returns the passed `default` type.
     */
    def adaptTypeOfReturn(tree: Tree, pt: Type, default: => Type): Type = default
  }

  // Syncnote: Annotation checkers inaccessible to reflection, so no sync in var necessary.
  /** The list of annotation checkers that have been registered */
  private var annotationCheckers: List[AnnotationChecker] = Nil

  /** Register an annotation checker.  Typically these
   *  are added by compiler plugins. */
  def addAnnotationChecker(checker: AnnotationChecker) {
    if (!(annotationCheckers contains checker))
      annotationCheckers = checker :: annotationCheckers
  }

  /** Remove all annotation checkers */
  def removeAllAnnotationCheckers() {
    annotationCheckers = Nil
  }

  /** Check that the annotations on two types conform.  To do
   *  so, consult all registered annotation checkers. */
  def annotationsConform(tp1: Type, tp2: Type): Boolean = {
    /* Finish quickly if there are no annotations */
    if (tp1.annotations.isEmpty && tp2.annotations.isEmpty)
      true
    else
     annotationCheckers.forall(
       _.annotationsConform(tp1,tp2))
  }

  /** Refine the computed least upper bound of a list of types.
   *  All this should do is add annotations. */
  def annotationsLub(tpe: Type, ts: List[Type]): Type = {
    annotationCheckers.foldLeft(tpe)((tpe, checker) =>
      checker.annotationsLub(tpe, ts))
  }

  /** Refine the computed greatest lower bound of a list of types.
   *  All this should do is add annotations. */
  def annotationsGlb(tpe: Type, ts: List[Type]): Type = {
    annotationCheckers.foldLeft(tpe)((tpe, checker) =>
      checker.annotationsGlb(tpe, ts))
  }

  /** Refine the bounds on type parameters to the given type arguments. */
  def adaptBoundsToAnnotations(bounds: List[TypeBounds],
    tparams: List[Symbol], targs: List[Type]): List[TypeBounds] = {
      annotationCheckers.foldLeft(bounds)((bounds, checker) =>
        checker.adaptBoundsToAnnotations(bounds, tparams, targs))
  }

  /** Let all annotations checkers add extra annotations
   *  to this tree's type. */
  def addAnnotations(tree: Tree, tpe: Type): Type = {
    annotationCheckers.foldLeft(tpe)((tpe, checker) =>
      checker.addAnnotations(tree, tpe))
  }

  /** Find out whether any annotation checker can adapt a tree
   *  to a given type. Called by Typers.adapt. */
  def canAdaptAnnotations(tree: Tree, mode: Int, pt: Type): Boolean = {
    annotationCheckers.exists(_.canAdaptAnnotations(tree, mode, pt))
  }

  /** Let registered annotation checkers adapt a tree
   *  to a given type (called by Typers.adapt). Annotation checkers
   *  that cannot do the adaption should pass the tree through
   *  unchanged. */
  def adaptAnnotations(tree: Tree, mode: Int, pt: Type): Tree = {
    annotationCheckers.foldLeft(tree)((tree, checker) =>
      checker.adaptAnnotations(tree, mode, pt))
  }

  /** Let a registered annotation checker adapt the type of a return expression.
   *  Annotation checkers that cannot do the adaptation should simply return
   *  the `default` argument.
   *  
   *  Note that the result is undefined if more than one annotation checker
   *  returns an adapted type which is not a subtype of `default`.
   */
  def adaptTypeOfReturn(tree: Tree, pt: Type, default: => Type): Type = {
    val adaptedTypes = annotationCheckers flatMap { checker =>
      val adapted = checker.adaptTypeOfReturn(tree, pt, default)
      if (!(adapted <:< default)) List(adapted)
      else List()
    }
    adaptedTypes match {
      case fst :: _ => fst
      case List() => default
    }
  }
}