Skip to content

Commit 5ee834d

Browse files
committed
Merge pull request #132 from retronym/ticket/105
Avoid dead code warnings for users of async.
2 parents a7a1e5b + 7238bc1 commit 5ee834d

File tree

6 files changed

+108
-24
lines changed

6 files changed

+108
-24
lines changed

src/main/scala/scala/async/Async.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ object Async {
4242
* Run the block of code `body` asynchronously. `body` may contain calls to `await` when the results of
4343
* a `Future` are needed; this is translated into non-blocking code.
4444
*/
45-
def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro internal.ScalaConcurrentAsync.asyncImpl[T]
45+
def async[T](body: => T)(implicit execContext: ExecutionContext): Future[T] = macro internal.ScalaConcurrentAsync.asyncImpl[T]
4646

4747
/**
4848
* Non-blocking await the on result of `awaitable`. This may only be used directly within an enclosing `async` block.

src/main/scala/scala/async/internal/AsyncId.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ object AsyncId extends AsyncBase {
1212
lazy val futureSystem = IdentityFutureSystem
1313
type FS = IdentityFutureSystem.type
1414

15-
def async[T](body: T) = macro asyncIdImpl[T]
15+
def async[T](body: => T) = macro asyncIdImpl[T]
1616

1717
def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[T] = asyncImpl[T](c)(body)(c.literalUnit)
1818
}

src/main/scala/scala/async/internal/AsyncTransform.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,11 @@ trait AsyncTransform {
7070
for ((state, flds) <- assignsOf) {
7171
val assigns = flds.map { fld =>
7272
val fieldSym = fld.symbol
73-
Block(
74-
List(
75-
asyncBase.nullOut(c.universe)(c.Expr[String](Literal(Constant(fieldSym.name.toString))), c.Expr[Any](Ident(fieldSym))).tree
76-
),
77-
Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info))
78-
)
73+
val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info))
74+
asyncBase.nullOut(c.universe)(c.Expr[String](Literal(Constant(fieldSym.name.toString))), c.Expr[Any](Ident(fieldSym))).tree match {
75+
case Literal(Constant(value: Unit)) => assign
76+
case x => Block(x :: Nil, assign)
77+
}
7978
}
8079
val asyncState = asyncBlock.asyncStates.find(_.state == state).get
8180
asyncState.stats = assigns ++ asyncState.stats

src/main/scala/scala/async/internal/ExprBuilder.scala

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,18 @@ trait ExprBuilder {
3434

3535
var stats: List[Tree]
3636

37+
def statsAnd(trees: List[Tree]): List[Tree] = {
38+
val body = stats match {
39+
case init :+ last if tpeOf(last) =:= definitions.NothingTpe =>
40+
adaptToUnit(init :+ Typed(last, TypeTree(definitions.AnyTpe)))
41+
case _ =>
42+
adaptToUnit(stats)
43+
}
44+
Try(body, Nil, adaptToUnit(trees)) :: Nil
45+
}
46+
3747
final def allStats: List[Tree] = this match {
38-
case a: AsyncStateWithAwait => stats :+ a.awaitable.resultValDef
48+
case a: AsyncStateWithAwait => statsAnd(a.awaitable.resultValDef :: Nil)
3949
case _ => stats
4050
}
4151

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

55-
def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef =
56-
mkHandlerCase(state, stats :+ mkStateTree(nextState, symLookup))
65+
def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = {
66+
mkHandlerCase(state, statsAnd(mkStateTree(nextState, symLookup) :: Nil))
67+
}
5768

5869
override val toString: String =
5970
s"AsyncState #$state, next = $nextState"
@@ -87,10 +98,10 @@ trait ExprBuilder {
8798
val tryGetOrCallOnComplete =
8899
if (futureSystemOps.continueCompletedFutureOnSameThread)
89100
If(futureSystemOps.isCompleted(c.Expr[futureSystem.Fut[_]](awaitable.expr)).tree,
90-
Block(ifIsFailureTree[T](futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) :: Nil, literalUnit),
91-
Block(callOnComplete :: Nil, Return(literalUnit)))
101+
adaptToUnit(ifIsFailureTree[T](futureSystemOps.getCompleted[Any](c.Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) :: Nil),
102+
Block(toList(callOnComplete), Return(literalUnit)))
92103
else
93-
Block(callOnComplete :: Nil, Return(literalUnit))
104+
Block(toList(callOnComplete), Return(literalUnit))
94105
mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup), tryGetOrCallOnComplete))
95106
}
96107

@@ -110,11 +121,11 @@ trait ExprBuilder {
110121
*/
111122
def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) =
112123
If(futureSystemOps.tryyIsFailure(c.Expr[futureSystem.Tryy[T]](tryReference)).tree,
113-
Block(futureSystemOps.completeProm[T](
124+
Block(toList(futureSystemOps.completeProm[T](
114125
c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)),
115126
c.Expr[futureSystem.Tryy[T]](
116127
TypeApply(Select(tryReference, newTermName("asInstanceOf")),
117-
List(TypeTree(futureSystemOps.tryType[T]))))).tree :: Nil,
128+
List(TypeTree(futureSystemOps.tryType[T]))))).tree),
118129
Return(literalUnit)),
119130
Block(List(tryGetTree(tryReference)), mkStateTree(nextState, symLookup))
120131
)
@@ -382,12 +393,12 @@ trait ExprBuilder {
382393
val t = c.Expr[Throwable](Ident(name.t))
383394
val complete = futureSystemOps.completeProm[T](
384395
c.Expr[futureSystem.Prom[T]](symLookup.memberRef(name.result)), futureSystemOps.tryyFailure[T](t)).tree
385-
Block(complete :: Nil, Return(literalUnit))
396+
Block(toList(complete), Return(literalUnit))
386397
})), EmptyTree)
387398

