diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2013-08-21 23:29:11 +0200 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2013-08-22 17:24:38 +0200 |
commit | 98e2f26000aaaf5abb527f776426c4759b95cde8 (patch) | |
tree | 03dbf931b87f952286febbf8bced6ca735b5c2a5 /src | |
parent | 8371f480ca6783ea52aa76f4990eed101938c224 (diff) | |
download | scala-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
Diffstat (limited to 'src')
7 files changed, 93 insertions, 13 deletions
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") + } + } + } +} |