Skip to content

Avoid dead code warnings for users of async. #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 30, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/scala/scala/async/Async.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object Async {
* Run the block of code `body` asynchronously. `body` may contain calls to `await` when the results of
* a `Future` are needed; this is translated into non-blocking code.
*/
def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro internal.ScalaConcurrentAsync.asyncImpl[T]
def async[T](body: => T)(implicit execContext: ExecutionContext): Future[T] = macro internal.ScalaConcurrentAsync.asyncImpl[T]

/**
* Non-blocking await the on result of `awaitable`. This may only be used directly within an enclosing `async` block.
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/scala/async/internal/AsyncId.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ object AsyncId extends AsyncBase {
lazy val futureSystem = IdentityFutureSystem
type FS = IdentityFutureSystem.type

def async[T](body: T) = macro asyncIdImpl[T]
def async[T](body: => T) = macro asyncIdImpl[T]

def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[T] = asyncImpl[T](c)(body)(c.literalUnit)
}
Expand Down
11 changes: 5 additions & 6 deletions src/main/scala/scala/async/internal/AsyncTransform.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@ trait AsyncTransform {
for ((state, flds) <- assignsOf) {
val assigns = flds.map { fld =>
val fieldSym = fld.symbol
Block(
List(
asyncBase.nullOut(c.universe)(c.Expr[String](Literal(Constant(fieldSym.name.toString))), c.Expr[Any](Ident(fieldSym))).tree
),
Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info))
)
val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info))
asyncBase.nullOut(c.universe)(c.Expr[String](Literal(Constant(fieldSym.name.toString))), c.Expr[Any](Ident(fieldSym))).tree match {
case Literal(Constant(value: Unit)) => assign
case x => Block(x :: Nil, assign)
}
}
val asyncState = asyncBlock.asyncStates.find(_.state == state).get
asyncState.stats = assigns ++ asyncState.stats
Expand Down
57 changes: 44 additions & 13 deletions src/main/scala/scala/async/internal/ExprBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,18 @@ trait ExprBuilder {

var stats: List[Tree]

def statsAnd(trees: List[Tree]): List[Tree] = {
val body = stats match {
case init :+ last if tpeOf(last) =:= definitions.NothingTpe =>
adaptToUnit(init :+ Typed(last, TypeTree(definitions.AnyTpe)))
case _ =>
adaptToUnit(stats)
}
Try(body, Nil, adaptToUnit(trees)) :: Nil
}

final def allStats: List[Tree] = this match {
case a: AsyncStateWithAwait => stats :+ a.awaitable.resultValDef
case a: AsyncStateWithAwait => statsAnd(a.awaitable.resultValDef :: Nil)
case _ => stats
}

Expand All @@ -52,8 +62,9 @@ trait ExprBuilder {
def nextStates: List[Int] =
List(nextState)

def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef =
mkHandlerCase(state, stats :+ mkStateTree(nextState, symLookup))
def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = {
mkHandlerCase(state, statsAnd(mkStateTree(nextState, symLookup) :: Nil))
}

override val toString: String =
s"AsyncState #$state, next = $nextState"
Expand Down Expand Up @@ -87,10 +98,10 @@ trait ExprBuilder {
val tryGetOrCallOnComplete =
if (futureSystemOps.continueCompletedFutureOnSameThread)
If(futureSystemOps.isCompleted(c.Expr[futureSystem.Fut[_]](awaitable.expr)).tree,
Block(ifIsFailureTree[T](futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) :: Nil, literalUnit),
Block(callOnComplete :: Nil, Return(literalUnit)))
adaptToUnit(ifIsFailureTree[T](futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) :: Nil),
Block(toList(callOnComplete), Return(literalUnit)))
else
Block(callOnComplete :: Nil, Return(literalUnit))
Block(toList(callOnComplete), Return(literalUnit))
mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup), tryGetOrCallOnComplete))
}

Expand All @@ -110,11 +121,11 @@ trait ExprBuilder {
*/
def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) =
If(futureSystemOps.tryyIsFailure(c.Expr[futureSystem.Tryy[T]](tryReference)).tree,
Block(futureSystemOps.completeProm[T](
Block(toList(futureSystemOps.completeProm[T](
c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)),
c.Expr[futureSystem.Tryy[T]](
TypeApply(Select(tryReference, newTermName("asInstanceOf")),
List(TypeTree(futureSystemOps.tryType[T]))))).tree :: Nil,
List(TypeTree(futureSystemOps.tryType[T]))))).tree),
Return(literalUnit)),
Block(List(tryGetTree(tryReference)), mkStateTree(nextState, symLookup))
)
Expand Down Expand Up @@ -382,12 +393,12 @@ trait ExprBuilder {
val t = c.Expr[Throwable](Ident(name.t))
val complete = futureSystemOps.completeProm[T](
c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), futureSystemOps.tryyFailure[T](t)).tree
Block(complete :: Nil, Return(literalUnit))
Block(toList(complete), Return(literalUnit))
})), EmptyTree)