388399
def forever(t: Tree): Tree = {
389400
val labelName = name.fresh("while$")
390-
LabelDef(labelName, Nil, Block(t :: Nil, Apply(Ident(labelName), Nil)))
401+
LabelDef(labelName, Nil, Block(toList(t), Apply(Ident(labelName), Nil)))
391402
}
392403

393404
/**
@@ -405,7 +416,7 @@ trait ExprBuilder {
405416
def onCompleteHandler[T: WeakTypeTag]: Tree = {
406417
val onCompletes = initStates.flatMap(_.mkOnCompleteHandler[T]).toList
407418
forever {
408-
Block(resumeFunTree :: Nil, literalUnit)
419+
adaptToUnit(toList(resumeFunTree))
409420
}
410421
}
411422
}
@@ -422,12 +433,32 @@ trait ExprBuilder {
422433
Assign(symLookup.memberRef(name.state), Literal(Constant(nextState)))
423434

424435
private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef =
425-
mkHandlerCase(num, Block(rhs, literalUnit))
436+
mkHandlerCase(num, adaptToUnit(rhs))
437+
438+
private def tpeOf(t: Tree): Type = t match {
439+
case _ if t.tpe != null => t.tpe
440+
case Try(body, Nil, _) => tpeOf(body)
441+
case _ => NoType
442+
}
443+
444+
private def adaptToUnit(rhs: List[Tree]): c.universe.Block = {
445+
rhs match {
446+
case init :+ last if tpeOf(last) <:< definitions.UnitTpe =>
447+
Block(init, last)
448+
case _ =>
449+
Block(rhs, literalUnit)
450+
}
451+
}
426452

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

430-
def literalUnit = Literal(Constant(()))
456+
def literalUnit = Literal(Constant(())) // a def to avoid sharing trees
457+
458+
def toList(tree: Tree): List[Tree] = tree match {
459+
case Block(stats, Literal(Constant(value))) if value == () => stats
460+
case _ => tree :: Nil
461+
}
431462

432463
def literalNull = Literal(Constant(null))
433464
}

src/test/scala/scala/async/package.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ package object async {
5151
m.mkToolBox(options = compileOptions)
5252
}
5353

54+
import scala.tools.nsc._, reporters._
55+
def mkGlobal(compileOptions: String = ""): Global = {
56+
val settings = new Settings()
57+
settings.processArgumentString(compileOptions)
58+
val initClassPath = settings.classpath.value
59+
settings.embeddedDefaults(getClass.getClassLoader)
60+
if (initClassPath == settings.classpath.value)
61+
settings.usejavacp.value = true // not running under SBT, try to use the Java claspath instead
62+
val reporter = new StoreReporter
63+
new Global(settings, reporter)
64+
}
65+
5466
def scalaBinaryVersion: String = {
5567
val PreReleasePattern = """.*-(M|RC).*""".r
5668
val Pattern = """(\d+\.\d+)\..*""".r

src/test/scala/scala/async/run/WarningsSpec.scala

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ package run
77

88
import org.junit.Test
99

10-
import scala.async.internal.AsyncId
11-
import scala.concurrent.Await
12-
import scala.concurrent.duration._
1310
import scala.language.{postfixOps, reflectiveCalls}
11+
import scala.tools.nsc.reporters.StoreReporter
1412

1513

1614
class WarningsSpec {
@@ -32,4 +30,48 @@ class WarningsSpec {
3230
""".stripMargin
3331
})
3432
}
33+
34+
@Test
35+
// https://github.com/scala/async/issues/74
36+
def noDeadCodeWarningForAsyncThrow() {
37+
val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks")
38+
// was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses"
39+
val source =
40+
"""
41+
| class Test {
42+
| import scala.async.Async._
43+
| import scala.concurrent.ExecutionContext.Implicits.global
44+
| async { throw new Error() }
45+
| }
46+
""".stripMargin
47+
val run = new global.Run
48+
val sourceFile = global.newSourceFile(source)
49+
run.compileSources(sourceFile :: Nil)
50+
assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
51+
}
52+
53+
@Test
54+
def noDeadCodeWarningInMacroExpansion() {
55+
val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks")
56+
val source = """
57+
| class Test {
58+
| def test = {
59+
| import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global
60+
| async {
61+
| val opt = await(async(Option.empty[String => Future[Unit]]))
62+
| opt match {
63+
| case None =>
64+
| throw new RuntimeException("case a")
65+
| case Some(f) =>
66+
| await(f("case b"))
67+
| }
68+
| }
69+
| }
70+
|}
71+
""".stripMargin
72+
val run = new global.Run
73+
val sourceFile = global.newSourceFile(source)
74+
run.compileSources(sourceFile :: Nil)
75+
assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
76+
}
3577
}

0 commit comments

Comments
 (0)