diff options
author | Tiark Rompf <tiark.rompf@epfl.ch> | 2010-03-11 16:55:38 +0000 |
---|---|---|
committer | Tiark Rompf <tiark.rompf@epfl.ch> | 2010-03-11 16:55:38 +0000 |
commit | f584d243487dcd1214291167707e2f53fef5ab5e (patch) | |
tree | af3e926d301193b259d27567470b4c483efbecbe | |
parent | 356540e284e9c9407151a44afdb9480d8eb137a1 (diff) | |
download | scala-f584d243487dcd1214291167707e2f53fef5ab5e.tar.gz scala-f584d243487dcd1214291167707e2f53fef5ab5e.tar.bz2 scala-f584d243487dcd1214291167707e2f53fef5ab5e.zip |
moved the continuations plugin into trunk.
78 files changed, 2322 insertions, 5 deletions
@@ -370,7 +370,7 @@ LOCAL REFERENCE BUILD (LOCKER) <touch file="${build-locker.dir}/compiler.complete" verbose="no"/> <stopwatch name="locker.comp.timer" action="total"/> </target> - + <target name="locker.done" depends="locker.comp"> <touch file="${build-locker.dir}/all.complete" verbose="no"/> <path id="locker.classpath"> @@ -447,6 +447,7 @@ QUICK BUILD (QUICK) <include name="library/**"/> <include name="dbc/**"/> <include name="actors/**"/> + <include name="continuations/**"/> <include name="swing/**"/> </srcfiles> </uptodate> @@ -598,7 +599,62 @@ QUICK BUILD (QUICK) <stopwatch name="quick.comp.timer" action="total"/> </target> - <target name="quick.pre-scalap" depends="quick.comp"> + + <target name="quick.pre-plugins" depends="quick.comp" unless="quick.available"> + <condition property="quick.plugins.needed"> + <not><available file="${build-quick.dir}/plugins.complete"/></not> + </condition> + </target> + + <target name="quick.plugins" depends="quick.pre-plugins" if="quick.plugins.needed"> + <stopwatch name="quick.plugins.timer"/> + <mkdir dir="${build-quick.dir}/classes/continuations-plugin"/> + <scalacfork + destdir="${build-quick.dir}/classes/continuations-plugin" + compilerpathref="locker.classpath" + params="${scalac.args.quick}" + srcdir="${src.dir}/continuations/plugin" + jvmargs="${scalacfork.jvmargs}"> + <include name="**/*.scala"/> + <compilationpath> + <pathelement location="${build-quick.dir}/classes/library"/> + <pathelement location="${build-quick.dir}/classes/compiler"/> + </compilationpath> + </scalacfork> + <copy todir="${build-quick.dir}/classes/continuations-plugin"> + <fileset dir="${src.dir}/continuations/plugin"> + <include name="**/*.tmpl"/> + <include name="**/*.xml"/> + <include name="**/*.js"/> + <include name="**/*.css"/> + <include name="**/*.properties"/> + <include name="**/*.swf"/> + <include name="**/*.png"/> + </fileset> + </copy> + <!-- not very nice to create jar here but needed to load plugin --> + <mkdir dir="${build-quick.dir}/plugins"/> + <jar destfile="${build-quick.dir}/plugins/continuations.jar"> + <fileset dir="${build-quick.dir}/classes/continuations-plugin"/> + </jar> + <!-- might split off library part into its own ant target --> + <scalacfork + destdir="${build-quick.dir}/classes/library" + compilerpathref="locker.classpath" + params="${scalac.args.quick} -Xplugin:${build-quick.dir}/plugins/continuations.jar -Xplugin-require:continuations -P:continuations:enable" + srcdir="${src.dir}/continuations/library" + jvmargs="${scalacfork.jvmargs}"> + <include name="**/*.scala"/> + <compilationpath> + <pathelement location="${build-quick.dir}/classes/library"/> + <pathelement location="${lib.dir}/forkjoin.jar"/> + </compilationpath> + </scalacfork> + <touch file="${build-quick.dir}/plugins.complete" verbose="no"/> + <stopwatch name="quick.plugins.timer" action="total"/> + </target> + + <target name="quick.pre-scalap" depends="quick.plugins"> <uptodate property="quick.scalap.available" targetfile="${build-quick.dir}/scalap.complete"> <srcfiles dir="${src.dir}/scalap"/> </uptodate> @@ -802,7 +858,21 @@ PACKED QUICK BUILD (PACK) <copy file="${jline.jar}" toDir="${build-pack.dir}/lib"/> </target> - <target name="pack.pre-partest" depends="pack.comp"> + <target name="pack.pre-plugins" depends="pack.comp"> + <uptodate + property="pack.plugins.available" + targetfile="${build-pack.dir}/plugins/continuations.jar" + srcfile="${build-quick.dir}/plugins.complete"/> + </target> + + <target name="pack.plugins" depends="pack.pre-plugins" unless="pack.plugins.available"> + <mkdir dir="${build-pack.dir}/plugins"/> + <jar destfile="${build-pack.dir}/plugins/continuations.jar"> + <fileset dir="${build-quick.dir}/classes/continuations-plugin"/> + </jar> + </target> + + <target name="pack.pre-partest" depends="pack.plugins"> <uptodate property="pack.partest.available" targetfile="${build-pack.dir}/lib/scala-partest.jar" @@ -1041,7 +1111,61 @@ BOOTSTRAPPING BUILD (STRAP) <stopwatch name="strap.comp.timer" action="total"/> </target> - <target name="strap.pre-scalap" depends="strap.comp"> + <target name="strap.pre-plugins" depends="strap.comp" unless="strap.available"> + <condition property="strap.plugins.needed"> + <not><available file="${build-strap.dir}/plugins.complete"/></not> + </condition> + </target> + + <target name="strap.plugins" depends="strap.pre-plugins" if="strap.plugins.needed"> + <stopwatch name="strap.plugins.timer"/> + <mkdir dir="${build-strap.dir}/classes/continuations-plugin"/> + <scalacfork + destdir="${build-strap.dir}/classes/continuations-plugin" + compilerpathref="pack.classpath" + params="${scalac.args.quick}" + srcdir="${src.dir}/continuations/plugin" + jvmargs="${scalacfork.jvmargs}"> + <include name="**/*.scala"/> + <compilationpath> + <pathelement location="${build-strap.dir}/classes/library"/> + <pathelement location="${build-strap.dir}/classes/compiler"/> + </compilationpath> + </scalacfork> + <copy todir="${build-strap.dir}/classes/continuations-plugin"> + <fileset dir="${src.dir}/continuations/plugin"> + <include name="**/*.tmpl"/> + <include name="**/*.xml"/> + <include name="**/*.js"/> + <include name="**/*.css"/> + <include name="**/*.properties"/> + <include name="**/*.swf"/> + <include name="**/*.png"/> + </fileset> + </copy> + <!-- not very nice to create jar here but needed to load plugin --> + <mkdir dir="${build-strap.dir}/plugins"/> + <jar destfile="${build-strap.dir}/plugins/continuations.jar"> + <fileset dir="${build-strap.dir}/classes/continuations-plugin"/> + </jar> + <!-- might split off library part into its own ant target --> + <scalacfork + destdir="${build-strap.dir}/classes/library" + compilerpathref="pack.classpath" + params="${scalac.args.quick} -Xplugin:${build-strap.dir}/plugins/continuations.jar -Xplugin-require:continuations -P:continuations:enable" + srcdir="${src.dir}/continuations/library" + jvmargs="${scalacfork.jvmargs}"> + <include name="**/*.scala"/> + <compilationpath> + <pathelement location="${build-strap.dir}/classes/library"/> + <pathelement location="${lib.dir}/forkjoin.jar"/> + </compilationpath> + </scalacfork> + <touch file="${build-strap.dir}/plugins.complete" verbose="no"/> + <stopwatch name="strap.plugins.timer" action="total"/> + </target> + + <target name="strap.pre-scalap" depends="strap.plugins"> <uptodate property="strap.scalap.available" targetfile="${build-strap.dir}/scalap.complete"> <srcfiles dir="${src.dir}/scalap"/> </uptodate> @@ -1283,6 +1407,7 @@ DOCUMENTATION classpathref="pack.classpath"> <src> <files includes="${src.dir}/actors"/> + <files includes="${src.dir}/actors"/> <!-- why twice ?? --> <files includes="${src.dir}/library/scala"/> <files includes="${src.dir}/swing"/> </src> @@ -1390,6 +1515,7 @@ BOOTRAPING TEST AND TEST SUITE <exclude name="**/*.properties"/> <exclude name="bin/**"/> <exclude name="*.complete"/> + <exclude name="plugins/*.jar"/> </same> </target> @@ -1439,7 +1565,24 @@ BOOTRAPING TEST AND TEST SUITE </partest> </target> - <target name="test.done" depends="test.suite, test.stability"/> + <target name="test.continuations.suite" depends="pack.done"> + <property name="partest.srcdir" value="files" /> + <partest showlog="yes" erroronfailed="yes" javacmd="${java.home}/bin/java" + timeout="2400000" javaccmd="${javac.cmd}" + srcdir="${partest.srcdir}" + scalacopts="${scalac.args.optimise} -Xplugin:${build-pack.dir}/plugins/continuations.jar -Xplugin-require:continuations -P:continuations:enable"> + <compilationpath> + <path refid="pack.classpath"/> + <fileset dir="${partest.dir}/files/lib" includes="*.jar" /> + </compilationpath> + <negtests dir="${partest.dir}/${partest.srcdir}/continuations-neg" includes="*.scala"/> + <runtests dir="${partest.dir}/${partest.srcdir}"> + <include name="continuations-run/**/*.scala"/> + </runtests> + </partest> + </target> + + <target name="test.done" depends="test.suite, test.continuations.suite, test.stability"/> <!-- =========================================================================== DISTRIBUTION @@ -1467,6 +1610,10 @@ DISTRIBUTION <copy toDir="${dist.dir}/etc"> <fileset dir="${build-pack.dir}/etc"/> </copy> + <mkdir dir="${dist.dir}/plugins"/> + <copy toDir="${dist.dir}/plugins"> + <fileset dir="${build-pack.dir}/plugins"/> + </copy> </target> <target name="dist.doc" depends="dist.base"> diff --git a/src/continuations/library/scala/util/continuations/package.scala b/src/continuations/library/scala/util/continuations/package.scala new file mode 100644 index 0000000000..0d924565c1 --- /dev/null +++ b/src/continuations/library/scala/util/continuations/package.scala @@ -0,0 +1,65 @@ +// $Id$ + + +// TODO: scaladoc + +package scala.util + +package object continuations { + + type cps[A] = cpsParam[A,A] + + type suspendable = cps[Unit] + + + def shift[A,B,C](fun: (A => B) => C): A @cpsParam[B,C] = { + throw new NoSuchMethodException("this code has to be compiled with the Scala continuations plugin enabled") + } + + def reset[A,C](ctx: =>(A @cpsParam[A,C])): C = { + val ctxR = reify[A,A,C](ctx) + if (ctxR.isTrivial) + ctxR.getTrivialValue.asInstanceOf[C] + else + ctxR.foreach((x:A) => x) + } + + def reset0[A](ctx: =>(A @cpsParam[A,A])): A = reset(ctx) + + def run[A](ctx: =>(Any @cpsParam[Unit,A])): A = { + val ctxR = reify[Any,Unit,A](ctx) + if (ctxR.isTrivial) + ctxR.getTrivialValue.asInstanceOf[A] + else + ctxR.foreach((x:Any) => ()) + } + + + // methods below are primarily implementation details and are not + // needed frequently in client code + + def shiftUnit0[A,B](x: A): A @cpsParam[B,B] = { + shiftUnit[A,B,B](x) + } + + def shiftUnit[A,B,C>:B](x: A): A @cpsParam[B,C] = { + throw new NoSuchMethodException("this code has to be compiled with the Scala continuations plugin enabled") + } + + def reify[A,B,C](ctx: =>(A @cpsParam[B,C])): ControlContext[A,B,C] = { + throw new NoSuchMethodException("this code has to be compiled with the Scala continuations plugin enabled") + } + + def shiftUnitR[A,B](x: A): ControlContext[A,B,B] = { + new ControlContext(null, x) + } + + def shiftR[A,B,C](fun: (A => B) => C): ControlContext[A,B,C] = { + new ControlContext(fun, null.asInstanceOf[A]) + } + + def reifyR[A,B,C](ctx: => ControlContext[A,B,C]): ControlContext[A,B,C] = { + ctx + } + +} diff --git a/src/continuations/plugin/scala/tools/selectivecps/CPSAnnotationChecker.scala b/src/continuations/plugin/scala/tools/selectivecps/CPSAnnotationChecker.scala new file mode 100644 index 0000000000..dc7c297b92 --- /dev/null +++ b/src/continuations/plugin/scala/tools/selectivecps/CPSAnnotationChecker.scala @@ -0,0 +1,448 @@ +// $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 + + /** + * 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 + +/* + hack no longer needed since introduction of adaptBoundsToAnnotations! + + // special treatment of type parameter bounds + if ((tpe2.typeSymbol eq AnyClass)) { + // This is an ugly hack to allow instantiating Functions with an @cps + // return type. A better way would be to make sure everything goes through adapt, + // but that's a bit of work. Alternatively, an explicit hook could be added in + // Inferencer.checkBounds + val w = new StringWriter() + new Exception().printStackTrace(new PrintWriter(w, true)) + if (w.toString.contains("scala.tools.nsc.typechecker.Infer$Inferencer.checkBounds")) { + vprintln("Testing whether " + tpe1 + " <:< " + tpe2 + ". We're inside Inferencer.checkBounds, so we just return true.") + 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 + if ((mode & global.analyzer.TYPEmode) != 0 && (mode & global.analyzer.BYVALmode) != 0) { + //println("can adapt pattern annotations? " + tree + " / " + tree.tpe + " / " + Integer.toHexString(mode) + " / " + pt) + if (!annots1.isEmpty) { + return true + } + } +*/ + + if ((mode & global.analyzer.EXPRmode) == 0) { + vprintln("only handling EXPRmode") + return false + } +/* + 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 = true//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)) { + val adapt = AnnotationInfo(MarkerCPSAdaptMinus.tpe, Nil, Nil) + if (!tree.tpe.annotations.contains(adapt)) { + vprintln("yes we can!! (byval)") + return true + } + } + } + false + } + + + override def adaptAnnotations(tree: Tree, mode: Int, pt: Type): Tree = { + if (!cpsEnabled) return tree + + // FIXME: there seems to be a problem when mode == 1 (expr, no poly) and + // there are wildcards inside an annotation (which we don't resolve yet) + // can we just instantiate things? <--- need to check this is still valid + + 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 = true//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) + + // if (tree.tpe <:< removeAllCPSAnnotations(pt)) { + + //val known = global.analyzer.isFullyDefined(pt) + + if (same && !tree.tpe.annotations.contains(adapt)) { + if (true /*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 { + // 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(_, sym, List(elemtp)) if sym == ByNameParamClass => + 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.error("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) return tpe + +// if (tree.tpe.hasAnnotation(MarkerCPSAdaptPlus)) +// println("addAnnotation " + tree + "/" + tpe) + + tree match { + + case Apply(fun @ Select(qual, name), args) if (fun.tpe ne null) && !fun.tpe.isErroneous => + + // 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(fun, args) if (fun.tpe ne null) && !fun.tpe.isErroneous => + + 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) => + + vprintln("[checker] checking select " + tree + "/" + tpe) + + // FIXME: put it back in?? (problem with test cases select.scala and Test2.scala) + // transChildrenInOrder(tree, tpe, List(qual)) + 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 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 + } + + + } + } +} diff --git a/src/continuations/plugin/scala/tools/selectivecps/CPSUtils.scala b/src/continuations/plugin/scala/tools/selectivecps/CPSUtils.scala new file mode 100644 index 0000000000..3e1f05e731 --- /dev/null +++ b/src/continuations/plugin/scala/tools/selectivecps/CPSUtils.scala @@ -0,0 +1,131 @@ +// $Id$ + +package scala.tools.selectivecps + +import scala.tools.nsc.Global + +trait CPSUtils { + val global: Global + import global._ + import definitions._ + + var cpsEnabled = false + val verbose: Boolean = System.getProperty("cpsVerbose", "false") == "true" + def vprintln(x: =>Any): Unit = if (verbose) println(x) + + + lazy val MarkerCPSSym = definitions.getClass("scala.util.continuations.cpsSym") + lazy val MarkerCPSTypes = definitions.getClass("scala.util.continuations.cpsParam") + lazy val MarkerCPSSynth = definitions.getClass("scala.util.continuations.cpsSynth") + + lazy val MarkerCPSAdaptPlus = definitions.getClass("scala.util.continuations.cpsPlus") + lazy val MarkerCPSAdaptMinus = definitions.getClass("scala.util.continuations.cpsMinus") + + + lazy val Context = definitions.getClass("scala.util.continuations.ControlContext") + + lazy val ModCPS = definitions.getModule("scala.util.continuations") + lazy val MethShiftUnit = definitions.getMember(ModCPS, "shiftUnit") + lazy val MethShiftUnitR = definitions.getMember(ModCPS, "shiftUnitR") + lazy val MethShift = definitions.getMember(ModCPS, "shift") + lazy val MethShiftR = definitions.getMember(ModCPS, "shiftR") + lazy val MethReify = definitions.getMember(ModCPS, "reify") + lazy val MethReifyR = definitions.getMember(ModCPS, "reifyR") + + + lazy val allCPSAnnotations = List(MarkerCPSSym, MarkerCPSTypes, MarkerCPSSynth, + MarkerCPSAdaptPlus, MarkerCPSAdaptMinus) + + // annotation checker + + def filterAttribs(tpe:Type, cls:Symbol) = + tpe.annotations.filter(_.atp.typeSymbol == cls) + + def removeAttribs(tpe:Type, cls:Symbol*) = + tpe.withoutAnnotations.withAnnotations(tpe.annotations.filterNot(cls contains _.atp.typeSymbol)) + + def removeAllCPSAnnotations(tpe: Type) = removeAttribs(tpe, allCPSAnnotations:_*) + + def linearize(ann: List[AnnotationInfo]): AnnotationInfo = { + ann.reduceLeft { (a, b) => + val atp0::atp1::Nil = a.atp.normalize.typeArgs + val btp0::btp1::Nil = b.atp.normalize.typeArgs + val (u0,v0) = (atp0, atp1) + val (u1,v1) = (btp0, btp1) +/* + val (u0,v0) = (a.atp.typeArgs(0), a.atp.typeArgs(1)) + val (u1,v1) = (b.atp.typeArgs(0), b.atp.typeArgs(1)) + vprintln("check lin " + a + " andThen " + b) +*/ + vprintln("check lin " + a + " andThen " + b) + if (!(v1 <:< u0)) + throw new TypeError("illegal answer type modification: " + a + " andThen " + b) + // TODO: improve error message (but it is not very common) + AnnotationInfo(appliedType(MarkerCPSTypes.tpe, List(u1,v0)),Nil,Nil) + } + } + + // anf transform + + def getExternalAnswerTypeAnn(tp: Type) = { + tp.annotations.find(a => a.atp.typeSymbol == MarkerCPSTypes) match { + case Some(AnnotationInfo(atp, _, _)) => + val atp0::atp1::Nil = atp.normalize.typeArgs + Some((atp0, atp1)) + case None => + if (tp.hasAnnotation(MarkerCPSAdaptPlus)) + global.warning("trying to instantiate type " + tp + " to unknown cps type") + None + } + } + + def getAnswerTypeAnn(tp: Type) = { + tp.annotations.find(a => a.atp.typeSymbol == MarkerCPSTypes) match { + case Some(AnnotationInfo(atp, _, _)) => + if (!tp.hasAnnotation(MarkerCPSAdaptPlus)) {//&& !tp.hasAnnotation(MarkerCPSAdaptMinus)) + val atp0::atp1::Nil = atp.normalize.typeArgs + Some((atp0, atp1)) + } else + None + case None => None + } + } + + def hasAnswerTypeAnn(tp: Type) = { + tp.hasAnnotation(MarkerCPSTypes) && !tp.hasAnnotation(MarkerCPSAdaptPlus) /*&& + !tp.hasAnnotation(MarkerCPSAdaptMinus)*/ + } + + def hasSynthAnn(tp: Type) = { + tp.annotations.exists(a => a.atp.typeSymbol == MarkerCPSSynth) + } + + def updateSynthFlag(tree: Tree) = { // remove annotations if *we* added them (@synth present) + if (hasSynthAnn(tree.tpe)) { + log("removing annotation from " + tree) + tree.setType(removeAllCPSAnnotations(tree.tpe)) + } else + tree + } + + type CPSInfo = Option[(Type,Type)] + + def linearize(a: CPSInfo, b: CPSInfo)(implicit unit: CompilationUnit, pos: Position): CPSInfo = { + (a,b) match { + case (Some((u0,v0)), Some((u1,v1))) => + vprintln("check lin " + a + " andThen " + b) + if (!(v1 <:< u0)) { + unit.error(pos,"cannot change answer type in composition of cps expressions " + + "from " + u1 + " to " + v0 + " because " + v1 + " is not a subtype of " + u0 + ".") + throw new Exception("check lin " + a + " andThen " + b) + } + Some((u1,v0)) + case (Some(_), _) => a + case (_, Some(_)) => b + case _ => None + } + } + + // cps transform + +}
\ No newline at end of file diff --git a/src/continuations/plugin/scala/tools/selectivecps/SelectiveANFTransform.scala b/src/continuations/plugin/scala/tools/selectivecps/SelectiveANFTransform.scala new file mode 100644 index 0000000000..2b6ce67ef4 --- /dev/null +++ b/src/continuations/plugin/scala/tools/selectivecps/SelectiveANFTransform.scala @@ -0,0 +1,389 @@ +// $Id$ + +package scala.tools.selectivecps + +import scala.tools.nsc._ +import scala.tools.nsc.transform._ +import scala.tools.nsc.symtab._ +import scala.tools.nsc.plugins._ + +import scala.tools.nsc.ast._ + +/** + * In methods marked @cps, explicitly name results of calls to other @cps methods + */ +abstract class SelectiveANFTransform extends PluginComponent with Transform with + TypingTransformers with CPSUtils { + // inherits abstract value `global' and class `Phase' from Transform + + import global._ // the global environment + import definitions._ // standard classes and methods + import typer.atOwner // methods to type trees + + /** the following two members override abstract members in Transform */ + val phaseName: String = "selectiveanf" + + protected def newTransformer(unit: CompilationUnit): Transformer = + new ANFTransformer(unit) + + + class ANFTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + implicit val _unit = unit // allow code in CPSUtils.scala to report errors + var cpsAllowed: Boolean = false // detect cps code in places we do not handle (yet) + + override def transform(tree: Tree): Tree = { + if (!cpsEnabled) return tree + + tree match { + + // TODO: Maybe we should further generalize the transform and move it over + // to the regular Transformer facility. But then, actual and required cps + // state would need more complicated (stateful!) tracking. + + // Making the default case use transExpr(tree, None, None) instead of + // calling super.transform() would be a start, but at the moment, + // this would cause infinite recursion. But we could remove the + // ValDef case here. + + case dd @ DefDef(mods, name, tparams, vparamss, tpt, rhs) => + log("transforming " + dd.symbol) + + atOwner(dd.symbol) { + val rhs1 = transExpr(rhs, None, getExternalAnswerTypeAnn(tpt.tpe)) + + log("result "+rhs1) + log("result is of type "+rhs1.tpe) + + treeCopy.DefDef(dd, mods, name, transformTypeDefs(tparams), transformValDefss(vparamss), + transform(tpt), rhs1) + } + + case ff @ Function(vparams, body) => + log("transforming anon function " + ff.symbol) + + atOwner(ff.symbol) { + val body1 = transExpr(body, None, getExternalAnswerTypeAnn(body.tpe)) + + log("result "+body1) + log("result is of type "+body1.tpe) + + treeCopy.Function(ff, transformValDefs(vparams), body1) + } + + case vd @ ValDef(mods, name, tpt, rhs) => // object-level valdefs + log("transforming valdef " + vd.symbol) + + atOwner(vd.symbol) { + + assert(getExternalAnswerTypeAnn(tpt.tpe) == None) + + val rhs1 = transExpr(rhs, None, None) + + treeCopy.ValDef(vd, mods, name, transform(tpt), rhs1) + } + + case TypeTree() => + // circumvent cpsAllowed here + super.transform(tree) + + case Apply(_,_) => + // this allows reset { ... } in object constructors + // it's kind of a hack to put it here (see note above) + transExpr(tree, None, None) + + case _ => + + if (hasAnswerTypeAnn(tree.tpe)) { + if (!cpsAllowed) + unit.error(tree.pos, "cps code not allowed here / " + tree.getClass + " / " + tree) + + log(tree) + } + + cpsAllowed = false + super.transform(tree) + } + } + + + def transExpr(tree: Tree, cpsA: CPSInfo, cpsR: CPSInfo): Tree = { + transTailValue(tree, cpsA, cpsR) match { + case (Nil, b) => b + case (a, b) => + treeCopy.Block(tree, a,b) + } + } + + + def transArgList(fun: Tree, args: List[Tree], cpsA: CPSInfo): (List[List[Tree]], List[Tree], CPSInfo) = { + val formals = fun.tpe.paramTypes + val overshoot = args.length - formals.length + + var spc: CPSInfo = cpsA + + val (stm,expr) = (for ((a,tp) <- args.zip(formals ::: List.fill(overshoot)(NoType))) yield { + tp match { + case TypeRef(_, sym, List(elemtp)) if sym == ByNameParamClass => + (Nil, transExpr(a, None, getAnswerTypeAnn(elemtp))) + case _ => + val (valStm, valExpr, valSpc) = transInlineValue(a, spc) + spc = valSpc + (valStm, valExpr) + } + }).unzip + + (stm,expr,spc) + } + + + def transValue(tree: Tree, cpsA: CPSInfo, cpsR: CPSInfo): (List[Tree], Tree, CPSInfo) = { + // return value: (stms, expr, spc), where spc is CPSInfo after stms but *before* expr + implicit val pos = tree.pos + tree match { + case Block(stms, expr) => + val (cpsA2, cpsR2) = (cpsA, linearize(cpsA, getAnswerTypeAnn(tree.tpe))) // tbd +// val (cpsA2, cpsR2) = (None, getAnswerTypeAnn(tree.tpe)) + val (a, b) = transBlock(stms, expr, cpsA2, cpsR2) + + val tree1 = (treeCopy.Block(tree, a, b)) // no updateSynthFlag here!!! + + (Nil, tree1, cpsA) + + case If(cond, thenp, elsep) => + + val (condStats, condVal, spc) = transInlineValue(cond, cpsA) + + val (cpsA2, cpsR2) = (spc, linearize(spc, getAnswerTypeAnn(tree.tpe))) +// val (cpsA2, cpsR2) = (None, getAnswerTypeAnn(tree.tpe)) + val thenVal = transExpr(thenp, cpsA2, cpsR2) + val elseVal = transExpr(elsep, cpsA2, cpsR2) + + // check that then and else parts agree (not necessary any more, but left as sanity check) + if (cpsR.isDefined) { + if (elsep == EmptyTree) + unit.error(tree.pos, "always need else part in cps code") + } + if (hasAnswerTypeAnn(thenVal.tpe) != hasAnswerTypeAnn(elseVal.tpe)) { + unit.error(tree.pos, "then and else parts must both be cps code or neither of them") + } + + (condStats, updateSynthFlag(treeCopy.If(tree, condVal, thenVal, elseVal)), spc) + + case Match(selector, cases) => + + val (selStats, selVal, spc) = transInlineValue(selector, cpsA) + val (cpsA2, cpsR2) = (spc, linearize(spc, getAnswerTypeAnn(tree.tpe))) +// val (cpsA2, cpsR2) = (None, getAnswerTypeAnn(tree.tpe)) + + val caseVals = for { + cd @ CaseDef(pat, guard, body) <- cases + val bodyVal = transExpr(body, cpsA2, cpsR2) + } yield { + treeCopy.CaseDef(cd, transform(pat), transform(guard), bodyVal) + } + + (selStats, updateSynthFlag(treeCopy.Match(tree, selVal, caseVals)), spc) + + + case ldef @ LabelDef(name, params, rhs) => + if (hasAnswerTypeAnn(tree.tpe)) { + val sym = currentOwner.newMethod(tree.pos, name)//unit.fresh.newName(tree.pos, "myloopvar")) + .setInfo(ldef.symbol.info) + .setFlag(Flags.SYNTHETIC) + + val subst = new TreeSymSubstituter(List(ldef.symbol), List(sym)) + val rhsVal = transExpr(subst(rhs), None, getAnswerTypeAnn(tree.tpe)) + + val stm1 = localTyper.typed(DefDef(sym, rhsVal)) + val expr = localTyper.typed(Apply(Ident(sym), List())) + + (List(stm1), expr, cpsA) + } else { + val rhsVal = transExpr(rhs, None, None) + (Nil, updateSynthFlag(treeCopy.LabelDef(tree, name, params, rhsVal)), cpsA) + } + + + case Try(block, catches, finalizer) => + // no cps code allowed in try/catch/finally! + val blockVal = transExpr(block, None, None) + + val catchVals = for { + cd @ CaseDef(pat, guard, body) <- catches + val bodyVal = transExpr(body, None, None) + } yield { + treeCopy.CaseDef(cd, transform(pat), transform(guard), bodyVal) + } + + val finallyVal = transExpr(finalizer, None, None) + + (Nil, updateSynthFlag(treeCopy.Try(tree, blockVal, catchVals, finallyVal)), cpsA) + + case Assign(lhs, rhs) => + // allow cps code in rhs only + val (stms, expr, spc) = transInlineValue(rhs, cpsA) + (stms, updateSynthFlag(treeCopy.Assign(tree, transform(lhs), expr)), spc) + + case Return(expr0) => + val (stms, expr, spc) = transInlineValue(expr0, cpsA) + (stms, updateSynthFlag(treeCopy.Return(tree, expr)), spc) + + case Throw(expr0) => + val (stms, expr, spc) = transInlineValue(expr0, cpsA) + (stms, updateSynthFlag(treeCopy.Throw(tree, expr)), spc) + + case Typed(expr0, tpt) => + // TODO: should x: A @cps[B,C] have a special meaning? + val (stms, expr, spc) = transInlineValue(expr0, cpsA) + val tpt1 = treeCopy.TypeTree(tpt).setType(removeAllCPSAnnotations(tpt.tpe)) + (stms, treeCopy.Typed(tree, expr, tpt1).setType(removeAllCPSAnnotations(tree.tpe)), spc) + + case TypeApply(fun, args) => + val (stms, expr, spc) = transInlineValue(fun, cpsA) + (stms, updateSynthFlag(treeCopy.TypeApply(tree, expr, args)), spc) + + case Select(qual, name) => + val (stms, expr, spc) = transInlineValue(qual, cpsA) + (stms, updateSynthFlag(treeCopy.Select(tree, expr, name)), spc) + + case Apply(fun, args) => + val (funStm, funExpr, funSpc) = transInlineValue(fun, cpsA) + val (argStm, argExpr, argSpc) = transArgList(fun, args, funSpc) + + (funStm ::: (argStm.flatten), updateSynthFlag(treeCopy.Apply(tree, funExpr, argExpr)), + argSpc) + + case _ => + cpsAllowed = true + (Nil, transform(tree), cpsA) + } + } + + def transTailValue(tree: Tree, cpsA: CPSInfo, cpsR: CPSInfo): (List[Tree], Tree) = { + + val (stms, expr, spc) = transValue(tree, cpsA, cpsR) + + val bot = linearize(spc, getAnswerTypeAnn(expr.tpe))(unit, tree.pos) + + val plainTpe = removeAllCPSAnnotations(expr.tpe) + + if (cpsR.isDefined && !bot.isDefined) { + + if (!expr.isEmpty && (expr.tpe.typeSymbol ne NothingClass)) { + // must convert! + log("cps type conversion (has: " + cpsA + "/" + spc + "/" + expr.tpe + ")") + log("cps type conversion (expected: " + cpsR.get + "): " + expr) + + if (!expr.tpe.hasAnnotation(MarkerCPSAdaptPlus)) + unit.warning(tree.pos, "expression " + tree + " is cps-transformed unexpectedly") + + try { + val Some((a, b)) = cpsR + + val res = localTyper.typed(atPos(tree.pos) { + Apply(TypeApply(gen.mkAttributedRef(MethShiftUnit), + List(TypeTree(plainTpe), TypeTree(a), TypeTree(b))), + List(expr)) + }) + return (stms, res) + + } catch { + case ex:TypeError => + unit.error(ex.pos, "cannot cps-transform expression " + tree + ": " + ex.msg) + } + } + + } else if (!cpsR.isDefined && bot.isDefined) { + // error! + log("cps type error: " + expr) + println("cps type error: " + expr + "/" + expr.tpe + "/" + getAnswerTypeAnn(expr.tpe)) + + println(cpsR + "/" + spc + "/" + bot) + + unit.error(tree.pos, "found cps expression in non-cps position") + } else { + // all is well + + if (expr.tpe.hasAnnotation(MarkerCPSAdaptPlus)) { + unit.warning(tree.pos, "expression " + expr + "/" + expr.tpe + " should not have cps-plus annotation") + expr.setType(removeAllCPSAnnotations(expr.tpe)) + } + + // TODO: sanity check that types agree + } + + (stms, expr) + } + + def transInlineValue(tree: Tree, cpsA: CPSInfo): (List[Tree], Tree, CPSInfo) = { + + val (stms, expr, spc) = transValue(tree, cpsA, None) // never required to be cps + + getAnswerTypeAnn(expr.tpe) match { + case spcVal @ Some(_) => + + val valueTpe = removeAllCPSAnnotations(expr.tpe) + + val sym = currentOwner.newValue(tree.pos, unit.fresh.newName(tree.pos, "tmp")) + .setInfo(valueTpe) + .setFlag(Flags.SYNTHETIC) + .setAnnotations(List(AnnotationInfo(MarkerCPSSym.tpe, Nil, Nil))) + + (stms ::: List(ValDef(sym, expr) setType(NoType)), + Ident(sym) setType(valueTpe) setPos(tree.pos), linearize(spc, spcVal)(unit, tree.pos)) + + case _ => + (stms, expr, spc) + } + + } + + + + def transInlineStm(stm: Tree, cpsA: CPSInfo): (List[Tree], CPSInfo) = { + stm match { + + // TODO: what about DefDefs? + // TODO: relation to top-level val def? + // TODO: what about lazy vals? + + case tree @ ValDef(mods, name, tpt, rhs) => + val (stms, anfRhs, spc) = atOwner(tree.symbol) { transValue(rhs, cpsA, None) } + + val tv = new ChangeOwnerTraverser(tree.symbol, currentOwner) + stms.foreach(tv.traverse(_)) + + // TODO: symbol might already have annotation. Should check conformance + // TODO: better yet: do without annotations on symbols + + val spcVal = getAnswerTypeAnn(anfRhs.tpe) + if (spcVal.isDefined) { + tree.symbol.setAnnotations(List(AnnotationInfo(MarkerCPSSym.tpe, Nil, Nil))) + } + + (stms:::List(treeCopy.ValDef(tree, mods, name, tpt, anfRhs)), linearize(spc, spcVal)(unit, tree.pos)) + + case _ => + val (headStms, headExpr, headSpc) = transInlineValue(stm, cpsA) + val valSpc = getAnswerTypeAnn(headExpr.tpe) + (headStms:::List(headExpr), linearize(headSpc, valSpc)(unit, stm.pos)) + } + } + + def transBlock(stms: List[Tree], expr: Tree, cpsA: CPSInfo, cpsR: CPSInfo): (List[Tree], Tree) = { + stms match { + case Nil => + transTailValue(expr, cpsA, cpsR) + + case stm::rest => + var (rest2, expr2) = (rest, expr) + val (headStms, headSpc) = transInlineStm(stm, cpsA) + val (restStms, restExpr) = transBlock(rest2, expr2, headSpc, cpsR) + (headStms:::restStms, restExpr) + } + } + + + } +} diff --git a/src/continuations/plugin/scala/tools/selectivecps/SelectiveCPSPlugin.scala b/src/continuations/plugin/scala/tools/selectivecps/SelectiveCPSPlugin.scala new file mode 100644 index 0000000000..7c56a78491 --- /dev/null +++ b/src/continuations/plugin/scala/tools/selectivecps/SelectiveCPSPlugin.scala @@ -0,0 +1,61 @@ +// $Id$ + +package scala.tools.selectivecps + +import scala.tools.nsc +import scala.tools.nsc.typechecker._ +import nsc.Global +import nsc.Phase +import nsc.plugins.Plugin +import nsc.plugins.PluginComponent + +class SelectiveCPSPlugin(val global: Global) extends Plugin { + import global._ + + val name = "continuations" + val description = "applies selective cps conversion" + + val anfPhase = new SelectiveANFTransform() { + val global = SelectiveCPSPlugin.this.global + val runsAfter = List("pickler") + } + + val cpsPhase = new SelectiveCPSTransform() { + val global = SelectiveCPSPlugin.this.global + val runsAfter = List("selectiveanf") + } + + + val components = List[PluginComponent](anfPhase, cpsPhase) + + val checker = new CPSAnnotationChecker { + val global: SelectiveCPSPlugin.this.global.type = SelectiveCPSPlugin.this.global + } + global.addAnnotationChecker(checker.checker) + + + def setEnabled(flag: Boolean) = { + checker.cpsEnabled = flag + anfPhase.cpsEnabled = flag + cpsPhase.cpsEnabled = flag + } + + // TODO: require -enabled command-line flag + + override def processOptions(options: List[String], error: String => Unit) = { + var enabled = false + for (option <- options) { + if (option == "enable") { + enabled = true + } else { + error("Option not understood: "+option) + } + } + setEnabled(enabled) + } + + override val optionsHelp: Option[String] = + Some(" -P:continuations:enable Enable continuations") +// " -sourcepath <path> Specify where to find input source files" + +} diff --git a/src/continuations/plugin/scala/tools/selectivecps/SelectiveCPSTransform.scala b/src/continuations/plugin/scala/tools/selectivecps/SelectiveCPSTransform.scala new file mode 100644 index 0000000000..b906c12568 --- /dev/null +++ b/src/continuations/plugin/scala/tools/selectivecps/SelectiveCPSTransform.scala @@ -0,0 +1,250 @@ +// $Id$ + +package scala.tools.selectivecps + +import scala.collection._ + +import scala.tools.nsc._ +import scala.tools.nsc.transform._ +import scala.tools.nsc.plugins._ + +import scala.tools.nsc.ast.TreeBrowsers +import scala.tools.nsc.ast._ + +/** + * In methods marked @cps, CPS-transform assignments introduced by ANF-transform phase. + */ +abstract class SelectiveCPSTransform extends PluginComponent with + InfoTransform with TypingTransformers with CPSUtils { + // inherits abstract value `global' and class `Phase' from Transform + + import global._ // the global environment + import definitions._ // standard classes and methods + import typer.atOwner // methods to type trees + + /** the following two members override abstract members in Transform */ + val phaseName: String = "selectivecps" + + protected def newTransformer(unit: CompilationUnit): Transformer = + new CPSTransformer(unit) + + + /** - return symbol's transformed type, + */ + def transformInfo(sym: Symbol, tp: Type): Type = { + if (!cpsEnabled) return tp + + val newtp = transformCPSType(tp) + + if (newtp != tp) + log("transformInfo changed type for " + sym + " to " + newtp); + + if (sym == MethReifyR) + log("transformInfo (not)changed type for " + sym + " to " + newtp); + + newtp + } + + def transformCPSType(tp: Type): Type = { // TODO: use a TypeMap? need to handle more cases? + tp match { + case PolyType(params,res) => PolyType(params, transformCPSType(res)) + case MethodType(params,res) => + MethodType(params, transformCPSType(res)) + case TypeRef(pre, sym, args) => TypeRef(pre, sym, args.map(transformCPSType(_))) + case _ => + getExternalAnswerTypeAnn(tp) match { + case Some((res, outer)) => + appliedType(Context.tpe, List(removeAllCPSAnnotations(tp), res, outer)) + case _ => + removeAllCPSAnnotations(tp) + } + } + } + + + class CPSTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + override def transform(tree: Tree): Tree = { + if (!cpsEnabled) return tree + postTransform(mainTransform(tree)) + } + + def postTransform(tree: Tree): Tree = { + tree.setType(transformCPSType(tree.tpe)) + } + + + def mainTransform(tree: Tree): Tree = { + tree match { + + // TODO: can we generalize this? + + case Apply(TypeApply(fun, targs), args) + if (fun.symbol == MethShift) => + log("found shift: " + tree) + atPos(tree.pos) { + val funR = gen.mkAttributedRef(MethShiftR) // TODO: correct? + log(funR.tpe) + Apply( + TypeApply(funR, targs).setType(appliedType(funR.tpe, targs.map((t:Tree) => t.tpe))), + args.map(transform(_)) + ).setType(transformCPSType(tree.tpe)) + } + + case Apply(TypeApply(fun, targs), args) + if (fun.symbol == MethShiftUnit) => + log("found shiftUnit: " + tree) + atPos(tree.pos) { + val funR = gen.mkAttributedRef(MethShiftUnitR) // TODO: correct? + log(funR.tpe) + Apply( + TypeApply(funR, List(targs(0), targs(1))).setType(appliedType(funR.tpe, + List(targs(0).tpe, targs(1).tpe))), + args.map(transform(_)) + ).setType(appliedType(Context.tpe, List(targs(0).tpe,targs(1).tpe,targs(1).tpe))) + } + + case Apply(TypeApply(fun, targs), args) + if (fun.symbol == MethReify) => + log("found reify: " + tree) + atPos(tree.pos) { + val funR = gen.mkAttributedRef(MethReifyR) // TODO: correct? + log(funR.tpe) + Apply( + TypeApply(funR, targs).setType(appliedType(funR.tpe, targs.map((t:Tree) => t.tpe))), + args.map(transform(_)) + ).setType(transformCPSType(tree.tpe)) + } + + case Block(stms, expr) => + + val (stms1, expr1) = transBlock(stms, expr) + treeCopy.Block(tree, stms1, expr1) + + case _ => + super.transform(tree) + } + } + + + + def transBlock(stms: List[Tree], expr: Tree): (List[Tree], Tree) = { + + stms match { + case Nil => + (Nil, transform(expr)) + + case stm::rest => + + stm match { + case vd @ ValDef(mods, name, tpt, rhs) + if (vd.symbol.hasAnnotation(MarkerCPSSym)) => + + log("found marked ValDef "+name+" of type " + vd.symbol.tpe) + + val tpe = vd.symbol.tpe + val rhs1 = transform(rhs) + + log("valdef symbol " + vd.symbol + " has type " + tpe) + log("right hand side " + rhs1 + " has type " + rhs1.tpe) + + log("currentOwner: " + currentOwner) + log("currentMethod: " + currentMethod) + + + val (bodyStms, bodyExpr) = transBlock(rest, expr) + + val specialCaseTrivial = bodyExpr match { + case Apply(fun, args) => + // for now, look for explicit tail calls only. + // are there other cases that could profit from specializing on + // trivial contexts as well? + (bodyExpr.tpe.typeSymbol == Context) && (currentMethod == fun.symbol) + case _ => false + } + + def applyTrivial(ctxValSym: Symbol, body: Tree) = { + + new TreeSymSubstituter(List(vd.symbol), List(ctxValSym)).traverse(body) + + val body2 = localTyper.typed(atPos(vd.symbol.pos) { body }) + + if ((body2.tpe == null) || !(body2.tpe.typeSymbol.tpe <:< Context.tpe)) { + println(body2 + "/" + body2.tpe) + unit.error(rhs.pos, "cannot compute type for CPS-transformed function result") + } + body2 + } + + def applyCombinatorFun(ctxR: Tree, body: Tree) = { + val arg = currentOwner.newValueParameter(ctxR.pos, name).setInfo(tpe) + new TreeSymSubstituter(List(vd.symbol), List(arg)).traverse(body) + val fun = localTyper.typed(atPos(vd.symbol.pos) { Function(List(ValDef(arg)), body) }) // types body as well + arg.owner = fun.symbol + new ChangeOwnerTraverser(currentOwner, fun.symbol).traverse(body) + + log("fun.symbol: "+fun.symbol) + log("fun.symbol.owner: "+fun.symbol.owner) + log("arg.owner: "+arg.owner) + + log("fun.tpe:"+fun.tpe) + log("return type of fun:"+body.tpe) + + var methodName = "map" + + if (body.tpe != null) { + if (body.tpe.typeSymbol.tpe <:< Context.tpe) + methodName = "flatMap" + } + else + unit.error(rhs.pos, "cannot compute type for CPS-transformed function result") + + log("will use method:"+methodName) + + localTyper.typed(atPos(vd.symbol.pos) { + Apply(Select(ctxR, ctxR.tpe.member(methodName)), List(fun)) + }) + } + + try { + if (specialCaseTrivial) { + log("will optimize possible tail call: " + bodyExpr) + // val ctx = <rhs> + // if (ctx.isTrivial) + // val <lhs> = ctx.getTrivialValue; ... + // else + // ctx.flatMap { <lhs> => ... } + val ctxSym = currentOwner.newValue(vd.symbol.name + "$shift").setInfo(rhs1.tpe) + val ctxDef = localTyper.typed(ValDef(ctxSym, rhs1)) + def ctxRef = localTyper.typed(Ident(ctxSym)) + val argSym = currentOwner.newValue(vd.symbol.name).setInfo(tpe) + val argDef = localTyper.typed(ValDef(argSym, Select(ctxRef, ctxRef.tpe.member("getTrivialValue")))) + val switchExpr = localTyper.typed(atPos(vd.symbol.pos) { + val body2 = duplicateTree(Block(bodyStms, bodyExpr)) // dup before typing! + If(Select(ctxRef, ctxSym.tpe.member("isTrivial")), + applyTrivial(argSym, Block(argDef::bodyStms, bodyExpr)), + applyCombinatorFun(ctxRef, body2)) + }) + (List(ctxDef), switchExpr) + } else { + // ctx.flatMap { <lhs> => ... } + // or + // ctx.map { <lhs> => ... } + (Nil, applyCombinatorFun(rhs1, Block(bodyStms, bodyExpr))) + } + } catch { + case ex:TypeError => + unit.error(ex.pos, ex.msg) + (bodyStms, bodyExpr) + } + + case _ => + val (a, b) = transBlock(rest, expr) + (transform(stm)::a, b) + } + } + } + + + } +} diff --git a/src/continuations/plugin/scalac-plugin.xml b/src/continuations/plugin/scalac-plugin.xml new file mode 100644 index 0000000000..04d42655c5 --- /dev/null +++ b/src/continuations/plugin/scalac-plugin.xml @@ -0,0 +1,5 @@ +<!-- $Id$ --> +<plugin> + <name>continuations</name> + <classname>scala.tools.selectivecps.SelectiveCPSPlugin</classname> +</plugin> diff --git a/test/files/continuations-neg/function0.check b/test/files/continuations-neg/function0.check new file mode 100644 index 0000000000..0a66763a0f --- /dev/null +++ b/test/files/continuations-neg/function0.check @@ -0,0 +1,6 @@ +function0.scala:11: error: type mismatch; + found : () => Int @scala.util.continuations.cpsParam[Int,Int] + required: () => Int + val g: () => Int = f + ^ +one error found diff --git a/test/files/continuations-neg/function0.scala b/test/files/continuations-neg/function0.scala new file mode 100644 index 0000000000..4112ee3835 --- /dev/null +++ b/test/files/continuations-neg/function0.scala @@ -0,0 +1,16 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val f = () => shift { k: (Int=>Int) => k(7) } + val g: () => Int = f + + println(reset(g())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-neg/function2.check b/test/files/continuations-neg/function2.check new file mode 100644 index 0000000000..4833057652 --- /dev/null +++ b/test/files/continuations-neg/function2.check @@ -0,0 +1,6 @@ +function2.scala:11: error: type mismatch; + found : () => Int + required: () => Int @util.continuations.package.cps[Int] + val g: () => Int @cps[Int] = f + ^ +one error found diff --git a/test/files/continuations-neg/function2.scala b/test/files/continuations-neg/function2.scala new file mode 100644 index 0000000000..ae0fda509d --- /dev/null +++ b/test/files/continuations-neg/function2.scala @@ -0,0 +1,16 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val f = () => 7 + val g: () => Int @cps[Int] = f + + println(reset(g())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-neg/function3.check b/test/files/continuations-neg/function3.check new file mode 100644 index 0000000000..4705ad9ed9 --- /dev/null +++ b/test/files/continuations-neg/function3.check @@ -0,0 +1,6 @@ +function3.scala:10: error: type mismatch; + found : Int @scala.util.continuations.cpsParam[Int,Int] + required: Int + val g: () => Int = () => shift { k: (Int=>Int) => k(7) } + ^ +one error found diff --git a/test/files/continuations-neg/function3.scala b/test/files/continuations-neg/function3.scala new file mode 100644 index 0000000000..0c3f1667e5 --- /dev/null +++ b/test/files/continuations-neg/function3.scala @@ -0,0 +1,15 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val g: () => Int = () => shift { k: (Int=>Int) => k(7) } + + println(reset(g())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-neg/infer0.check b/test/files/continuations-neg/infer0.check new file mode 100644 index 0000000000..1dd072ef09 --- /dev/null +++ b/test/files/continuations-neg/infer0.check @@ -0,0 +1,4 @@ +infer0.scala:11: error: cannot cps-transform expression 8: type arguments [Int(8),String,Int] do not conform to method shiftUnit's type parameter bounds [A,B,C >: B] + test(8) + ^ +one error found diff --git a/test/files/continuations-neg/infer0.scala b/test/files/continuations-neg/infer0.scala new file mode 100644 index 0000000000..894d5228b1 --- /dev/null +++ b/test/files/continuations-neg/infer0.scala @@ -0,0 +1,14 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x: => Int @cpsParam[String,Int]) = 7 + + def main(args: Array[String]): Any = { + test(8) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-neg/infer2.check b/test/files/continuations-neg/infer2.check new file mode 100644 index 0000000000..59eb670bc3 --- /dev/null +++ b/test/files/continuations-neg/infer2.check @@ -0,0 +1,4 @@ +infer2.scala:14: error: illegal answer type modification: scala.util.continuations.cpsParam[String,Int] andThen scala.util.continuations.cpsParam[String,Int] + test { sym(); sym() } + ^ +one error found diff --git a/test/files/continuations-neg/infer2.scala b/test/files/continuations-neg/infer2.scala new file mode 100644 index 0000000000..a890ac1fc4 --- /dev/null +++ b/test/files/continuations-neg/infer2.scala @@ -0,0 +1,19 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x: => Int @cpsParam[String,Int]) = 7 + + def sym() = shift { k: (Int => String) => 9 } + + + def main(args: Array[String]): Any = { + test { sym(); sym() } + } + +} + + diff --git a/test/files/continuations-neg/t1929.check b/test/files/continuations-neg/t1929.check new file mode 100644 index 0000000000..f42c3a1e15 --- /dev/null +++ b/test/files/continuations-neg/t1929.check @@ -0,0 +1,6 @@ +t1929.scala:8: error: type mismatch; + found : Int @scala.util.continuations.cpsParam[String,java.lang.String] @scala.util.continuations.cpsSynth + required: Int @scala.util.continuations.cpsParam[Int,java.lang.String] + reset { + ^ +one error found diff --git a/test/files/continuations-neg/t1929.scala b/test/files/continuations-neg/t1929.scala new file mode 100644 index 0000000000..02eda9170d --- /dev/null +++ b/test/files/continuations-neg/t1929.scala @@ -0,0 +1,17 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + def main(args : Array[String]) { + reset { + println("up") + val x = shift((k:Int=>String) => k(8) + k(2)) + println("down " + x) + val y = shift((k:Int=>String) => k(3)) + println("down2 " + y) + y + x + } + } +}
\ No newline at end of file diff --git a/test/files/continuations-neg/t2285.check b/test/files/continuations-neg/t2285.check new file mode 100644 index 0000000000..d5dff6a4f2 --- /dev/null +++ b/test/files/continuations-neg/t2285.check @@ -0,0 +1,6 @@ +t2285.scala:9: error: type mismatch; + found : Int @scala.util.continuations.cpsParam[String,String] @scala.util.continuations.cpsSynth + required: Int @scala.util.continuations.cpsParam[Int,String] + def foo() = reset { bar(); 7 } + ^ +one error found diff --git a/test/files/continuations-neg/t2285.scala b/test/files/continuations-neg/t2285.scala new file mode 100644 index 0000000000..f3c7f4c89c --- /dev/null +++ b/test/files/continuations-neg/t2285.scala @@ -0,0 +1,11 @@ +// $Id$ + +import scala.util.continuations._ + +object Test { + + def bar() = shift { k: (String => String) => k("1") } + + def foo() = reset { bar(); 7 } + +} diff --git a/test/files/continuations-neg/t2949.check b/test/files/continuations-neg/t2949.check new file mode 100644 index 0000000000..dd9768807c --- /dev/null +++ b/test/files/continuations-neg/t2949.check @@ -0,0 +1,6 @@ +t2949.scala:13: error: type mismatch; + found : Int + required: ? @scala.util.continuations.cpsParam[List[?],Any] + x * y + ^ +one error found diff --git a/test/files/continuations-neg/t2949.scala b/test/files/continuations-neg/t2949.scala new file mode 100644 index 0000000000..ce27c7c0e8 --- /dev/null +++ b/test/files/continuations-neg/t2949.scala @@ -0,0 +1,15 @@ +// $Id$ + +import scala.util.continuations._ + +object Test { + + def reflect[A,B](xs : List[A]) = shift{ xs.flatMap[B, List[B]] } + def reify[A, B](x : A @cpsParam[List[A], B]) = reset{ List(x) } + + def main(args: Array[String]): Unit = println(reify { + val x = reflect[Int, Int](List(1,2,3)) + val y = reflect[Int, Int](List(2,4,8)) + x * y + }) +} diff --git a/test/files/continuations-run/basics.check b/test/files/continuations-run/basics.check new file mode 100755 index 0000000000..54c059fdcb --- /dev/null +++ b/test/files/continuations-run/basics.check @@ -0,0 +1,2 @@ +28 +28
\ No newline at end of file diff --git a/test/files/continuations-run/basics.scala b/test/files/continuations-run/basics.scala new file mode 100755 index 0000000000..9df209b11c --- /dev/null +++ b/test/files/continuations-run/basics.scala @@ -0,0 +1,23 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def m0() = { + shift((k:Int => Int) => k(k(7))) * 2 + } + + def m1() = { + 2 * shift((k:Int => Int) => k(k(7))) + } + + def main(args: Array[String]) = { + + println(reset(m0())) + println(reset(m1())) + + } + +} diff --git a/test/files/continuations-run/function1.check b/test/files/continuations-run/function1.check new file mode 100644 index 0000000000..7f8f011eb7 --- /dev/null +++ b/test/files/continuations-run/function1.check @@ -0,0 +1 @@ +7 diff --git a/test/files/continuations-run/function1.scala b/test/files/continuations-run/function1.scala new file mode 100644 index 0000000000..3b39722e3a --- /dev/null +++ b/test/files/continuations-run/function1.scala @@ -0,0 +1,16 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val f = () => shift { k: (Int=>Int) => k(7) } + val g: () => Int @cps[Int] = f + + println(reset(g())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/function4.check b/test/files/continuations-run/function4.check new file mode 100644 index 0000000000..c7930257df --- /dev/null +++ b/test/files/continuations-run/function4.check @@ -0,0 +1 @@ +7
\ No newline at end of file diff --git a/test/files/continuations-run/function4.scala b/test/files/continuations-run/function4.scala new file mode 100644 index 0000000000..b73eedb02c --- /dev/null +++ b/test/files/continuations-run/function4.scala @@ -0,0 +1,15 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val g: () => Int @cps[Int] = () => shift { k: (Int=>Int) => k(7) } + + println(reset(g())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/function5.check b/test/files/continuations-run/function5.check new file mode 100644 index 0000000000..c7930257df --- /dev/null +++ b/test/files/continuations-run/function5.check @@ -0,0 +1 @@ +7
\ No newline at end of file diff --git a/test/files/continuations-run/function5.scala b/test/files/continuations-run/function5.scala new file mode 100644 index 0000000000..a689ccf243 --- /dev/null +++ b/test/files/continuations-run/function5.scala @@ -0,0 +1,15 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val g: () => Int @cps[Int] = () => 7 + + println(reset(g())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/ifelse0.check b/test/files/continuations-run/ifelse0.check new file mode 100644 index 0000000000..f8bc79860d --- /dev/null +++ b/test/files/continuations-run/ifelse0.check @@ -0,0 +1,2 @@ +10 +9
\ No newline at end of file diff --git a/test/files/continuations-run/ifelse0.scala b/test/files/continuations-run/ifelse0.scala new file mode 100644 index 0000000000..e34b86ee84 --- /dev/null +++ b/test/files/continuations-run/ifelse0.scala @@ -0,0 +1,18 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x:Int) = if (x <= 7) + shift { k: (Int=>Int) => k(k(k(x))) } + else + shift { k: (Int=>Int) => k(x) } + + def main(args: Array[String]): Any = { + println(reset(1 + test(7))) + println(reset(1 + test(8))) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/ifelse1.check b/test/files/continuations-run/ifelse1.check new file mode 100644 index 0000000000..86a3fbc0c1 --- /dev/null +++ b/test/files/continuations-run/ifelse1.check @@ -0,0 +1,4 @@ +10 +9 +8 +11
\ No newline at end of file diff --git a/test/files/continuations-run/ifelse1.scala b/test/files/continuations-run/ifelse1.scala new file mode 100644 index 0000000000..2ccc1ed730 --- /dev/null +++ b/test/files/continuations-run/ifelse1.scala @@ -0,0 +1,25 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test1(x:Int) = if (x <= 7) + shift { k: (Int=>Int) => k(k(k(x))) } + else + x + + def test2(x:Int) = if (x <= 7) + x + else + shift { k: (Int=>Int) => k(k(k(x))) } + + def main(args: Array[String]): Any = { + println(reset(1 + test1(7))) + println(reset(1 + test1(8))) + println(reset(1 + test2(7))) + println(reset(1 + test2(8))) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/ifelse2.check b/test/files/continuations-run/ifelse2.check new file mode 100644 index 0000000000..f97a95b08d --- /dev/null +++ b/test/files/continuations-run/ifelse2.check @@ -0,0 +1,4 @@ +abort +() +alive +() diff --git a/test/files/continuations-run/ifelse2.scala b/test/files/continuations-run/ifelse2.scala new file mode 100644 index 0000000000..536e350190 --- /dev/null +++ b/test/files/continuations-run/ifelse2.scala @@ -0,0 +1,16 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x:Int) = if (x <= 7) + shift { k: (Unit=>Unit) => println("abort") } + + def main(args: Array[String]): Any = { + println(reset{ test(7); println("alive") }) + println(reset{ test(8); println("alive") }) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/ifelse3.check b/test/files/continuations-run/ifelse3.check new file mode 100644 index 0000000000..95b562c8e6 --- /dev/null +++ b/test/files/continuations-run/ifelse3.check @@ -0,0 +1,2 @@ +6 +9 diff --git a/test/files/continuations-run/ifelse3.scala b/test/files/continuations-run/ifelse3.scala new file mode 100644 index 0000000000..5dbd079d1c --- /dev/null +++ b/test/files/continuations-run/ifelse3.scala @@ -0,0 +1,21 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def util(x: Boolean) = shift { k: (Boolean=>Int) => k(x) } + + def test(x:Int) = if (util(x <= 7)) + x - 1 + else + x + 1 + + + def main(args: Array[String]): Any = { + println(reset(test(7))) + println(reset(test(8))) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/infer1.scala b/test/files/continuations-run/infer1.scala new file mode 100644 index 0000000000..a6c6c07215 --- /dev/null +++ b/test/files/continuations-run/infer1.scala @@ -0,0 +1,33 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x: => Int @cpsParam[String,Int]) = 7 + + def test2() = { + val x = shift { k: (Int => String) => 9 } + x + } + + def test3(x: => Int @cpsParam[Int,Int]) = 7 + + + def util() = shift { k: (String => String) => "7" } + + def main(args: Array[String]): Any = { + test { shift { k: (Int => String) => 9 } } + test { shift { k: (Int => String) => 9 }; 2 } +// test { shift { k: (Int => String) => 9 }; util() } <-- doesn't work + test { shift { k: (Int => String) => 9 }; util(); 2 } + + + test { shift { k: (Int => String) => 9 }; { test3(0); 2 } } + + test3 { { test3(0); 2 } } + + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/match0.check b/test/files/continuations-run/match0.check new file mode 100644 index 0000000000..f8bc79860d --- /dev/null +++ b/test/files/continuations-run/match0.check @@ -0,0 +1,2 @@ +10 +9
\ No newline at end of file diff --git a/test/files/continuations-run/match0.scala b/test/files/continuations-run/match0.scala new file mode 100644 index 0000000000..bd36238d7f --- /dev/null +++ b/test/files/continuations-run/match0.scala @@ -0,0 +1,18 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x:Int) = x match { + case 7 => shift { k: (Int=>Int) => k(k(k(x))) } + case 8 => shift { k: (Int=>Int) => k(x) } + } + + def main(args: Array[String]): Any = { + println(reset(1 + test(7))) + println(reset(1 + test(8))) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/match1.check b/test/files/continuations-run/match1.check new file mode 100644 index 0000000000..73053d3f4f --- /dev/null +++ b/test/files/continuations-run/match1.check @@ -0,0 +1,2 @@ +10 +9 diff --git a/test/files/continuations-run/match1.scala b/test/files/continuations-run/match1.scala new file mode 100644 index 0000000000..ea4e219666 --- /dev/null +++ b/test/files/continuations-run/match1.scala @@ -0,0 +1,18 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test(x:Int) = x match { + case 7 => shift { k: (Int=>Int) => k(k(k(x))) } + case _ => x + } + + def main(args: Array[String]): Any = { + println(reset(1 + test(7))) + println(reset(1 + test(8))) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/match2.check b/test/files/continuations-run/match2.check new file mode 100644 index 0000000000..cbf91349cc --- /dev/null +++ b/test/files/continuations-run/match2.check @@ -0,0 +1,2 @@ +B +B diff --git a/test/files/continuations-run/match2.scala b/test/files/continuations-run/match2.scala new file mode 100644 index 0000000000..8d4f04870f --- /dev/null +++ b/test/files/continuations-run/match2.scala @@ -0,0 +1,26 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def test1() = { + val (a, b) = shift { k: (((String,String)) => String) => k("A","B") } + b + } + + case class Elem[T,U](a: T, b: U) + + def test2() = { + val Elem(a,b) = shift { k: (Elem[String,String] => String) => k(Elem("A","B")) } + b + } + + + def main(args: Array[String]): Any = { + println(reset(test1())) + println(reset(test2())) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/t1807.check b/test/files/continuations-run/t1807.check new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/test/files/continuations-run/t1807.check @@ -0,0 +1 @@ +1
\ No newline at end of file diff --git a/test/files/continuations-run/t1807.scala b/test/files/continuations-run/t1807.scala new file mode 100644 index 0000000000..278b3a9936 --- /dev/null +++ b/test/files/continuations-run/t1807.scala @@ -0,0 +1,14 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + def main(args: Array[String]): Unit = { + val z = reset { + val f: (() => Int @cps[Int]) = () => 1 + f() + } + println(z) + } +}
\ No newline at end of file diff --git a/test/files/continuations-run/t1808.scala b/test/files/continuations-run/t1808.scala new file mode 100644 index 0000000000..125c7c1cdf --- /dev/null +++ b/test/files/continuations-run/t1808.scala @@ -0,0 +1,10 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + def main(args: Array[String]): Unit = { + reset0 { 0 } + } +}
\ No newline at end of file diff --git a/test/files/continuations-run/t1820.scala b/test/files/continuations-run/t1820.scala new file mode 100644 index 0000000000..893ddab6d1 --- /dev/null +++ b/test/files/continuations-run/t1820.scala @@ -0,0 +1,14 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + def shifted: Unit @suspendable = shift { (k: Unit => Unit) => () } + def test1(b: => Boolean) = { + reset { + if (b) shifted + } + } + def main(args: Array[String]) = test1(true) +}
\ No newline at end of file diff --git a/test/files/continuations-run/t1821.check b/test/files/continuations-run/t1821.check new file mode 100644 index 0000000000..f7b76115db --- /dev/null +++ b/test/files/continuations-run/t1821.check @@ -0,0 +1,4 @@ +() +() +() +()
\ No newline at end of file diff --git a/test/files/continuations-run/t1821.scala b/test/files/continuations-run/t1821.scala new file mode 100644 index 0000000000..0d5fb553be --- /dev/null +++ b/test/files/continuations-run/t1821.scala @@ -0,0 +1,20 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + def suspended[A](x: A): A @suspendable = x + def test1[A](x: A): A @suspendable = suspended(x) match { case x => x } + def test2[A](x: List[A]): A @suspendable = suspended(x) match { case List(x) => x } + + def test3[A](x: A): A @suspendable = x match { case x => x } + def test4[A](x: List[A]): A @suspendable = x match { case List(x) => x } + + def main(args: Array[String]) = { + println(reset(test1())) + println(reset(test2(List(())))) + println(reset(test3())) + println(reset(test4(List(())))) + } +}
\ No newline at end of file diff --git a/test/files/continuations-run/while0.check b/test/files/continuations-run/while0.check new file mode 100644 index 0000000000..d58c55a31d --- /dev/null +++ b/test/files/continuations-run/while0.check @@ -0,0 +1 @@ +9000 diff --git a/test/files/continuations-run/while0.scala b/test/files/continuations-run/while0.scala new file mode 100644 index 0000000000..9735f9d2c3 --- /dev/null +++ b/test/files/continuations-run/while0.scala @@ -0,0 +1,22 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def foo(): Int @cps[Unit] = 2 + + def test(): Unit @cps[Unit] = { + var x = 0 + while (x < 9000) { // pick number large enough to require tail-call opt + x += foo() + } + println(x) + } + + def main(args: Array[String]): Any = { + reset(test()) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/while1.check b/test/files/continuations-run/while1.check new file mode 100644 index 0000000000..3d5f0b9a46 --- /dev/null +++ b/test/files/continuations-run/while1.check @@ -0,0 +1,11 @@ +up +up +up +up +up +10 +down +down +down +down +down diff --git a/test/files/continuations-run/while1.scala b/test/files/continuations-run/while1.scala new file mode 100644 index 0000000000..fb5dc0079a --- /dev/null +++ b/test/files/continuations-run/while1.scala @@ -0,0 +1,22 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def foo(): Int @cps[Unit] = shift { k => println("up"); k(2); println("down") } + + def test(): Unit @cps[Unit] = { + var x = 0 + while (x < 9) { + x += foo() + } + println(x) + } + + def main(args: Array[String]): Any = { + reset(test()) + } + +}
\ No newline at end of file diff --git a/test/files/continuations-run/while2.check b/test/files/continuations-run/while2.check new file mode 100644 index 0000000000..9fe515181b --- /dev/null +++ b/test/files/continuations-run/while2.check @@ -0,0 +1,19 @@ +up +up +up +up +up +up +up +up +up +9000 +down +down +down +down +down +down +down +down +down diff --git a/test/files/continuations-run/while2.scala b/test/files/continuations-run/while2.scala new file mode 100644 index 0000000000..f36288929e --- /dev/null +++ b/test/files/continuations-run/while2.scala @@ -0,0 +1,23 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def foo1(): Int @cps[Unit] = 2 + def foo2(): Int @cps[Unit] = shift { k => println("up"); k(2); println("down") } + + def test(): Unit @cps[Unit] = { + var x = 0 + while (x < 9000) { // pick number large enough to require tail-call opt + x += (if (x % 1000 != 0) foo1() else foo2()) + } + println(x) + } + + def main(args: Array[String]): Any = { + reset(test()) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example0.scala b/test/pending/continuations-run/example0.scala new file mode 100644 index 0000000000..44b1331339 --- /dev/null +++ b/test/pending/continuations-run/example0.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test0.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example1.scala b/test/pending/continuations-run/example1.scala new file mode 100644 index 0000000000..195a98e59f --- /dev/null +++ b/test/pending/continuations-run/example1.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test1.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example16.scala b/test/pending/continuations-run/example16.scala new file mode 100644 index 0000000000..5eb64046ed --- /dev/null +++ b/test/pending/continuations-run/example16.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test16Printf.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example2.scala b/test/pending/continuations-run/example2.scala new file mode 100644 index 0000000000..0d96257c40 --- /dev/null +++ b/test/pending/continuations-run/example2.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test2.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example3.scala b/test/pending/continuations-run/example3.scala new file mode 100644 index 0000000000..3f5052a4ad --- /dev/null +++ b/test/pending/continuations-run/example3.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test3.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example4.scala b/test/pending/continuations-run/example4.scala new file mode 100644 index 0000000000..66c6774791 --- /dev/null +++ b/test/pending/continuations-run/example4.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test4.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example5.scala b/test/pending/continuations-run/example5.scala new file mode 100644 index 0000000000..0994bdee8a --- /dev/null +++ b/test/pending/continuations-run/example5.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test5.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example6.scala b/test/pending/continuations-run/example6.scala new file mode 100644 index 0000000000..5207e3fc68 --- /dev/null +++ b/test/pending/continuations-run/example6.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test6.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example7.scala b/test/pending/continuations-run/example7.scala new file mode 100644 index 0000000000..fb22387dac --- /dev/null +++ b/test/pending/continuations-run/example7.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test7.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example8.scala b/test/pending/continuations-run/example8.scala new file mode 100644 index 0000000000..8e21e6c674 --- /dev/null +++ b/test/pending/continuations-run/example8.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test8.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/example9.scala b/test/pending/continuations-run/example9.scala new file mode 100644 index 0000000000..0f27c686f7 --- /dev/null +++ b/test/pending/continuations-run/example9.scala @@ -0,0 +1,9 @@ +// $Id$ + +object Test { + + def main(args: Array[String]): Any = { + examples.continuations.Test9Monads.main(args) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/foreach.check b/test/pending/continuations-run/foreach.check new file mode 100644 index 0000000000..9bab7a2eed --- /dev/null +++ b/test/pending/continuations-run/foreach.check @@ -0,0 +1,4 @@ +1 +2 +3 +enough is enough
\ No newline at end of file diff --git a/test/pending/continuations-run/foreach.scala b/test/pending/continuations-run/foreach.scala new file mode 100644 index 0000000000..4daade452c --- /dev/null +++ b/test/pending/continuations-run/foreach.scala @@ -0,0 +1,33 @@ +// $Id$ + +import scala.util.continuations._ + +import scala.util.continuations.Loops._ + +object Test { + + def main(args: Array[String]): Any = { + + + reset { + + val list = List(1,2,3,4,5) + + for (x <- list.suspendable) { + + shift { k: (Unit => Unit) => + println(x) + if (x < 3) + k() + else + println("enough is enough") + } + + } + + } + + + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/function6.scala b/test/pending/continuations-run/function6.scala new file mode 100644 index 0000000000..f1296ae410 --- /dev/null +++ b/test/pending/continuations-run/function6.scala @@ -0,0 +1,15 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args: Array[String]): Any = { + + val g: PartialFunction[Int, Int @cps[Int,Int]] = { case x => 7 } + + println(reset(g(2))) + } + +}
\ No newline at end of file diff --git a/test/pending/continuations-run/select-run.log b/test/pending/continuations-run/select-run.log new file mode 100644 index 0000000000..6faa868ce3 --- /dev/null +++ b/test/pending/continuations-run/select-run.log @@ -0,0 +1,20 @@ +8 +java.lang.ClassCastException: scala.util.continuations.ControlContext cannot be cast to java.lang.Integer + at scala.runtime.BoxesRunTime.unboxToInt(Unknown Source) + at Test$$anonfun$main$2.apply(select.scala:18) + at Test$$anonfun$main$2.apply(select.scala:18) + at scala.util.continuations.ControlContext$.reset(ControlContext.scala:65) + at Test$.main(select.scala:18) + at Test.main(select.scala) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) + at java.lang.reflect.Method.invoke(Method.java:597) + at scala.tools.nsc.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:55) + at scala.tools.nsc.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:22) + at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:61) + at scala.tools.nsc.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:55) + at scala.tools.nsc.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:61) + at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:33) + at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:153) + at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala) diff --git a/test/pending/continuations-run/select.check b/test/pending/continuations-run/select.check new file mode 100644 index 0000000000..620ce84217 --- /dev/null +++ b/test/pending/continuations-run/select.check @@ -0,0 +1,2 @@ +8 +8
\ No newline at end of file diff --git a/test/pending/continuations-run/select.scala b/test/pending/continuations-run/select.scala new file mode 100644 index 0000000000..faf5842329 --- /dev/null +++ b/test/pending/continuations-run/select.scala @@ -0,0 +1,21 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + class Bla { + val x = 8 + } + + def bla = shift { k:(Bla=>Bla) => k(new Bla) } + + // TODO: check whether this also applies to a::shift { k => ... } + + def main(args: Array[String]) = { + println(reset(bla).x) + println(reset(bla.x)) + } + +} diff --git a/test/pending/continuations-run/t2864.scala b/test/pending/continuations-run/t2864.scala new file mode 100644 index 0000000000..291e739332 --- /dev/null +++ b/test/pending/continuations-run/t2864.scala @@ -0,0 +1,18 @@ +// $Id$ + +import scala.util.continuations._ + + + +object Test { + + def double[B](n : Int)(k : Int => B) : B = k(n * 2) + + def main(args : Array[String]) { + reset { + val result1 = shift(double[Unit](100)) + val result2 = shift(double[Unit](result1)) + println(result2) + } + } +}
\ No newline at end of file diff --git a/test/pending/continuations-run/t2934.scala b/test/pending/continuations-run/t2934.scala new file mode 100644 index 0000000000..6089355bcf --- /dev/null +++ b/test/pending/continuations-run/t2934.scala @@ -0,0 +1,14 @@ +// $Id$ + +import scala.util.continuations._ + + +object Test { + + def main(args : Array[String]) { + println(reset { + val x = shift(List(1,2,3).flatMap[Int, List[Int]]) + List(x + 2) + }) + } +}
\ No newline at end of file |