8000 Overhaul integration with front end macros · scala/scala@0dc0333 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0dc0333

Browse files
committed
Overhaul integration with front end macros
- The state machine generated by the front end macro must Now implement a small set of methods as a facade over the particular Future/Awaitable/Task type being used. This replaces the AST factory methods in `FutureSystem` - Remove intrinsic implemnentation for scala-async's async macro. Show what it will need to do in a test implementation, `s.t.n.partest.Async` and update all test cases to use this. - Add a heuristic to interpret `@compileTimeOnly` annotated methods to register `await` methods. Defer the check in refchecks for all of these and instead check that no references survive the async phase. - Refactor the test implementations of async front ends to share an interface for the state machine. We don't ship this but it could be copy/pasted into third party integrations. - Add a test integration with Java's CompletableFuture. - Expose a method through macro `Internals` API to let front ends mark a method in the state machine for async translation.
1 parent 5fd609e commit 0dc0333

File tree

108 files changed

+649
-672
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+649
-672
lines changed

build.sbt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,10 @@ val mimaFilterSettings = Seq {
370370
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.removeElement"),
371371
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.addElement"),
372372
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.containsElement"),
373+
374+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.Settings.async"),
375+
376+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.api.Internals#InternalApi.markForAsyncTransform"),
373377
)
374378
}
375379

project/ScalaOptionParser.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ object ScalaOptionParser {
8282
}
8383

8484
// TODO retrieve this data programmatically, ala https://github.com/scala/scala-tool-support/blob/master/bash-completion/src/main/scala/BashCompletion.scala
85-
private def booleanSettingNames = List("-X", "-Xcheckinit", "-Xdev", "-Xdisable-assertions", "-Xexperimental", "-Xfatal-warnings", "-Xfull-lubs", "-Xfuture", "-Xlog-free-terms", "-Xlog-free-types", "-Xlog-implicit-conversions", "-Xlog-implicits", "-Xlog-reflective-calls",
85+
private def booleanSettingNames = List("-X", "-Xasync", "-Xcheckinit", "-Xdev", "-Xdisable-assertions", "-Xexperimental", "-Xfatal-warnings", "-Xfull-lubs", "-Xfuture", "-Xlog-free-terms", "-Xlog-free-types", "-Xlog-implicit-conversions", "-Xlog-implicits", "-Xlog-reflective-calls",
8686
"-Xno-forwarders", "-Xno-patmat-analysis", "-Xno-uescape", "-Xnojline", "-Xprint-pos", "-Xprint-types", "-Xprompt", "-Xresident", "-Xshow-phases", "-Xstrict-inference", "-Xverify", "-Y",
8787
"-Ybreak-cycles", "-Ydebug", "-Ycompact-trees", "-YdisableFlatCpCaching", "-Ydoc-debug",
8888
"-Yide-debug", "-Yinfer-argument-types",

src/compiler/scala/reflect/macros/contexts/Internals.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,8 @@ trait Internals extends scala.tools.nsc.transform.TypingTransformers {
5555
val trans = new HofTypingTransformer(transformer)
5656
trans.atOwner(owner)(trans.transform(tree))
5757
}
58+
override def markForAsyncTransform(owner: Symbol, method: DefDef, awaitSymbol: Symbol, config: Map[String, AnyRef]): DefDef = {
59+
global.async.markForAsyncTransform(owner, method, awaitSymbol, config)
60+
}
5861
}
59-
}
62+
}

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,12 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
374374
getSourceFile(f)
375375
}
376376

377+
override lazy val internal: Internal = new SymbolTableInternal {
378+
override def markForAsyncTransform(owner: Symbol, method: DefDef, awaitSymbol: Symbol, config: Map[String, AnyRef]): DefDef = {
379+
async.markForAsyncTransform(owner, method, awaitSymbol, config)
380+
}
381+
}
382+
377383
lazy val loaders = new {
378384
val global: Global.this.type = Global.this
379385
val platform: Global.this.platform.type = Global.this.platform
@@ -1011,7 +1017,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
10111017
definitions.isDefinitionsInitialized
10121018
&& rootMirror.isMirrorInitialized
10131019
)
1014-
override def isPastTyper = isPast(currentRun.typerPhase)
1020+
override def isPastTyper = globalPhase != null && isPast(currentRun.typerPhase)
10151021
def isPast(phase: Phase) = (
10161022
(curRun ne null)
10171023
&& isGlobalInitialized // defense against init order issues
@@ -1348,6 +1354,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
13481354
def runIsAt(ph: Phase) = globalPhase.id == ph.id
13491355
def runIsAtOptimiz = runIsAt(jvmPhase)
13501356

1357+
firstPhase.iterator.foreach(_.init())
13511358
isDefined = true
13521359

13531360
// ----------- Units and top-level classes and objects --------

src/compiler/scala/tools/nsc/settings/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ trait ScalaSettings extends AbsScalaSettings
113113
* -X "Advanced" settings
114114
*/
115115
val Xhelp = BooleanSetting ("-X", "Print a synopsis of advanced options.")
116+
val async = BooleanSetting ("-Xasync", "Enable the async phase for scala.async.Async.{async,await}.")
116117
val checkInit = BooleanSetting ("-Xcheckinit", "Wrap field accessors to throw an exception on uninitialized access.")
117118
val developer = BooleanSetting ("-Xdev", "Indicates user is a developer - issue warnings about anything which seems amiss")
118119
val noassertions = BooleanSetting ("-Xdisable-assertions", "Generate no assertions or assumptions.") andThen (flag =>

src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,5 +392,3 @@ private[async] trait AnfTransform extends TransformUtils {
392392
}
393393
}
394394
}
395-
396-
object SyntheticBindVal

