Skip to content

Commit 01b36ea

Browse files
committed
Avoid spurious "illegal await" error in IDE with nesting
The presentation compiler runs with `-Ymacro-expand:discard`, which retains the macro expandee in the typechecked trees, rather than substituting in the expansion. This mode was motivated as a means to keep IDE functionality working (e.g. completion, navigation, refactoring) inside macro applications. However, if one has nested async macro applications, as reported in the IDE ticket: https://www.assembla.com/spaces/scala-ide/tickets/1002561 ... the expansion of the outer async application was reporting await calls enclosed by the inner async application. This change tweaks the traversers used for this analysis to stop whenever it sees an async.
1 parent 7263aaa commit 01b36ea

File tree

7 files changed

+63
-17
lines changed

7 files changed

+63
-17
lines changed

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

Lines changed: 15 additions & 5 deletions
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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 3 additions & 0 deletions
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

Lines changed: 2 additions & 4 deletions
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

Lines changed: 16 additions & 6 deletions
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

Lines changed: 20 additions & 0 deletions
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)