def forever(t: Tree): Tree = {
val labelName = name.fresh("while$")
LabelDef(labelName, Nil, Block(t :: Nil, Apply(Ident(labelName), Nil)))
LabelDef(labelName, Nil, Block(toList(t), Apply(Ident(labelName), Nil)))
}

/**
Expand All @@ -405,7 +416,7 @@ trait ExprBuilder {
def onCompleteHandler[T: WeakTypeTag]: Tree = {
val onCompletes = initStates.flatMap(_.mkOnCompleteHandler[T]).toList
forever {
Block(resumeFunTree :: Nil, literalUnit)
adaptToUnit(toList(resumeFunTree))
}
}
}
Expand All @@ -422,12 +433,32 @@ trait ExprBuilder {
Assign(symLookup.memberRef(name.state), Literal(Constant(nextState)))

private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef =
mkHandlerCase(num, Block(rhs, literalUnit))
mkHandlerCase(num, adaptToUnit(rhs))

private def tpeOf(t: Tree): Type = t match {
case _ if t.tpe != null => t.tpe
case Try(body, Nil, _) => tpeOf(body)
case _ => NoType
}

private def adaptToUnit(rhs: List[Tree]): c.universe.Block = {
rhs match {
case init :+ last if tpeOf(last) <:< definitions.UnitTpe =>
Block(init, last)
case _ =>
Block(rhs, literalUnit)
}
}

private def mkHandlerCase(num: Int, rhs: Tree): CaseDef =
CaseDef(Literal(Constant(num)), EmptyTree, rhs)

def literalUnit = Literal(Constant(()))
def literalUnit = Literal(Constant(())) // a def to avoid sharing trees

def toList(tree: Tree): List[Tree] = tree match {
case Block(stats, Literal(Constant(value))) if value == () => stats
case _ => tree :: Nil
}

def literalNull = Literal(Constant(null))
}
12 changes: 12 additions & 0 deletions src/test/scala/scala/async/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ package object async {
m.mkToolBox(options = compileOptions)
}

import scala.tools.nsc._, reporters._
def mkGlobal(compileOptions: String = ""): Global = {
val settings = new Settings()
settings.processArgumentString(compileOptions)
val initClassPath = settings.classpath.value
settings.embeddedDefaults(getClass.getClassLoader)
if (initClassPath == settings.classpath.value)
settings.usejavacp.value = true // not running under SBT, try to use the Java claspath instead
val reporter = new StoreReporter
new Global(settings, reporter)
}

def scalaBinaryVersion: String = {
val PreReleasePattern = """.*-(M|RC).*""".r
val Pattern = """(\d+\.\d+)\..*""".r
Expand Down
48 changes: 45 additions & 3 deletions src/test/scala/scala/async/run/WarningsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ package run

import org.junit.Test

import scala.async.internal.AsyncId
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.language.{postfixOps, reflectiveCalls}
import scala.tools.nsc.reporters.StoreReporter


class WarningsSpec {
Expand All @@ -32,4 +30,48 @@ class WarningsSpec {
""".stripMargin
})
}

@Test
// https://github.com/scala/async/issues/74
def noDeadCodeWarningForAsyncThrow() {
val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks")
// was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses"
val source =
"""
| class Test {
| import scala.async.Async._
| import scala.concurrent.ExecutionContext.Implicits.global
| async { throw new Error() }
| }
""".stripMargin
val run = new global.Run
val sourceFile = global.newSourceFile(source)
run.compileSources(sourceFile :: Nil)
assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
}

@Test
def noDeadCodeWarningInMacroExpansion() {
val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks")
val source = """
| class Test {
| def test = {
| import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global
| async {
| val opt = await(async(Option.empty[String => Future[Unit]]))
| opt match {
| case None =>
| throw new RuntimeException("case a")
| case Some(f) =>
| await(f("case b"))
| }
| }
| }
|}
""".stripMargin
val run = new global.Run
val sourceFile = global.newSourceFile(source)
run.compileSources(sourceFile :: Nil)
assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
}
}