alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Scala example source code file (CPSAnnotationChecker.scala)

This example Scala source code file (CPSAnnotationChecker.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Scala tags/keywords

bynameparamclass, int, io, list, list, markercpsadaptplus, markercpstypes, markercpstypes, nil, nil, select, tree, type, type, typeerror

The Scala CPSAnnotationChecker.scala source code

// $Id$

package scala.tools.selectivecps

import scala.tools.nsc.Global

import scala.collection.mutable.{Map, HashMap}

import java.io.{StringWriter, PrintWriter}

abstract class CPSAnnotationChecker extends CPSUtils {
  val global: Global
  import global._
  import definitions._

  //override val verbose = true
  @inline override final def vprintln(x: =>Any): Unit = if (verbose) println(x)

  /** 
   *  Checks whether @cps annotations conform
   */
  object checker extends AnnotationChecker {

    /** Check annotations to decide whether tpe1 <:< tpe2 */
    def annotationsConform(tpe1: Type, tpe2: Type): Boolean = {
      if (!cpsEnabled) return true

      vprintln("check annotations: " + tpe1 + " <:< " + tpe2)

      // Nothing is least element, but Any is not the greatest
      if (tpe1.typeSymbol eq NothingClass)
        return true
      
      val annots1 = filterAttribs(tpe1,MarkerCPSTypes)
      val annots2 = filterAttribs(tpe2,MarkerCPSTypes)
      
      // @plus and @minus should only occur at the left, and never together
      // TODO: insert check
      val adaptPlusAnnots1 = filterAttribs(tpe1,MarkerCPSAdaptPlus)
      val adaptMinusAnnots1 = filterAttribs(tpe1,MarkerCPSAdaptMinus)
      
      // @minus @cps is the same as no annotations
      if (!adaptMinusAnnots1.isEmpty)
        return annots2.isEmpty

      // to handle answer type modification, we must make @plus <:< @cps
      if (!adaptPlusAnnots1.isEmpty && annots1.isEmpty)
        return true

      // @plus @cps will fall through and compare the @cps type args

      // @cps parameters must match exactly
      if ((annots1 corresponds annots2) { _.atp <:< _.atp })
        return true

      false
    }
    

    /** Refine the computed least upper bound of a list of types. 
     *  All this should do is add annotations. */
    override def annotationsLub(tpe: Type, ts: List[Type]): Type = {
      if (!cpsEnabled) return tpe
      
      val annots1 = filterAttribs(tpe, MarkerCPSTypes)
      val annots2 = ts flatMap (filterAttribs(_, MarkerCPSTypes))
      
      if (annots2.nonEmpty) {
        val cpsLub = AnnotationInfo(global.lub(annots1:::annots2 map (_.atp)), Nil, Nil)
        val tpe1 = if (annots1.nonEmpty) removeAttribs(tpe, MarkerCPSTypes) else tpe
        tpe1.withAnnotation(cpsLub)
      } else tpe
    }

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

      val anyAtCPS = AnnotationInfo(appliedType(MarkerCPSTypes.tpe, List(NothingClass.tpe, AnyClass.tpe)), Nil, Nil)
      if (isFunctionType(tparams.head.owner.tpe) || tparams.head.owner == PartialFunctionClass) {
        vprintln("function bound: " + tparams.head.owner.tpe + "/"+bounds+"/"+targs)
        if (targs.last.hasAnnotation(MarkerCPSTypes))
          bounds.reverse match {
            case res::b if !res.hi.hasAnnotation(MarkerCPSTypes) => 
              (TypeBounds(res.lo, res.hi.withAnnotation(anyAtCPS))::b).reverse
            case _ => bounds
          }
        else
          bounds
      } else if (tparams.head.owner == ByNameParamClass) {
        vprintln("byname bound: " + tparams.head.owner.tpe + "/"+bounds+"/"+targs)
        if (targs.head.hasAnnotation(MarkerCPSTypes) && !bounds.head.hi.hasAnnotation(MarkerCPSTypes))
          TypeBounds(bounds.head.lo, bounds.head.hi.withAnnotation(anyAtCPS))::Nil
        else bounds
      } else
        bounds
    }


    override def canAdaptAnnotations(tree: Tree, mode: Int, pt: Type): Boolean = {
      if (!cpsEnabled) return false
      vprintln("can adapt annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)

      val annots1 = filterAttribs(tree.tpe,MarkerCPSTypes)
      val annots2 = filterAttribs(pt,MarkerCPSTypes)
      
      if ((mode & global.analyzer.PATTERNmode) != 0) {
        //println("can adapt pattern annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
        if (!annots1.isEmpty) {
          return true
        }
      }

/*
      // not precise enough -- still relying on addAnnotations to remove things from ValDef symbols
      if ((mode & global.analyzer.TYPEmode) != 0 && (mode & global.analyzer.BYVALmode) != 0) {
        if (!annots1.isEmpty) {
          return true
        }
      }
*/

/*
      this interferes with overloading resolution
      if ((mode & global.analyzer.BYVALmode) != 0 && tree.tpe <:< pt) {
        vprintln("already compatible, can't adapt further")
        return false
      }
*/
      if ((mode & global.analyzer.EXPRmode) != 0) {
        if ((annots1 corresponds annots2) { case (a1,a2) => a1.atp <:< a2.atp }) {
          vprintln("already same, can't adapt further")
          return false
        }

        if (annots1.isEmpty && !annots2.isEmpty && ((mode & global.analyzer.BYVALmode) == 0)) {
          //println("can adapt annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
          val adapt = AnnotationInfo(MarkerCPSAdaptPlus.tpe, Nil, Nil)
          if (!tree.tpe.annotations.contains(adapt)) {
  //          val base = tree.tpe <:< removeAllCPSAnnotations(pt)
  //          val known = global.analyzer.isFullyDefined(pt)
  //          println(same + "/" + base + "/" + known)
            //val same = annots2 forall { case AnnotationInfo(atp: TypeRef, _, _) => atp.typeArgs(0) =:= atp.typeArgs(1) }
            // TBD: use same or not?
            //if (same) {
              vprintln("yes we can!! (unit)")
              return true
            //}
          }
        } else if (!annots1.isEmpty && ((mode & global.analyzer.BYVALmode) != 0)) {
          if (!tree.tpe.hasAnnotation(MarkerCPSAdaptMinus)) {
            vprintln("yes we can!! (byval)")
            return true
          }
        }
      }
      false
    }
    
    
    override def adaptAnnotations(tree: Tree, mode: Int, pt: Type): Tree = {
      if (!cpsEnabled) return tree
      
      vprintln("adapt annotations " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)

      val annots1 = filterAttribs(tree.tpe,MarkerCPSTypes)
      val annots2 = filterAttribs(pt,MarkerCPSTypes)

      if ((mode & global.analyzer.PATTERNmode) != 0) {
        if (!annots1.isEmpty) {
          return tree.setType(removeAllCPSAnnotations(tree.tpe))
        }
      }

/*
      // doesn't work correctly -- still relying on addAnnotations to remove things from ValDef symbols
      if ((mode & global.analyzer.TYPEmode) != 0 && (mode & global.analyzer.BYVALmode) != 0) {
        if (!annots1.isEmpty) {
          println("removing annotation from " + tree + "/" + tree.tpe)
          val s = tree.setType(removeAllCPSAnnotations(tree.tpe))
          println(s)
          s
        }
      }
*/
      
      if ((mode & global.analyzer.EXPRmode) != 0) {
        if (annots1.isEmpty && !annots2.isEmpty && ((mode & global.analyzer.BYVALmode) == 0)) { // shiftUnit
          // add a marker annotation that will make tree.tpe behave as pt, subtyping wise
          // tree will look like having any possible annotation
          //println("adapt annotations " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
        
          val adapt = AnnotationInfo(MarkerCPSAdaptPlus.tpe, Nil, Nil)
          //val same = annots2 forall { case AnnotationInfo(atp: TypeRef, _, _) => atp.typeArgs(0) =:= atp.typeArgs(1) }
          // TBD: use same or not? see infer0.scala/infer1.scala

          // CAVEAT:
          //  for monomorphic answer types we want to have @plus @cps (for better checking)
          //  for answer type modification we want to have only @plus (because actual answer type may differ from pt)
      
          //val known = global.analyzer.isFullyDefined(pt)

          if (/*same &&*/ !tree.tpe.annotations.contains(adapt)) {
            //if (known)
              return tree.setType(tree.tpe.withAnnotations(adapt::annots2)) // needed for #1807
            //else
            //  return tree.setType(tree.tpe.withAnnotations(adapt::Nil))
          }
          tree
        } else if (!annots1.isEmpty && ((mode & global.analyzer.BYVALmode) != 0)) { // dropping annotation
          // add a marker annotation that will make tree.tpe behave as pt, subtyping wise
          // tree will look like having no annotation
          if (!tree.tpe.hasAnnotation(MarkerCPSAdaptMinus)) {
            val adapt = AnnotationInfo(MarkerCPSAdaptMinus.tpe, Nil, Nil)
            return tree.setType(tree.tpe.withAnnotations(adapt::Nil))
          }
        }
      }
      tree
    }


    def updateAttributesFromChildren(tpe: Type, childAnnots: List[AnnotationInfo], byName: List[Tree]): Type = {
      tpe match {
        // Would need to push annots into each alternative of overloaded type
        // But we can't, since alternatives aren't types but symbols, which we
        // can't change (we'd be affecting symbols globally)
        /*
        case OverloadedType(pre, alts) =>
          OverloadedType(pre, alts.map((sym: Symbol) => updateAttributes(pre.memberType(sym), annots)))
        */
        case _ =>
          assert(childAnnots forall (_.atp.typeSymbol == MarkerCPSTypes), childAnnots)
          /*
            [] + [] = []
            plus + [] = plus
            cps + [] = cps
            plus cps + [] = plus cps
            minus cps + [] = minus cp
            synth cps + [] = synth cps // <- synth on left - does it happen?

            [] + cps = cps
            plus + cps = synth cps
            cps + cps = cps! <- lin
            plus cps + cps = synth cps! <- unify
            minus cps + cps = minus cps! <- lin
            synth cps + cps = synth cps! <- unify
          */
          
          val plus = tpe.hasAnnotation(MarkerCPSAdaptPlus) || (tpe.hasAnnotation(MarkerCPSTypes) && 
                        byName.nonEmpty && byName.forall(_.tpe.hasAnnotation(MarkerCPSAdaptPlus)))
          
          // move @plus annotations outward from by-name children
          if (childAnnots.isEmpty) {
            if (plus) { // @plus or @plus @cps
              for (t <- byName) {
                //println("removeAnnotation " + t + " / " + t.tpe)
                t.setType(removeAttribs(t.tpe, MarkerCPSAdaptPlus, MarkerCPSTypes))
              }
              return tpe.withAnnotation(AnnotationInfo(MarkerCPSAdaptPlus.tpe, Nil, Nil))
            } else
              return tpe
          }

          val annots1 = filterAttribs(tpe, MarkerCPSTypes)

          if (annots1.isEmpty) { // nothing or @plus
            val synth = MarkerCPSSynth.tpe
            val annots2 = List(linearize(childAnnots))
            removeAttribs(tpe,MarkerCPSAdaptPlus).withAnnotations(AnnotationInfo(synth, Nil, Nil)::annots2)
          } else {
            val annot1 = single(annots1)
            if (plus) { // @plus @cps
              val synth = AnnotationInfo(MarkerCPSSynth.tpe, Nil, Nil)
              val annot2 = linearize(childAnnots)
              if (!(annot2.atp <:< annot1.atp))
                throw new TypeError(annot2 + " is not a subtype of " + annot1)
              val res = removeAttribs(tpe, MarkerCPSAdaptPlus, MarkerCPSTypes).withAnnotations(List(synth, annot2))
              for (t <- byName) {
                //println("removeAnnotation " + t + " / " + t.tpe)
                t.setType(removeAttribs(t.tpe, MarkerCPSAdaptPlus, MarkerCPSTypes))
              }
              res
            } else if (tpe.hasAnnotation(MarkerCPSSynth)) { // @synth @cps
              val annot2 = linearize(childAnnots)
              if (!(annot2.atp <:< annot1.atp))
                throw new TypeError(annot2 + " is not a subtype of " + annot1)
              removeAttribs(tpe, MarkerCPSTypes).withAnnotation(annot2)
            } else { // @cps
              removeAttribs(tpe, MarkerCPSTypes).withAnnotation(linearize(childAnnots:::annots1))
            }
          }
      }
    }





    def transArgList(fun: Tree, args: List[Tree]): List[List[Tree]] = {
      val formals = fun.tpe.paramTypes
      val overshoot = args.length - formals.length
    
      for ((a,tp) <- args.zip(formals ::: List.fill(overshoot)(NoType))) yield {
        tp match {
          case TypeRef(_, ByNameParamClass, List(elemtp)) =>
            Nil // TODO: check conformance??
          case _ =>
            List(a)
        }
      }
    }


    def transStms(stms: List[Tree]): List[Tree] = stms match {
      case ValDef(mods, name, tpt, rhs)::xs =>
        rhs::transStms(xs)
      case Assign(lhs, rhs)::xs =>
        rhs::transStms(xs)
      case x::xs =>
        x::transStms(xs)
      case Nil =>
        Nil
    }
    
    def single(xs: List[AnnotationInfo]) = xs match {
      case List(x) => x
      case _ =>
        global.globalError("not a single cps annotation: " + xs)// FIXME: error message
        xs(0)
    }

    def transChildrenInOrder(tree: Tree, tpe: Type, childTrees: List[Tree], byName: List[Tree]) = {
      val children = childTrees.flatMap { t =>
        if (t.tpe eq null) Nil else {
          val types = filterAttribs(t.tpe, MarkerCPSTypes)
          // TODO: check that it has been adapted and if so correctly
          if (types.isEmpty) Nil else List(single(types))
        }
      }
                            
      val newtpe = updateAttributesFromChildren(tpe, children, byName)
    
      if (!newtpe.annotations.isEmpty)
        vprintln("[checker] inferred " + tree + " / " + tpe + " ===> "+ newtpe)
      
      newtpe
    }

    /** Modify the type that has thus far been inferred
     *  for a tree.  All this should do is add annotations. */

    override def addAnnotations(tree: Tree, tpe: Type): Type = {
      if (!cpsEnabled) { 
        if (tpe.annotations.nonEmpty && tpe.hasAnnotation(MarkerCPSTypes))
          global.reporter.error(tree.pos, "this code must be compiled with the Scala continuations plugin enabled")
        return tpe 
      } 

//      if (tree.tpe.hasAnnotation(MarkerCPSAdaptPlus))
//        println("addAnnotation " + tree + "/" + tpe)

      tree match {

        case Apply(fun @ Select(qual, name), args) if fun.isTyped =>

          // HACK: With overloaded methods, fun will never get annotated. This is because
          // the 'overloaded' type gets annotated, but not the alternatives (among which
          // fun's type is chosen)

          vprintln("[checker] checking select apply " + tree + "/" + tpe)
          
          transChildrenInOrder(tree, tpe, qual::(transArgList(fun, args).flatten), Nil)

        case TypeApply(fun @ Select(qual, name), args) if fun.isTyped =>
          def stripNullaryMethodType(tp: Type) = tp match { case NullaryMethodType(restpe) => restpe case tp => tp }
          vprintln("[checker] checking select apply " + tree + "/" + tpe)

          transChildrenInOrder(tree, stripNullaryMethodType(tpe), List(qual, fun), Nil)

        case Apply(fun, args) if fun.isTyped =>

          vprintln("[checker] checking unknown apply " + tree + "/" + tpe)
          
          transChildrenInOrder(tree, tpe, fun::(transArgList(fun, args).flatten), Nil)

        case TypeApply(fun, args) =>

          vprintln("[checker] checking type apply " + tree + "/" + tpe)

          transChildrenInOrder(tree, tpe, List(fun), Nil)

        case Select(qual, name) if qual.isTyped =>

          vprintln("[checker] checking select " + tree + "/" + tpe)

          // straightforward way is problematic (see select.scala and Test2.scala)
          // transChildrenInOrder(tree, tpe, List(qual), Nil)

          // the problem is that qual may be of type OverloadedType (or MethodType) and
          // we cannot safely annotate these. so we just ignore these cases and
          // clean up later in the Apply/TypeApply trees.
          
          if (qual.tpe.hasAnnotation(MarkerCPSTypes)) {
            // however there is one special case:
            // if it's a method without parameters, just apply it. normally done in adapt, but
            // we have to do it here so we don't lose the cps information (wouldn't trigger our
            // adapt and there is no Apply/TypeApply created)
            tpe match {
              case NullaryMethodType(restpe) =>
                //println("yep: " + restpe + "," + restpe.getClass)
                transChildrenInOrder(tree, restpe, List(qual), Nil)
              case _ : PolyType => tpe
              case _ : MethodType => tpe
              case _ : OverloadedType => tpe
              case _ =>
                transChildrenInOrder(tree, tpe, List(qual), Nil)
            }
          } else
            tpe

        case If(cond, thenp, elsep) =>
          transChildrenInOrder(tree, tpe, List(cond), List(thenp, elsep))

        case Match(select, cases) =>
          // TODO: can there be cases that are not CaseDefs?? check collect vs map!
          transChildrenInOrder(tree, tpe, List(select), cases:::(cases collect { case CaseDef(_, _, body) => body }))

        case Try(block, catches, finalizer) =>
          val tpe1 = transChildrenInOrder(tree, tpe, Nil, block::catches:::(catches collect { case CaseDef(_, _, body) => body }))

          val annots = filterAttribs(tpe1, MarkerCPSTypes)
          if (annots.nonEmpty) {
            val ann = single(annots)
            val atp0::atp1::Nil = ann.atp.normalize.typeArgs
            if (!(atp0 =:= atp1))
              throw new TypeError("only simple cps types allowed in try/catch blocks (found: " + tpe1 + ")")
            if (!finalizer.isEmpty) // no finalizers allowed. see explanation in SelectiveCPSTransform
              reporter.error(tree.pos, "try/catch blocks that use continuations cannot have finalizers")
          }
          tpe1

        case Block(stms, expr) =>
          // if any stm has annotation, so does block
          transChildrenInOrder(tree, tpe, transStms(stms), List(expr))

        case ValDef(mods, name, tpt, rhs) =>
          vprintln("[checker] checking valdef " + name + "/"+tpe+"/"+tpt+"/"+tree.symbol.tpe)
          // ValDef symbols must *not* have annotations!
          if (hasAnswerTypeAnn(tree.symbol.info)) { // is it okay to modify sym here?
            vprintln("removing annotation from sym " + tree.symbol + "/" + tree.symbol.tpe + "/" + tpt)
            tpt.setType(removeAllCPSAnnotations(tpt.tpe))
            tree.symbol.setInfo(removeAllCPSAnnotations(tree.symbol.info))
          }
          tpe

        case _ =>
          tpe
      }

          
    }
  }
}

Other Scala examples (source code examples)

Here is a short list of links related to this Scala CPSAnnotationChecker.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.