Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

513 lines (420 sloc) 21.053 kb
// $Id$
package scala.tools.selectivecps
import scala.tools.nsc.Global
import scala.tools.nsc.typechecker.Modes
abstract class CPSAnnotationChecker extends CPSUtils with Modes {
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 {
private def addPlusMarker(tp: Type) = tp withAnnotation newPlusMarker()
private def addMinusMarker(tp: Type) = tp withAnnotation newMinusMarker()
private def cleanPlus(tp: Type) =
removeAttribs(tp, MarkerCPSAdaptPlus, MarkerCPSTypes)
private def cleanPlusWith(tp: Type)(newAnnots: AnnotationInfo*) =
cleanPlus(tp) withAnnotations newAnnots.toList
/** 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 = cpsParamAnnotation(tpe1)
val annots2 = cpsParamAnnotation(tpe2)
// @plus and @minus should only occur at the left, and never together
// TODO: insert check
// @minus @cps is the same as no annotations
if (hasMinusMarker(tpe1))
return annots2.isEmpty
// to handle answer type modification, we must make @plus <:< @cps
if (hasPlusMarker(tpe1) && 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
// Need to handle uninstantiated type vars specially:
// g map (x => x) with expected type List[Int] @cps
// results in comparison ?That <:< List[Int] @cps
// Instantiating ?That to an annotated type would fail during
// transformation.
// Instead we force-compare tpe1 <:< tpe2.withoutAnnotations
// to trigger instantiation of the TypeVar to the base type
// This is a bit unorthodox (we're only supposed to look at
// annotations here) but seems to work.
if (!annots2.isEmpty && !tpe1.isGround)
return tpe1 <:< tpe2.withoutAnnotations
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 = cpsParamAnnotation(tpe)
val annots2 = ts flatMap cpsParamAnnotation
if (annots2.nonEmpty) {
val cpsLub = newMarker(global.lub(annots1:::annots2 map (_.atp)))
val tpe1 = if (annots1.nonEmpty) removeAttribs(tpe, MarkerCPSTypes) else tpe
tpe1.withAnnotation(cpsLub)
}
else tpe
}
def isPartialFunctionType(tp: Type): Boolean = {
val sym = tp.typeSymbol
sym eq PartialFunctionClass
}
/** 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 = newCpsParamsMarker(NothingClass.tpe, AnyClass.tpe)
if (isFunctionType(tparams.head.owner.tpe) || isPartialFunctionType(tparams.head.owner.tpe)) {
vprintln("function bound: " + tparams.head.owner.tpe + "/"+bounds+"/"+targs)
if (hasCpsParamTypes(targs.last))
bounds.reverse match {
case res::b if !hasCpsParamTypes(res.hi) =>
(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)
val TypeBounds(lo, hi) = bounds.head
if (hasCpsParamTypes(targs.head) && !hasCpsParamTypes(hi))
TypeBounds(lo, 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 = cpsParamAnnotation(tree.tpe)
val annots2 = cpsParamAnnotation(pt)
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)(_.atp <:< _.atp)) {
vprintln("already same, can't adapt further")
false
} else if (annots1.isEmpty && !annots2.isEmpty && ((mode & global.analyzer.BYVALmode) == 0)) {
//println("can adapt annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt)
if (!hasPlusMarker(tree.tpe)) {
// 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)")
true
//}
} else false
} else if (!hasPlusMarker(tree.tpe) && annots1.isEmpty && !annots2.isEmpty && ((mode & global.analyzer.RETmode) != 0)) {
vprintln("checking enclosing method's result type without annotations")
tree.tpe <:< pt.withoutAnnotations
} else if (!hasMinusMarker(tree.tpe) && !annots1.isEmpty && ((mode & global.analyzer.BYVALmode) != 0)) {
val optCpsTypes: Option[(Type, Type)] = cpsParamTypes(tree.tpe)
val optExpectedCpsTypes: Option[(Type, Type)] = cpsParamTypes(pt)
if (optCpsTypes.isEmpty || optExpectedCpsTypes.isEmpty) {
vprintln("yes we can!! (byval)")
true
} else { // check cps param types
val cpsTpes = optCpsTypes.get
val cpsPts = optExpectedCpsTypes.get
// class cpsParam[-B,+C], therefore:
cpsPts._1 <:< cpsTpes._1 && cpsTpes._2 <:< cpsPts._2
}
} else false
} else false
}
override def adaptAnnotations(tree: Tree, mode: Int, pt: Type): Tree = {
if (!cpsEnabled) return tree
vprintln("adapt annotations " + tree + " / " + tree.tpe + " / " + modeString(mode) + " / " + pt)
val patMode = (mode & global.analyzer.PATTERNmode) != 0
val exprMode = (mode & global.analyzer.EXPRmode) != 0
val byValMode = (mode & global.analyzer.BYVALmode) != 0
val retMode = (mode & global.analyzer.RETmode) != 0
val annotsTree = cpsParamAnnotation(tree.tpe)
val annotsExpected = cpsParamAnnotation(pt)
// not sure I rephrased this comment correctly:
// replacing `patMode` in the condition below by `patMode || ((mode & global.analyzer.TYPEmode) != 0 && (mode & global.analyzer.BYVALmode))`
// doesn't work correctly -- still relying on addAnnotations to remove things from ValDef symbols
if (patMode && !annotsTree.isEmpty) tree modifyType removeAllCPSAnnotations
else if (exprMode && !byValMode && !hasPlusMarker(tree.tpe) && annotsTree.isEmpty && annotsExpected.nonEmpty) { // 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)
// 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 res = tree modifyType (_ withAnnotations newPlusMarker() :: annotsExpected) // needed for #1807
vprintln("adapted annotations (not by val) of " + tree + " to " + res.tpe)
res
} else if (exprMode && byValMode && !hasMinusMarker(tree.tpe) && annotsTree.nonEmpty) { // dropping annotation
// add a marker annotation that will make tree.tpe behave as pt, subtyping wise
// tree will look like having no annotation
val res = tree modifyType addMinusMarker
vprintln("adapted annotations (by val) of " + tree + " to " + res.tpe)
res
} else if (retMode && !hasPlusMarker(tree.tpe) && annotsTree.isEmpty && annotsExpected.nonEmpty) {
// add a marker annotation that will make tree.tpe behave as pt, subtyping wise
// tree will look like having any possible annotation
// note 1: we are only adding a plus marker if the method's result type is a cps type
// (annotsExpected.nonEmpty == cpsParamAnnotation(pt).nonEmpty)
// note 2: we are not adding the expected cps annotations, since they will be added
// by adaptTypeOfReturn (see below).
val res = tree modifyType (_ withAnnotations List(newPlusMarker()))
vprintln("adapted annotations (return) of " + tree + " to " + res.tpe)
res
} else tree
}
// only adapt type if this return will
// (a) be removed (in tail position and method's result type (pt) is cps type), or
// (b) cause an error
override def adaptTypeOfReturn(tree: Tree, pt: Type, default: => Type): Type = {
// only adapt if method's result type (pt) is cps type
val annots = cpsParamAnnotation(pt)
if (annots.nonEmpty) {
// return type of `tree` without plus marker, but only if it doesn't have other cps annots
if (hasPlusMarker(tree.tpe) && !hasCpsParamTypes(tree.tpe))
tree.setType(removeAttribs(tree.tpe, MarkerCPSAdaptPlus))
tree.tpe
} else default
}
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 OverloadedType(pre, alts) => tpe //reconstruct correct annotations later
case MethodType(params, restpe) => tpe
case PolyType(params, restpe) => tpe
case _ =>
assert(childAnnots forall (matches(_, MarkerCPSTypes)), childAnnots)
/*
[] + [] = []
plus + [] = plus
cps + [] = cps
plus cps + [] = plus cps
minus cps + [] = minus cps
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 = hasPlusMarker(tpe) || (
hasCpsParamTypes(tpe)
&& byName.nonEmpty
&& (byName forall (t => hasPlusMarker(t.tpe)))
)
// move @plus annotations outward from by-name children
if (childAnnots.isEmpty) return {
if (plus) { // @plus or @plus @cps
byName foreach (_ modifyType cleanPlus)
addPlusMarker(tpe)
}
else tpe
}
val annots1 = cpsParamAnnotation(tpe)
if (annots1.isEmpty) { // nothing or @plus
cleanPlusWith(tpe)(newSynthMarker(), linearize(childAnnots))
}
else {
val annot1 = single(annots1)
if (plus) { // @plus @cps
val annot2 = linearize(childAnnots)
if (annot2.atp <:< annot1.atp) {
try cleanPlusWith(tpe)(newSynthMarker(), annot2)
finally byName foreach (_ modifyType cleanPlus)
}
else throw new TypeError(annot2 + " is not a subtype of " + annot1)
}
else if (hasSynthMarker(tpe)) { // @synth @cps
val annot2 = linearize(childAnnots)
if (annot2.atp <:< annot1.atp)
cleanPlusWith(tpe)(annot2)
else
throw new TypeError(annot2 + " is not a subtype of " + annot1)
}
else // @cps
cleanPlusWith(tpe)(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)
xs(0)
}
def emptyOrSingleList(xs: List[AnnotationInfo]) = if (xs.isEmpty) Nil else List(single(xs))
def transChildrenInOrder(tree: Tree, tpe: Type, childTrees: List[Tree], byName: List[Tree]) = {
def inspect(t: Tree): List[AnnotationInfo] = {
if (t.tpe eq null) Nil else {
val extra: List[AnnotationInfo] = t.tpe match {
case _: MethodType | _: PolyType | _: OverloadedType =>
// method types, poly types and overloaded types do not obtain cps annotions by propagation
// need to reconstruct transitively from their children.
t match {
case Select(qual, name) => inspect(qual)
case Apply(fun, args) => (fun::(transArgList(fun,args).flatten)) flatMap inspect
case TypeApply(fun, args) => (fun::(transArgList(fun,args).flatten)) flatMap inspect
case _ => Nil
}
case _ => Nil
}
val types = cpsParamAnnotation(t.tpe)
// TODO: check that it has been adapted and if so correctly
extra ++ emptyOrSingleList(types)
}
}
val children = childTrees flatMap inspect
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 (hasCpsParamTypes(tpe))
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 Apply(TypeApply(fun @ Select(qual, name), targs), args) if fun.isTyped => // not trigge
vprintln("[checker] checking select apply type-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 type-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 unknown 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 (hasCpsParamTypes(qual.tpe)) {
// 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) =>
transChildrenInOrder(tree, tpe, List(select), cases:::(cases map { case CaseDef(_, _, body) => body }))
case Try(block, catches, finalizer) =>
val tpe1 = transChildrenInOrder(tree, tpe, Nil, block::catches:::(catches map { case CaseDef(_, _, body) => body }))
val annots = cpsParamAnnotation(tpe1)
if (annots.nonEmpty) {
val ann = single(annots)
val (atp0, atp1) = annTypes(ann)
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 modifyType removeAllCPSAnnotations
tree.symbol modifyInfo removeAllCPSAnnotations
}
tpe
case _ =>
tpe
}
}
}
}
Jump to Line
Something went wrong with that request. Please try again.