13
13
package scala .tools .nsc .transform .async
14
14
15
15
import scala .collection .mutable
16
- import scala .tools .nsc .transform .async .user .FutureSystem
17
16
import scala .tools .nsc .transform .{Transform , TypingTransformers }
17
+ import scala .reflect .internal .util .SourceFile
18
18
19
19
abstract class AsyncPhase extends Transform with TypingTransformers with AnfTransform with AsyncAnalysis with Lifter with LiveVariables {
20
20
self =>
21
21
import global ._
22
22
23
- private [async] var currentTransformState : AsyncTransformState [global. type ] = _
23
+ private [async] var currentTransformState : AsyncTransformState = _
24
24
private [async] val asyncNames = new AsyncNames [global.type ](global)
25
25
protected [async] val tracing = new Tracing
26
26
27
27
val phaseName : String = " async"
28
+ override def enabled : Boolean = settings.async
28
29
29
- private final class FutureSystemAttachment ( val system : FutureSystem ) extends PlainAttachment
30
+ private final case class AsyncAttachment ( awaitSymbol : Symbol , postAnfTransform : Block => Block , stateDiagram : (( Symbol , Tree ) => Option [ String => Unit ]) ) extends PlainAttachment
30
31
31
32
// Optimization: avoid the transform altogether if there are no async blocks in a unit.
32
- private val units = perRunCaches.newSet[CompilationUnit ]()
33
- final def addFutureSystemAttachment (unit : CompilationUnit , method : Tree , system : FutureSystem ): method.type = {
34
- units += unit
35
- method.updateAttachment(new FutureSystemAttachment (system))
33
+ private val sourceFilesToTransform = perRunCaches.newSet[SourceFile ]()
34
+ private val awaits : mutable.Set [Symbol ] = perRunCaches.newSet[Symbol ]()
35
+
36
+ /**
37
+ * Mark the given method as requiring an async transform.
38
+ */
39
+ final def markForAsyncTransform (owner : Symbol , method : DefDef , awaitMethod : Symbol ,
40
+ config : Map [String , AnyRef ]): DefDef = {
41
+ val pos = owner.pos
42
+ if (! settings.async)
43
+ reporter.warning(pos, s " ${settings.async.name} must be enabled for async transformation. " )
44
+ sourceFilesToTransform += pos.source
45
+ val postAnfTransform = config.getOrElse(" postAnfTransform" , (x : Block ) => x).asInstanceOf [Block => Block ]
46
+ val stateDiagram = config.getOrElse(" stateDiagram" , (sym : Symbol , tree : Tree ) => None ).asInstanceOf [(Symbol , Tree ) => Option [String => Unit ]]
47
+ method.updateAttachment(new AsyncAttachment (awaitMethod, postAnfTransform, stateDiagram))
48
+ deriveDefDef(method) { rhs =>
49
+ Block (rhs.updateAttachment(SuppressPureExpressionWarning ), Literal (Constant (())))
50
+ }.updateAttachment(ChangeOwnerAttachment (owner))
36
51
}
37
52
38
- protected object macroExpansion extends AsyncEarlyExpansion {
39
- val global : self.global.type = self.global
40
- }
41
-
42
- import treeInfo .Applied
43
- def fastTrackEntry : (Symbol , PartialFunction [Applied , scala.reflect.macros.contexts.Context { val universe : self.global.type } => Tree ]) =
44
- (currentRun.runDefinitions.Async_async , {
45
- // def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro ???
46
- case app@ Applied (_, _, List (asyncBody :: Nil , execContext :: Nil )) =>
47
- c => c.global.async.macroExpansion.apply(c.callsiteTyper, asyncBody, execContext, asyncBody.tpe)
48
- })
49
-
50
53
def newTransformer (unit : CompilationUnit ): Transformer = new AsyncTransformer (unit)
51
54
55
+ private def compileTimeOnlyPrefix : String = " [async] "
56
+
57
+ /** Should refchecks defer reporting `@compileTimeOnly` errors for `sym` and instead let this phase issue the warning
58
+ * if they survive the async tranform? */
59
+ private [scala] def deferCompileTimeOnlyError (sym : Symbol ): Boolean = settings.async && {
60
+ awaits.contains(sym) || {
61
+ val msg = sym.compileTimeOnlyMessage.getOrElse(" " )
62
+ val shouldDefer =
63
+ msg.startsWith(compileTimeOnlyPrefix) || (sym.name == nme.await) && msg.contains(" must be enclosed" ) && sym.owner.info.member(nme.async) != NoSymbol
64
+ if (shouldDefer) awaits += sym
65
+ shouldDefer
66
+ }
67
+ }
68
+
52
69
// TOOD: figure out how to make the root-level async built-in macro sufficiently configurable:
53
70
// replace the ExecutionContext implicit arg with an AsyncContext implicit that also specifies the type of the Future/Awaitable/Node/...?
54
71
final class AsyncTransformer (unit : CompilationUnit ) extends TypingTransformer (unit) {
55
72
private lazy val liftableMap = new mutable.AnyRefMap [Symbol , (Symbol , List [Tree ])]()
56
73
57
- override def transformUnit (unit : CompilationUnit ): Unit =
58
- if (units.contains(unit)) super .transformUnit(unit)
74
+ override def transformUnit (unit : CompilationUnit ): Unit = {
75
+ if (settings.async) {
76
+ if (sourceFilesToTransform.contains(unit.source)) super .transformUnit(unit)
77
+ if (awaits.exists(_.isInitialized)) {
78
+ unit.body.foreach {
79
+ case tree : RefTree if tree.symbol != null && awaits.contains(tree.symbol) =>
80
+ val sym = tree.symbol
81
+ val msg = sym.compileTimeOnlyMessage.getOrElse(s " ` ${sym.decodedName}` must be enclosed in an `async` block " ).stripPrefix(compileTimeOnlyPrefix)
82
+ global.reporter.error(tree.pos, msg)
83
+ case _ =>
84
+ }
85
+ }
86
+ }
87
+ }
59
88
60
89
// Together, these transforms below target this tree shaps
61
90
// {
62
91
// class $STATE_MACHINE extends ... {
63
92
// def $APPLY_METHOD(....) = {
64
93
// ...
65
- // }.updateAttachment(FutureSystemAttachment (...))
94
+ // }.updateAttachment(AsyncAttachment (...))
66
95
// }
67
96
// }
68
97
//
@@ -74,16 +103,16 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
74
103
case cd : ClassDef if liftableMap.contains(cd.symbol) =>
75
104
val (applySym, liftedTrees) = liftableMap.remove(cd.symbol).get
76
105
val liftedSyms = liftedTrees.iterator.map(_.symbol).toSet
77
- val cd1 = atOwner(tree .symbol) {
106
+ val cd1 = atOwner(cd .symbol) {
78
107
deriveClassDef(cd)(impl => {
79
108
deriveTemplate(impl)(liftedTrees ::: _)
80
109
})
81
110
}
82
111
assert(localTyper.context.owner == cd.symbol.owner)
83
112
new UseFields (localTyper, cd.symbol, applySym, liftedSyms).transform(cd1)
84
113
85
- case dd : DefDef if tree .hasAttachment[FutureSystemAttachment ] =>
86
- val futureSystem = tree .getAndRemoveAttachment[FutureSystemAttachment ].get.system
114
+ case dd : DefDef if dd .hasAttachment[AsyncAttachment ] =>
115
+ val asyncAttachment = dd .getAndRemoveAttachment[AsyncAttachment ].get
87
116
val asyncBody = (dd.rhs: @ unchecked) match {
88
117
case blk@ Block (stats, Literal (Constant (()))) => treeCopy.Block (blk, stats.init, stats.last).setType(stats.last.tpe)
89
118
}
@@ -92,7 +121,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
92
121
atOwner(dd, dd.symbol) {
93
122
val trSym = dd.vparamss.head.head.symbol
94
123
val saved = currentTransformState
95
- currentTransformState = new AsyncTransformState [global.type ](global, futureSystem, this , trSym, asyncBody.tpe)
124
+ currentTransformState = new AsyncTransformState (asyncAttachment.awaitSymbol,
125
+ asyncAttachment.postAnfTransform, asyncAttachment.stateDiagram, this , trSym, asyncBody.tpe)
96
126
try {
97
127
val (newRhs, liftableFields) = asyncTransform(asyncBody)
98
128
liftableMap(dd.symbol.owner) = (dd.symbol, liftableFields)
@@ -101,13 +131,13 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
101
131
currentTransformState = saved
102
132
}
103
133
}
341A
104
- case tree => tree
134
+ case tree =>
135
+ tree
105
136
}
106
137
107
138
private def asyncTransform (asyncBody : Tree ): (Tree , List [Tree ]) = {
108
139
val transformState = currentTransformState
109
140
import transformState .applySym
110
- val futureSystemOps = transformState.ops
111
141
112
142
val asyncPos = asyncBody.pos
113
143
@@ -119,7 +149,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
119
149
// Transform to A-normal form:
120
150
// - no await calls in qualifiers or arguments,
121
151
// - if/match only used in statement position.
122
- val anfTree : Block = futureSystemOps .postAnfTransform(new AnfTransformer (localTyper).apply(asyncBody))
152
+ val anfTree : Block = transformState .postAnfTransform(new AnfTransformer (localTyper).apply(asyncBody))
123
153
124
154
// The ANF transform re-parents some trees, so the previous traversal to mark ancestors of
125
155
// await is no longer reliable. Clear previous results and run it again for use in the `buildAsyncBlock`.
@@ -144,10 +174,10 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
144
174
val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler)
145
175
146
176
// Logging
147
- if (settings.debug.value && shouldLogAtThisPhase)
177
+ if (( settings.debug.value && shouldLogAtThisPhase) )
148
178
logDiagnostics(anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString))
149
- // Offer the future system a change to produce the .dot diagram
150
- futureSystemOps.dot (applySym, asyncBody).foreach(f => f(asyncBlock.toDot))
179
+ // Offer async frontends a change to produce the .dot diagram
180
+ transformState.dotDiagram (applySym, asyncBody).foreach(f => f(asyncBlock.toDot))
151
181
152
182
cleanupContainsAwaitAttachments(applyBody)
153
183
0 commit comments