src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala

Lines changed: 0 additions & 131 deletions
This file was deleted.

src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,56 +13,85 @@
1313
package scala.tools.nsc.transform.async
1414

1515
import scala.collection.mutable
16-
import scala.tools.nsc.transform.async.user.FutureSystem
1716
import scala.tools.nsc.transform.{Transform, TypingTransformers}
17+
import scala.reflect.internal.util.SourceFile
1818

1919
abstract class AsyncPhase extends Transform with TypingTransformers with AnfTransform with AsyncAnalysis with Lifter with LiveVariables {
2020
self =>
2121
import global._
2222

23-
private[async] var currentTransformState: AsyncTransformState[global.type] = _
23+
private[async] var currentTransformState: AsyncTransformState = _
2424
private[async] val asyncNames = new AsyncNames[global.type](global)
2525
protected[async] val tracing = new Tracing
2626

2727
val phaseName: String = "async"
28+
override def enabled: Boolean = settings.async
2829

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
3031

3132
// 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))
3651
}
3752

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-
5053
def newTransformer(unit: CompilationUnit): Transformer = new AsyncTransformer(unit)
5154

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+
5269
// TOOD: figure out how to make the root-level async built-in macro sufficiently configurable:
5370
// replace the ExecutionContext implicit arg with an AsyncContext implicit that also specifies the type of the Future/Awaitable/Node/...?
5471
final class AsyncTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
5572
private lazy val liftableMap = new mutable.AnyRefMap[Symbol, (Symbol, List[Tree])]()
5673

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+
}
5988

6089
// Together, these transforms below target this tree shaps
6190
// {
6291
// class $STATE_MACHINE extends ... {
6392
// def $APPLY_METHOD(....) = {
6493
// ...
65-
// }.updateAttachment(FutureSystemAttachment(...))
94+
// }.updateAttachment(AsyncAttachment(...))
6695
// }
6796
// }
6897
//
@@ -74,16 +103,16 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
74103
case cd: ClassDef if liftableMap.contains(cd.symbol) =>
75104
val (applySym, liftedTrees) = liftableMap.remove(cd.symbol).get
76105
val liftedSyms = liftedTrees.iterator.map(_.symbol).toSet
77-
val cd1 = atOwner(tree.symbol) {
106+
val cd1 = atOwner(cd.symbol) {
78107
deriveClassDef(cd)(impl => {
79108
deriveTemplate(impl)(liftedTrees ::: _)
80109
})
81110
}
82111
assert(localTyper.context.owner == cd.symbol.owner)
83112
new UseFields(localTyper, cd.symbol, applySym, liftedSyms).transform(cd1)
84113

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
87116
val asyncBody = (dd.rhs: @unchecked) match {
88117
case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last).setType(stats.last.tpe)
89118
}
@@ -92,7 +121,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
92121
atOwner(dd, dd.symbol) {
93122
val trSym = dd.vparamss.head.head.symbol
94123
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)
96126
try {
97127
val (newRhs, liftableFields) = asyncTransform(asyncBody)
98128
liftableMap(dd.symbol.owner) = (dd.symbol, liftableFields)
@@ -101,13 +131,13 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
101131
currentTransformState = saved
102132
}
103133
}
341A
104-
case tree => tree
134+
case tree =>
135+
tree
105136
}
106137

107138
private def asyncTransform(asyncBody: Tree): (Tree, List[Tree]) = {
108139
val transformState = currentTransformState
109140
import transformState.applySym
110-
val futureSystemOps = transformState.ops
111141

112142
val asyncPos = asyncBody.pos
113143

@@ -119,7 +149,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran
119149
// Transform to A-normal form:
120150
// - no await calls in qualifiers or arguments,
121151
// - 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))
123153

124154
// The ANF transform re-parents some trees, so the previous traversal to mark ancestors of
125155
// 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
144174
val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler)
145175

146176
// Logging
147-
if (settings.debug.value && shouldLogAtThisPhase)
177+
if ((settings.debug.value && shouldLogAtThisPhase))
148178
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))
151181

152182
cleanupContainsAwaitAttachments(applyBody)
153183

0 commit comments

Comments
 (0)
0