Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
504 lines (411 sloc) 20 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 no annotation
// note that we are only adding a plus marker if the method's result type is a CPS type
// (annotsExpected.nonEmpty == cpsParamAnnotation(pt).nonEmpty)
val res = tree modifyType (_ withAnnotations List(newPlusMarker()))
vprintln("adapted annotations (return) of " + tree + " to " + res.tpe)
res
} else 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 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 ret @ Return(expr) =>
// only change type if this return will (a) be removed (in tail position) or (b) cause
// an error (not in tail position)
if (expr.tpe != null && hasPlusMarker(expr.tpe))
ret setType expr.tpe
ret.tpe
case _ =>
tpe
}
}
}
}
Jump to Line
Something went wrong with that request. Please try again.