aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2013-08-21 23:29:11 +0200
committerJason Zaugg <jzaugg@gmail.com>2013-08-22 17:24:38 +0200
commit98e2f26000aaaf5abb527f776426c4759b95cde8 (patch)
tree03dbf931b87f952286febbf8bced6ca735b5c2a5
parent8371f480ca6783ea52aa76f4990eed101938c224 (diff)
downloadscala-async-98e2f26000aaaf5abb527f776426c4759b95cde8.tar.gz
scala-async-98e2f26000aaaf5abb527f776426c4759b95cde8.tar.bz2
scala-async-98e2f26000aaaf5abb527f776426c4759b95cde8.zip
Use @uncheckedBounds to avoid introducing refchecks errors
... in code that would otherwise have smuggled through these slack LUBs in the types of trees but never in a TypeTree. More details in SI-7694. Fixes #29
-rw-r--r--build.sbt6
-rw-r--r--src/main/scala/scala/async/internal/AnfTransform.scala6
-rw-r--r--src/main/scala/scala/async/internal/AsyncId.scala2
-rw-r--r--src/main/scala/scala/async/internal/AsyncTransform.scala11
-rw-r--r--src/main/scala/scala/async/internal/ExprBuilder.scala8
-rw-r--r--src/main/scala/scala/async/internal/TransformUtils.scala13
-rw-r--r--src/test/scala/scala/async/run/toughtype/ToughType.scala9
-rw-r--r--src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala57
8 files changed, 96 insertions, 16 deletions
diff --git a/build.sbt b/build.sbt
index 2ee1235..a013bb2 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,10 +1,10 @@
-scalaVersion := "2.10.2"
+scalaVersion := "2.10.3-RC1"
+
+organization := "org.typesafe.async" // TODO new org name under scala-lang.
// Uncomment to test with a locally built copy of Scala.
// scalaHome := Some(file("/code/scala2/build/pack"))
-organization := "org.typesafe.async" // TODO new org name under scala-lang.
-
name := "scala-async"
version := "1.0.0-SNAPSHOT"
diff --git a/src/main/scala/scala/async/internal/AnfTransform.scala b/src/main/scala/scala/async/internal/AnfTransform.scala
index 6aeaba3..0f8bc67 100644
--- a/src/main/scala/scala/async/internal/AnfTransform.scala
+++ b/src/main/scala/scala/async/internal/AnfTransform.scala
@@ -119,8 +119,8 @@ private[async] trait AnfTransform {
}
private def defineVar(prefix: String, tp: Type, pos: Position): ValDef = {
- val sym = currOwner.newTermSymbol(name.fresh(prefix), pos, MUTABLE | SYNTHETIC).setInfo(tp)
- ValDef(sym, gen.mkZero(tp)).setType(NoType).setPos(pos)
+ val sym = currOwner.newTermSymbol(name.fresh(prefix), pos, MUTABLE | SYNTHETIC).setInfo(uncheckedBounds(tp))
+ ValDef(sym, gen.mkZero(uncheckedBounds(tp))).setType(NoType).setPos(pos)
}
}
@@ -145,7 +145,7 @@ private[async] trait AnfTransform {
}
private def defineVal(prefix: String, lhs: Tree, pos: Position): ValDef = {
- val sym = currOwner.newTermSymbol(name.fresh(prefix), pos, SYNTHETIC).setInfo(lhs.tpe)
+ val sym = currOwner.newTermSymbol(name.fresh(prefix), pos, SYNTHETIC).setInfo(uncheckedBounds(lhs.tpe))
changeOwner(lhs, currentOwner, sym)
ValDef(sym, changeOwner(lhs, currentOwner, sym)).setType(NoType).setPos(pos)
}
diff --git a/src/main/scala/scala/async/internal/AsyncId.scala b/src/main/scala/scala/async/internal/AsyncId.scala
index 4334088..b9d82e2 100644
--- a/src/main/scala/scala/async/internal/AsyncId.scala
+++ b/src/main/scala/scala/async/internal/AsyncId.scala
@@ -41,7 +41,7 @@ object IdentityFutureSystem extends FutureSystem {
def execContextType: Type = weakTypeOf[Unit]
def createProm[A: WeakTypeTag]: Expr[Prom[A]] = reify {
- new Prom()
+ new Prom[A]()
}
def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = reify {
diff --git a/src/main/scala/scala/async/internal/AsyncTransform.scala b/src/main/scala/scala/async/internal/AsyncTransform.scala
index c755c87..78a0876 100644
--- a/src/main/scala/scala/async/internal/AsyncTransform.scala
+++ b/src/main/scala/scala/async/internal/AsyncTransform.scala
@@ -6,7 +6,12 @@ trait AsyncTransform {
import global._
def asyncTransform[T](body: Tree, execContext: Tree, cpsFallbackEnabled: Boolean)
- (implicit resultType: WeakTypeTag[T]): Tree = {
+ (resultType: WeakTypeTag[T]): Tree = {
+
+ // We annotate the type of the whole expression as `T @uncheckedBounds` so as not to introduce
+ // warnings about non-conformant LUBs. See SI-7694
+ // This implicit propatages the annotated type in the type tag.
+ implicit val uncheckedBoundsResultTag: WeakTypeTag[T] = WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, uncheckedBounds(resultType.tpe)))
reportUnsupportedAwaits(body, report = !cpsFallbackEnabled)
@@ -22,12 +27,12 @@ trait AsyncTransform {
DefDef(NoMods, name.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), Literal(Constant(())))
}
- val stateMachineType = applied("scala.async.StateMachine", List(futureSystemOps.promType[T], futureSystemOps.execContextType))
+ val stateMachineType = applied("scala.async.StateMachine", List(futureSystemOps.promType[T](uncheckedBoundsResultTag), futureSystemOps.execContextType))
val stateMachine: ClassDef = {
val body: List[Tree] = {
val stateVar = ValDef(Modifiers(Flag.MUTABLE | Flag.PRIVATE | Flag.LOCAL), name.state, TypeTree(definitions.IntTpe), Literal(Constant(0)))
- val result = ValDef(NoMods, name.result, TypeTree(futureSystemOps.promType[T]), futureSystemOps.createProm[T].tree)
+ val result = ValDef(NoMods, name.result, TypeTree(futureSystemOps.promType[T](uncheckedBoundsResultTag)), futureSystemOps.createProm[T](uncheckedBoundsResultTag).tree)
val execContextValDef = ValDef(NoMods, name.execContext, TypeTree(), execContext)
val apply0DefDef: DefDef = {
diff --git a/src/main/scala/scala/async/internal/ExprBuilder.scala b/src/main/scala/scala/async/internal/ExprBuilder.scala
index e0da874..f43d1cb 100644
--- a/src/main/scala/scala/async/internal/ExprBuilder.scala
+++ b/src/main/scala/scala/async/internal/ExprBuilder.scala
@@ -284,7 +284,7 @@ trait ExprBuilder {
def onCompleteHandler[T: WeakTypeTag]: Tree
- def resumeFunTree[T]: DefDef
+ def resumeFunTree[T: WeakTypeTag]: DefDef
}
case class SymLookup(stateMachineClass: Symbol, applyTrParam: Symbol) {
@@ -303,12 +303,12 @@ trait ExprBuilder {
new AsyncBlock {
def asyncStates = blockBuilder.asyncStates.toList
- def mkCombinedHandlerCases[T]: List[CaseDef] = {
+ def mkCombinedHandlerCases[T: WeakTypeTag]: List[CaseDef] = {
val caseForLastState: CaseDef = {
val lastState = asyncStates.last
val lastStateBody = Expr[T](lastState.body)
val rhs = futureSystemOps.completeProm(
- Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), reify(scala.util.Success(lastStateBody.splice)))
+ Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), reify(scala.util.Success[T](lastStateBody.splice)))
mkHandlerCase(lastState.state, rhs.tree)
}
asyncStates.toList match {
@@ -337,7 +337,7 @@ trait ExprBuilder {
* }
* }
*/
- def resumeFunTree[T]: DefDef =
+ def resumeFunTree[T: WeakTypeTag]: DefDef =
DefDef(Modifiers(), name.resume, Nil, List(Nil), Ident(definitions.UnitClass),
Try(
Match(symLookup.memberRef(name.state), mkCombinedHandlerCases[T]),
diff --git a/src/main/scala/scala/async/internal/TransformUtils.scala b/src/main/scala/scala/async/internal/TransformUtils.scala
index 70237bc..663ca45 100644
--- a/src/main/scala/scala/async/internal/TransformUtils.scala
+++ b/src/main/scala/scala/async/internal/TransformUtils.scala
@@ -244,8 +244,19 @@ private[async] trait TransformUtils {
// Attributed version of `TreeGen#mkCastPreservingAnnotations`
def mkAttributedCastPreservingAnnotations(tree: Tree, tp: Type): Tree = {
atPos(tree.pos) {
- val casted = gen.mkAttributedCast(tree, tp.withoutAnnotations.dealias)
+ val casted = gen.mkAttributedCast(tree, uncheckedBounds(tp.withoutAnnotations).dealias)
Typed(casted, TypeTree(tp)).setType(tp)
}
}
+
+ // =====================================
+ // Copy/Pasted from Scala 2.10.3. See SI-7694.
+ private lazy val UncheckedBoundsClass = {
+ global.rootMirror.getClassIfDefined("scala.reflect.internal.annotations.uncheckedBounds")
+ }
+ final def uncheckedBounds(tp: Type): Type = {
+ if (tp.typeArgs.isEmpty || UncheckedBoundsClass == NoSymbol) tp
+ else tp.withAnnotation(AnnotationInfo marker UncheckedBoundsClass.tpe)
+ }
+ // =====================================
}
diff --git a/src/test/scala/scala/async/run/toughtype/ToughType.scala b/src/test/scala/scala/async/run/toughtype/ToughType.scala
index 202a288..b342a00 100644
--- a/src/test/scala/scala/async/run/toughtype/ToughType.scala
+++ b/src/test/scala/scala/async/run/toughtype/ToughType.scala
@@ -10,7 +10,7 @@ import language.{reflectiveCalls, postfixOps}
import scala.concurrent._
import scala.concurrent.duration._
import scala.async.Async._
-import org.junit.Test
+import org.junit.{Assert, Test}
import scala.async.internal.AsyncId
@@ -136,3 +136,10 @@ class ToughTypeSpec {
foo
}
}
+
+trait A
+trait B
+
+trait L[A2, B2 <: A2] {
+ def bar(a: Any, b: Any) = 0
+}
diff --git a/src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala b/src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala
new file mode 100644
index 0000000..5eb1f32
--- /dev/null
+++ b/src/test/scala/scala/async/run/uncheckedBounds/UncheckedBoundsSpec.scala
@@ -0,0 +1,57 @@
+package scala.async
+package run
+package uncheckedBounds
+
+import org.junit.{Test, Assert}
+import scala.async.TreeInterrogation
+
+class UncheckedBoundsSpec {
+ @Test def insufficientLub_SI_7694() {
+ suppressingFailureBefore2_10_3 {
+ eval( s"""
+ object Test {
+ import _root_.scala.async.run.toughtype._
+ import _root_.scala.async.internal.AsyncId.{async, await}
+ async {
+ (if (true) await(null: L[A, A]) else await(null: L[B, B]))
+ }
+ }
+ """, compileOptions = s"-cp ${toolboxClasspath} ")
+ }
+ }
+
+ @Test def insufficientLub_SI_7694_ScalaConcurrent() {
+ suppressingFailureBefore2_10_3 {
+ eval( s"""
+ object Test {
+ import _root_.scala.async.run.toughtype._
+ import _root_.scala.async.Async.{async, await}
+ import scala.concurrent._
+ import scala.concurrent.ExecutionContext.Implicits.global
+ async {
+ (if (true) await(null: Future[L[A, A]]) else await(null: Future[L[B, B]]))
+ }
+ }
+ """, compileOptions = s"-cp ${toolboxClasspath} ")
+ }
+ }
+
+ private def suppressingFailureBefore2_10_3(body: => Any) {
+ try {
+ body
+ } catch {
+ case x: Throwable =>
+ // @uncheckedBounds was only introduced in 2.10.3/ 2.11.0-M5, so avoid reporting this test failure in those cases.
+ scala.util.Properties.versionNumberString match {
+ case "2.10.0" | "2.10.1" | "2.10.2" | "2.11.0-M4" => // ignore, the @uncheckedBounds doesn't exist yet
+ case _ =>
+ val annotationExists =
+ reflect.runtime.currentMirror.staticClass("scala.reflect.internal.annotations.uncheckedBounds") == reflect.runtime.universe.NoSymbol
+ if (annotationExists)
+ Assert.fail("@uncheckedBounds not found in scala-reflect.jar")
+ else
+ Assert.fail(s"@uncheckedBounds exists, but it didn't prevent this failure: $x")
+ }
+ }
+ }
+}