Skip to content

Commit 656748c

Browse files
committed
Merge pull request #143 from retronym/ticket/prescompiler-nested-async
Avoid spurious "illegal await" error in IDE with nesting
2 parents 7263aaa + 01b36ea commit 656748c

File tree

7 files changed

+63
-17
lines changed

7 files changed

+63
-17
lines changed

src/main/scala/scala/async/internal/AsyncAnalysis.scala

+15-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package scala.async.internal
66

7+
import scala.collection.mutable.ListBuffer
78
import scala.reflect.macros.Context
89
import scala.collection.mutable
910

@@ -53,14 +54,13 @@ trait AsyncAnalysis {
5354
}
5455

5556
override def traverse(tree: Tree) {
56-
def containsAwait = tree exists isAwait
5757
tree match {
58-
case Try(_, _, _) if containsAwait =>
58+
case Try(_, _, _) if containsAwait(tree) =>
5959
reportUnsupportedAwait(tree, "try/catch")
6060
super.traverse(tree)
6161
case Return(_) =>
6262
c.abort(tree.pos, "return is illegal within a async block")
63-
case DefDef(mods, _, _, _, _, _) if mods.hasFlag(Flag.LAZY) && containsAwait =>
63+
case DefDef(mods, _, _, _, _, _) if mods.hasFlag(Flag.LAZY) && containsAwait(tree) =>
6464
reportUnsupportedAwait(tree, "lazy val initializer")
6565
case CaseDef(_, guard, _) if guard exists isAwait =>
6666
// TODO lift this restriction
@@ -74,9 +74,19 @@ trait AsyncAnalysis {
7474
* @return true, if the tree contained an unsupported await.
7575
*/
7676
private def reportUnsupportedAwait(tree: Tree, whyUnsupported: String): Boolean = {
77-
val badAwaits: List[RefTree] = tree collect {
78-
case rt: RefTree if isAwait(rt) => rt
77+
val badAwaits = ListBuffer[Tree]()
78+
object traverser extends Traverser {
79+
override def traverse(tree: Tree): Unit = {
80+
if (!isAsync(tree))
81+
super.traverse(tree)
82+
tree match {
83+
case rt: RefTree if isAwait(rt) =>
84+
badAwaits += rt
85+
case _ =>
86+
}
87+
}
7988
}
89+
traverser(tree)
8090
badAwaits foreach {
8191
tree =>
8292
reportError(tree.pos, s"await must not be used under a $whyUnsupported.")

src/main/scala/scala/async/internal/AsyncBase.scala

+5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ abstract class AsyncBase {
5353
c.Expr[futureSystem.Fut[T]](code)
5454
}
5555

56+
protected[async] def asyncMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = {
57+
import u._
58+
asyncMacroSymbol.owner.typeSignature.member(newTermName("async"))
59+
}
60+
5661
protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = {
5762
import u._
5863
asyncMacroSymbol.owner.typeSignature.member(newTermName("await"))

src/main/scala/scala/async/internal/AsyncMacro.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ object AsyncMacro {
1111
// These members are required by `ExprBuilder`:
1212
val futureSystem: FutureSystem = base.futureSystem
1313
val futureSystemOps: futureSystem.Ops {val c: self.c.type} = futureSystem.mkOps(c)
14-
val containsAwait: c.Tree => Boolean = containsAwaitCached(body0)
14+
var containsAwait: c.Tree => Boolean = containsAwaitCached(body0)
1515
}
1616
}
1717
}
@@ -22,7 +22,7 @@ private[async] trait AsyncMacro
2222

2323
val c: scala.reflect.macros.Context
2424
val body: c.Tree
25-
val containsAwait: c.Tree => Boolean
25+
var containsAwait: c.Tree => Boolean
2626

2727
lazy val macroPos = c.macroApplication.pos.makeTransparent
2828
def atMacroPos(t: c.Tree) = c.universe.atPos(macroPos)(t)

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

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ trait AsyncTransform {
2626

2727
val anfTree = futureSystemOps.postAnfTransform(anfTree0)
2828

29+
cleanupContainsAwaitAttachments(anfTree)
30+
containsAwait = containsAwaitCached(anfTree)
31+
2932
val applyDefDefDummyBody: DefDef = {
3033
val applyVParamss = List(List(ValDef(Modifiers(Flag.PARAM), name.tr, TypeTree(futureSystemOps.tryType[Any]), EmptyTree)))
3134
DefDef(NoMods, name.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), literalUnit)

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,8 @@ trait ExprBuilder {
237237
var stateBuilder = new AsyncStateBuilder(startState, symLookup)
238238
var currState = startState
239239

240-
def checkForUnsupportedAwait(tree: Tree) = if (tree exists {
241-
case Apply(fun, _) if isAwait(fun) => true
242-
case _ => false
243-
}) c.abort(tree.pos, "await must not be used in this position")
240+
def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree))
241+
c.abort(tree.pos, "await must not be used in this position")
244242

245243
def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = {
246244
val (nestedStats, nestedExpr) = statsAndExpr(nestedTree)

src/main/scala/scala/async/internal/TransformUtils.scala

+16-6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ private[async] trait TransformUtils {
3838
def fresh(name: String): String = c.freshName(name)
3939
}
4040

41+
def isAsync(fun: Tree) =
42+
fun.symbol == defn.Async_async
43+
4144
def isAwait(fun: Tree) =
4245
fun.symbol == defn.Async_await
4346

@@ -164,6 +167,7 @@ private[async] trait TransformUtils {
164167

165168
val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal")
166169
val ThrowableClass = rootMirror.staticClass("java.lang.Throwable")
170+
val Async_async = asyncBase.asyncMethod(c.universe)(c.macroApplication.symbol).ensuring(_ != NoSymbol)
167171
val Async_await = asyncBase.awaitMethod(c.universe)(c.macroApplication.symbol).ensuring(_ != NoSymbol)
168172
val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException")
169173
}
@@ -281,6 +285,8 @@ private[async] trait TransformUtils {
281285

282286
override def traverse(tree: Tree) {
283287
tree match {
288+
case _ if isAsync(tree) =>
289+
// Under -Ymacro-expand:discard, used in the IDE, nested async blocks will be visible to the outer blocks
284290
case cd: ClassDef => nestedClass(cd)
285291
case md: ModuleDef => nestedModule(md)
286292
case dd: DefDef => nestedMethod(dd)
@@ -398,7 +404,7 @@ private[async] trait TransformUtils {
398404
final def containsAwaitCached(t: Tree): Tree => Boolean = {
399405
def treeCannotContainAwait(t: Tree) = t match {
400406
case _: Ident | _: TypeTree | _: Literal => true
401-
case _ => false
407+
case _ => isAsync(t)
402408
}
403409
def shouldAttach(t: Tree) = !treeCannotContainAwait(t)
404410
val symtab = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]
@@ -417,11 +423,15 @@ private[async] trait TransformUtils {
417423
override def traverse(tree: Tree): Unit = {
418424
stack ::= tree
419425
try {
420-
if (isAwait(tree))
421-
stack.foreach(attachContainsAwait)
422-
else
423-
attachNoAwait(tree)
424-
super.traverse(tree)
426+
if (isAsync(tree)) {
427+
;
428+
} else {
429+
if (isAwait(tree))
430+
stack.foreach(attachContainsAwait)
431+
else
432+
attachNoAwait(tree)
433+
super.traverse(tree)
434+
}
425435
} finally stack = stack.tail
426436
}
427437
}

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

+20
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,24 @@ class WarningsSpec {
7474
run.compileSources(sourceFile :: Nil)
7575
assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
7676
}
77+
78+
@Test
79+
def ignoreNestedAwaitsInIDE_t1002561() {
80+
// https://www.assembla.com/spaces/scala-ide/tickets/1002561
81+
val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ystop-after:typer ")
82+
val source = """
83+
| class Test {
84+
| def test = {
85+
| import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global
86+
| async {
87+
| 1 + await({def foo = (async(await(async(2)))); foo})
88+
| }
89+
| }
90+
|}
91+
""".stripMargin
92+
val run = new global.Run
93+
val sourceFile = global.newSourceFile(source)
94+
run.compileSources(sourceFile :: Nil)
95+
assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos)
96+
}
7797
}

0 commit comments

Comments
 (0)