diff --git a/project/ScalaOptionParser.scala b/project/ScalaOptionParser.scala index a5cbb35dde40..46305595256f 100644 --- a/project/ScalaOptionParser.scala +++ b/project/ScalaOptionParser.scala @@ -82,7 +82,7 @@ object ScalaOptionParser { } // TODO retrieve this data programmatically, ala https://github.com/scala/scala-tool-support/blob/master/bash-completion/src/main/scala/BashCompletion.scala - 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", + 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", "-Xno-forwarders", "-Xno-patmat-analysis", "-Xno-uescape", "-Xnojline", "-Xprint-pos", "-Xprint-types", "-Xprompt", "-Xresident", "-Xshow-phases", "-Xstrict-inference", "-Xverify", "-Y", "-Ybreak-cycles", "-Ydebug", "-Ycompact-trees", "-YdisableFlatCpCaching", "-Ydoc-debug", "-Yide-debug", "-Yinfer-argument-types", diff --git a/src/compiler/scala/reflect/macros/contexts/Internals.scala b/src/compiler/scala/reflect/macros/contexts/Internals.scala index d4713f540507..2601273568f2 100644 --- a/src/compiler/scala/reflect/macros/contexts/Internals.scala +++ b/src/compiler/scala/reflect/macros/contexts/Internals.scala @@ -55,5 +55,8 @@ trait Internals extends scala.tools.nsc.transform.TypingTransformers { val trans = new HofTypingTransformer(transformer) trans.atOwner(owner)(trans.transform(tree)) } + override def markForAsyncTransform(owner: Symbol, method: DefDef, awaitSymbol: Symbol, config: Map[String, AnyRef]): DefDef = { + global.async.markForAsyncTransform(owner, method, awaitSymbol, config) + } } -} \ No newline at end of file +} diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index ba841dbc8a43..42aae785c269 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -374,6 +374,12 @@ class Global(var currentSettings: Settings, reporter0: Reporter) getSourceFile(f) } + override lazy val internal: Internal = new SymbolTableInternal { + override def markForAsyncTransform(owner: Symbol, method: DefDef, awaitSymbol: Symbol, config: Map[String, AnyRef]): DefDef = { + async.markForAsyncTransform(owner, method, awaitSymbol, config) + } + } + lazy val loaders = new { val global: Global.this.type = Global.this val platform: Global.this.platform.type = Global.this.platform @@ -1011,7 +1017,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) definitions.isDefinitionsInitialized && rootMirror.isMirrorInitialized ) - override def isPastTyper = isPast(currentRun.typerPhase) + override def isPastTyper = globalPhase != null && isPast(currentRun.typerPhase) def isPast(phase: Phase) = ( (curRun ne null) && isGlobalInitialized // defense against init order issues @@ -1348,6 +1354,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) def runIsAt(ph: Phase) = globalPhase.id == ph.id def runIsAtOptimiz = runIsAt(jvmPhase) + firstPhase.iterator.foreach(_.init()) isDefined = true // ----------- Units and top-level classes and objects -------- diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index c2d71ffed565..70cf32f60932 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -113,6 +113,7 @@ trait ScalaSettings extends AbsScalaSettings * -X "Advanced" settings */ val Xhelp = BooleanSetting ("-X", "Print a synopsis of advanced options.") + val async = BooleanSetting ("-Xasync", "Enable the async phase for scala.async.Async.{async,await}.") val checkInit = BooleanSetting ("-Xcheckinit", "Wrap field accessors to throw an exception on uninitialized access.") val developer = BooleanSetting ("-Xdev", "Indicates user is a developer - issue warnings about anything which seems amiss") val noassertions = BooleanSetting ("-Xdisable-assertions", "Generate no assertions or assumptions.") andThen (flag => diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 0e8222a19f1b..5921d2d26fa5 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -392,5 +392,3 @@ private[async] trait AnfTransform extends TransformUtils { } } } - -object SyntheticBindVal diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala deleted file mode 100644 index d61639b61d5a..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.tools.nsc.transform.async - -import user.{FutureSystem, ScalaConcurrentFutureSystem} -import scala.reflect.internal.Flags -import scala.tools.nsc.transform.TypingTransformers - -// NOTE: this part runs during typer to wrap argument to the `async` macro -// in a class. This will later serve as storage for state of the state -// machine, and before that will force the compiler to use outer pointers -// for references from the async block to members of its owning or other -// enclosing classes. -abstract class AsyncEarlyExpansion extends TypingTransformers { - import global._ - - private lazy val Promise_class = rootMirror.requiredClass[scala.concurrent.Promise[_]] - - /** Perform async macro expansion during typers to a block that creates the state machine class, - * along with supporting definitions, but without the ANF/Async expansion. - * - * The full expansion of the actual state machine logic (anf & async) is performed by asyncTransform after erasure. - * Until then, the state machine's apply method just has the original async macro invocation. - * - * The goal is to balance compiler performance by delaying tree explosion (due to anf and state machine mechanics) and - * complexity arising from deftree synthesis in later phases, which would require - * retro-actively running erasure (bridges/types) and explicitouter (add outer arg to state machine ctor) - * on the synthetic def trees. - * - * Synthesizes: - { - val execContext0$async: scala.concurrent.ExecutionContext = `execContext`; - class stateMachine$async extends extends scala.runtime.AbstractFunction1[scala.util.Try[Int],Unit] { - def (): stateMachine$async = { - stateMachine$async.super.(); - () - }; - private[this] var state$async: Int = 0; - private def state$async: Int = stateMachine$async.this.state$async; - private def state$async_=(x$1: Int): Unit = stateMachine$async.this.state$async = x$1; - private[this] val result$async: scala.concurrent.Promise[`resultType`] = Promise.apply[`resultType`](); - def result$async: scala.concurrent.Promise[`resultType`] = stateMachine$async.this.result$async; - private[this] val execContext$async: scala.concurrent.ExecutionContext = execContext0$async; - def execContext$async: scala.concurrent.ExecutionContext = stateMachine$async.this.execContext$async; - def apply(tr$async: scala.util.Try[`resultType`]): Unit = { - `asyncBody` - () - } - }; - val stateMachine$async: stateMachine$async = new stateMachine$async(); - scala.concurrent.Future.unit.onComplete[Unit](stateMachine$async.asInstanceOf[scala.util.Try[Unit] => Unit])(stateMachine$async.execContext$async); - stateMachine$async.result$async.future - } - */ - def apply(callsiteTyper: analyzer.Typer, asyncBody: Tree, execContext: Tree, resultType: Type) = { - val futureSystem: FutureSystem = ScalaConcurrentFutureSystem - val futureSystemOps: futureSystem.Ops[global.type] = futureSystem.mkOps(global) - - val tryResult = futureSystemOps.tryType(resultType) - - val execContextTempVal = - ValDef(NoMods, nme.execContextTemp, TypeTree(execContext.tpe), execContext) - - val stateMachine: ClassDef = { - val parents = { - val customParents = futureSystemOps.stateMachineClassParents - // prefer extending a class to reduce the class file size of the state machine. - // ... unless a custom future system already extends some class - val useClass = customParents.forall(_.typeSymbol.isTrait) - - val fun1Tpe = - if (useClass) definitions.abstractFunctionType(tryResult :: Nil, definitions.UnitTpe) - else definitions.functionType(tryResult :: Nil, definitions.UnitTpe) - - val funParents = List(fun1Tpe) - (customParents ::: funParents).map(TypeTree(_)) - } - - val stateVar = - ValDef(Modifiers(Flags.MUTABLE | Flags.PRIVATE), nme.state, TypeTree(definitions.IntTpe), Literal(Constant(StateAssigner.Initial))) - - def createProm(resultType: Type): Tree = - Apply(TypeApply(gen.mkAttributedStableRef(Promise_class.companionModule), TypeTree(resultType) :: Nil), Nil) - - val resultVal = - ValDef(NoMods, nme.result, TypeTree(appliedType(Promise_class, resultType)), createProm(resultType)) - - val execContextVal = - ValDef(NoMods, nme.execContext, TypeTree(execContext.tpe), Ident(nme.execContextTemp)) - - val applyFSM: DefDef = { - val applyVParamss = List(List(ValDef(Modifiers(Flags.PARAM), nme.tr, TypeTree(tryResult), EmptyTree))) - DefDef(NoMods, nme.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), Block( - asyncBody.updateAttachment(SuppressPureExpressionWarning), Literal(Constant(()))) - ).updateAttachment(ChangeOwnerAttachment(callsiteTyper.context.owner)) - } - async.addFutureSystemAttachment(callsiteTyper.context.unit, applyFSM, futureSystem) - - atPos(asyncBody.pos)(ClassDef(NoMods, tpnme.stateMachine, Nil, - gen.mkTemplate(parents, noSelfType, NoMods, List(Nil), - List(stateVar, resultVal, execContextVal, applyFSM)))) - } - - val newStateMachine = ValDef(NoMods, nme.stateMachine, TypeTree(), Apply(Select(New(Ident(tpnme.stateMachine)), nme.CONSTRUCTOR), Nil)) - def execContextSelect = Select(Ident(nme.stateMachine), nme.execContext) - - // Use KeptPromise.onComplete to get the ball rolling. - val futureUnit = futureSystemOps.futureUnit(execContextSelect) - - // stateMachine.asInstanceOf[Function1[Try[Unit], Unit] - // This cast is safe because we know that `def apply` does not consult its argument when `state == 0`. - val castStateMachine = gen.mkCast(Ident(nme.stateMachine), - definitions.functionType(futureSystemOps.tryType(definitions.UnitTpe) :: Nil, definitions.UnitTpe)) - - val stateMachineToFuture = futureSystemOps.onComplete(futureUnit, castStateMachine, execContextSelect) - - val promToFuture = Select(Select(Ident(nme.stateMachine), nme.result), nme.future) - - Block(List(execContextTempVal, stateMachine, newStateMachine, stateMachineToFuture), promToFuture) - } -} diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 97e6eeadaed8..49b77bf5d78e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -13,56 +13,87 @@ package scala.tools.nsc.transform.async import scala.collection.mutable -import scala.tools.nsc.transform.async.user.FutureSystem import scala.tools.nsc.transform.{Transform, TypingTransformers} +import scala.reflect.internal.Flags +import scala.reflect.internal.util.SourceFile +import scala.reflect.io.AbstractFile abstract class AsyncPhase extends Transform with TypingTransformers with AnfTransform with AsyncAnalysis with Lifter with LiveVariables { self => import global._ - private[async] var currentTransformState: AsyncTransformState[global.type] = _ + private[async] var currentTransformState: AsyncTransformState = _ private[async] val asyncNames = new AsyncNames[global.type](global) protected[async] val tracing = new Tracing val phaseName: String = "async" + override def enabled: Boolean = settings.async - private final class FutureSystemAttachment(val system: FutureSystem) extends PlainAttachment + private final case class AsyncAttachment(awaitSymbol: Symbol, postAnfTransform: Block => Block, stateDiagram: ((Symbol, Tree) => Option[String => Unit])) extends PlainAttachment // Optimization: avoid the transform altogether if there are no async blocks in a unit. - private val units = perRunCaches.newSet[CompilationUnit]() - final def addFutureSystemAttachment(unit: CompilationUnit, method: Tree, system: FutureSystem): method.type = { - units += unit - method.updateAttachment(new FutureSystemAttachment(system)) + private val sourceFilesToTransform = perRunCaches.newSet[SourceFile]() + private val awaits: mutable.Set[Symbol] = perRunCaches.newSet[Symbol]() + + /** + * Mark the given method as requiring an async transform. + */ + final def markForAsyncTransform(owner: Symbol, method: DefDef, awaitMethod: Symbol, + config: Map[String, AnyRef]): DefDef = { + val pos = owner.pos + if (!settings.async) + reporter.warning(pos, s"${settings.async.name} must be enabled for async transformation.") + sourceFilesToTransform += pos.source + val postAnfTransform = config.getOrElse("postAnfTransform", (x: Block) => x).asInstanceOf[Block => Block] + val stateDiagram = config.getOrElse("stateDiagram", (sym: Symbol, tree: Tree) => None).asInstanceOf[(Symbol, Tree) => Option[String => Unit]] + method.updateAttachment(new AsyncAttachment(awaitMethod, postAnfTransform, stateDiagram)) + deriveDefDef(method) { rhs => + Block(rhs.updateAttachment(SuppressPureExpressionWarning), Literal(Constant(()))) + }.updateAttachment(ChangeOwnerAttachment(owner)) } - protected object macroExpansion extends AsyncEarlyExpansion { - val global: self.global.type = self.global - } - - import treeInfo.Applied - def fastTrackEntry: (Symbol, PartialFunction[Applied, scala.reflect.macros.contexts.Context { val universe: self.global.type } => Tree]) = - (currentRun.runDefinitions.Async_async, { - // def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro ??? - case app@Applied(_, _, List(asyncBody :: Nil, execContext :: Nil)) => - c => c.global.async.macroExpansion.apply(c.callsiteTyper, asyncBody, execContext, asyncBody.tpe) - }) - def newTransformer(unit: CompilationUnit): Transformer = new AsyncTransformer(unit) + private def compileTimeOnlyPrefix: String = "[async] " + + /** Should refchecks defer reporting `@compileTimeOnly` errors for `sym` and instead let this phase issue the warning + * if they survive the async tranform? */ + private[scala] def deferCompileTimeOnlyError(sym: Symbol): Boolean = settings.async && { + awaits.contains(sym) || { + val msg = sym.compileTimeOnlyMessage.getOrElse("") + val shouldDefer = + msg.startsWith(compileTimeOnlyPrefix) || (sym.name == nme.await) && msg.contains("must be enclosed") && sym.owner.info.member(nme.async) != NoSymbol + if (shouldDefer) awaits += sym + shouldDefer + } + } + // TOOD: figure out how to make the root-level async built-in macro sufficiently configurable: // replace the ExecutionContext implicit arg with an AsyncContext implicit that also specifies the type of the Future/Awaitable/Node/...? final class AsyncTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { private lazy val liftableMap = new mutable.AnyRefMap[Symbol, (Symbol, List[Tree])]() - override def transformUnit(unit: CompilationUnit): Unit = - if (units.contains(unit)) super.transformUnit(unit) + override def transformUnit(unit: CompilationUnit): Unit = { + if (settings.async) { + if (sourceFilesToTransform.contains(unit.source)) super.transformUnit(unit) + if (awaits.exists(_.isInitialized)) { + unit.body.foreach { + case tree: RefTree if tree.symbol != null && awaits.contains(tree.symbol) => + val sym = tree.symbol + val msg = sym.compileTimeOnlyMessage.getOrElse(s"`${sym.decodedName}` must be enclosed in an `async` block").stripPrefix(compileTimeOnlyPrefix) + global.reporter.error(tree.pos, msg) + case _ => + } + } + } + } // Together, these transforms below target this tree shaps // { // class $STATE_MACHINE extends ... { // def $APPLY_METHOD(....) = { // ... - // }.updateAttachment(FutureSystemAttachment(...)) + // }.updateAttachment(AsyncAttachment(...)) // } // } // @@ -74,7 +105,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran case cd: ClassDef if liftableMap.contains(cd.symbol) => val (applySym, liftedTrees) = liftableMap.remove(cd.symbol).get val liftedSyms = liftedTrees.iterator.map(_.symbol).toSet - val cd1 = atOwner(tree.symbol) { + val cd1 = atOwner(cd.symbol) { deriveClassDef(cd)(impl => { deriveTemplate(impl)(liftedTrees ::: _) }) @@ -82,8 +113,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran assert(localTyper.context.owner == cd.symbol.owner) new UseFields(localTyper, cd.symbol, applySym, liftedSyms).transform(cd1) - case dd: DefDef if tree.hasAttachment[FutureSystemAttachment] => - val futureSystem = tree.getAndRemoveAttachment[FutureSystemAttachment].get.system + case dd: DefDef if dd.hasAttachment[AsyncAttachment] => + val asyncAttachment = dd.getAndRemoveAttachment[AsyncAttachment].get val asyncBody = (dd.rhs: @unchecked) match { case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last).setType(stats.last.tpe) } @@ -92,7 +123,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran atOwner(dd, dd.symbol) { val trSym = dd.vparamss.head.head.symbol val saved = currentTransformState - currentTransformState = new AsyncTransformState[global.type](global, futureSystem, this, trSym, asyncBody.tpe) + currentTransformState = new AsyncTransformState(asyncAttachment.awaitSymbol, + asyncAttachment.postAnfTransform, asyncAttachment.stateDiagram, this, trSym, asyncBody.tpe) try { val (newRhs, liftableFields) = asyncTransform(asyncBody) liftableMap(dd.symbol.owner) = (dd.symbol, liftableFields) @@ -101,13 +133,13 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran currentTransformState = saved } } - case tree => tree + case tree => + tree } private def asyncTransform(asyncBody: Tree): (Tree, List[Tree]) = { val transformState = currentTransformState import transformState.applySym - val futureSystemOps = transformState.ops val asyncPos = asyncBody.pos @@ -119,7 +151,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran // Transform to A-normal form: // - no await calls in qualifiers or arguments, // - if/match only used in statement position. - val anfTree: Block = futureSystemOps.postAnfTransform(new AnfTransformer(localTyper).apply(asyncBody)) + val anfTree: Block = transformState.postAnfTransform(new AnfTransformer(localTyper).apply(asyncBody)) // The ANF transform re-parents some trees, so the previous traversal to mark ancestors of // await is no longer reliable. Clear previous results and run it again for use in the `buildAsyncBlock`. @@ -144,10 +176,10 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) // Logging - if (settings.debug.value && shouldLogAtThisPhase) + if ((settings.debug.value && shouldLogAtThisPhase)) logDiagnostics(anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) - // Offer the future system a change to produce the .dot diagram - futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) + // Offer async frontends a change to produce the .dot diagram + transformState.dotDiagram(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) cleanupContainsAwaitAttachments(applyBody) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala deleted file mode 100644 index ee9cdcbb2b21..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.tools.nsc.transform.async - -import user.FutureSystem -import scala.tools.nsc.Global -import scala.tools.nsc.transform.TypingTransformers - -class AsyncTransformState[U <: Global with Singleton](val symbolTable: U, val futureSystem: FutureSystem, - val typingTransformer: TypingTransformers#TypingTransformer, - val applyTrParam: U#Symbol, - val asyncType: U#Type) { - import symbolTable._ - val ops: futureSystem.Ops[symbolTable.type] = futureSystem.mkOps(symbolTable) - - val localTyper: symbolTable.analyzer.Typer = typingTransformer.localTyper.asInstanceOf[symbolTable.analyzer.Typer] - val stateAssigner = new StateAssigner - val labelDefStates = collection.mutable.Map[symbolTable.Symbol, Int]() - - lazy val applyTr: Symbol = applyTrParam.asInstanceOf[symbolTable.Symbol] - lazy val applySym: Symbol = applyTr.owner - lazy val stateMachineClass: Symbol = applySym.owner - lazy val stateGetter: Symbol = stateMachineMember(nme.state) - lazy val stateSetter: Symbol = stateGetter.setterIn(stateGetter.owner) - lazy val whileLabel: Symbol = applySym.newLabel(nme.WHILE_PREFIX).setInfo(MethodType(Nil, definitions.UnitTpe)) - - def stateMachineMember(name: TermName): Symbol = - stateMachineClass.info.member(name) - def memberRef(name: TermName): Tree = - gen.mkAttributedRef(stateMachineClass.typeConstructor, stateMachineMember(name)) - def memberRef(sym: Symbol): Tree = - gen.mkAttributedRef(stateMachineClass.typeConstructor, sym) - def selectResult: Tree = Apply(memberRef(nme.result), Nil) -} diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala new file mode 100644 index 000000000000..1721f5df1d9c --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala @@ -0,0 +1,57 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.tools.nsc.transform.async + +import scala.tools.nsc.Global +import scala.tools.nsc.transform.TypingTransformers + +trait AsyncTransformStates extends TypingTransformers { + private[async] def currentTransformState: AsyncTransformState + + val global: Global + import global._ + + class AsyncTransformState(val awaitSymbol: Symbol, + val postAnfTransform: Block => Block, + val dotDiagram: (Symbol, Tree) => Option[String => Unit], + val typingTransformer: TypingTransformer, + val applyTrParam: Symbol, + val asyncType: Type) { + val localTyper: analyzer.Typer = typingTransformer.localTyper + val stateAssigner = new StateAssigner + val labelDefStates = collection.mutable.Map[Symbol, Int]() + + lazy val Async_await: Symbol = awaitSymbol + + lazy val applyTr: Symbol = applyTrParam + lazy val applySym: Symbol = applyTr.owner + lazy val stateMachineClass: Symbol = applySym.owner + lazy val stateGetter: Symbol = stateMachineMember(nme.state) + lazy val stateSetter: Symbol = stateGetter.setterIn(stateGetter.owner) + lazy val stateOnComplete: Symbol = stateMachineMember(TermName("onComplete")) + lazy val stateCompleteSuccess: Symbol = stateMachineMember(TermName("completeSuccess")) + lazy val stateCompleteFailure: Symbol = stateMachineMember(TermName("completeFailure")) + lazy val stateGetCompleted: Symbol = stateMachineMember(TermName("getCompleted")) + lazy val stateTryGet: Symbol = stateMachineMember(TermName("tryGet")) + lazy val whileLabel: Symbol = applySym.newLabel(nme.WHILE_PREFIX).setInfo(MethodType(Nil, definitions.UnitTpe)) + + def stateMachineMember(name: TermName): Symbol = + stateMachineClass.info.member(name) + def memberRef(name: TermName): Tree = + gen.mkAttributedRef(stateMachineClass.typeConstructor, stateMachineMember(name)) + def memberRef(sym: Symbol): Tree = + gen.mkAttributedRef(stateMachineClass.typeConstructor, sym) + def selectResult: Tree = Apply(memberRef(nme.result), Nil) + } + +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 6923aea24ddc..ed40de66f0ff 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -192,7 +192,7 @@ trait ExprBuilder extends TransformUtils { val transition = StateTransitionStyle.UpdateAndAwait(arg.changeOwner(vd.symbol, vd.symbol.owner)) buildStateAndOpenNextState(afterAwaitState, style = transition) - stateBuilder.stats ++= resumeTree(vd) + stateBuilder.stats += resumeTree(vd) case If(cond, thenp, elsep) if containsAwait(stat) => // Emit a modified `If` in this state with each branch incorprating the @@ -315,8 +315,13 @@ trait ExprBuilder extends TransformUtils { stateBuilder = new AsyncStateBuilder(nextState, this) } - private def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree)) - global.reporter.error(tree.pos, "await must not be used in this position") + private def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree)) { + tree.foreach { + case tree: RefTree if isAwait(tree) => + global.reporter.error(tree.pos, "await must not be used in this position") + case _ => + } + } /** Copy these states into the current block builder's async stats updating the open state builder's * next states @@ -415,8 +420,6 @@ trait ExprBuilder extends TransformUtils { * Builds the definition of the `apply(tr: Try)` method. */ def onCompleteHandler: Tree = { - val futureSystemOps = currentTransformState.ops - val transformState = currentTransformState def stateMemberRef = gen.mkApplyIfNeeded(transformState.memberRef(transformState.stateGetter)) val throww = Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), IllegalStateExceptionClass_NEW_String), List(gen.mkMethodCall(currentRun.runDefinitions.String_valueOf_Int, stateMemberRef :: Nil)))) @@ -428,21 +431,15 @@ trait ExprBuilder extends TransformUtils { val body1 = compactStates(body.asInstanceOf[Match]) - val stateMatch = maybeTry(currentTransformState.futureSystem.emitTryCatch)( + val stateMatch = Try( body1, List( CaseDef( - Bind(nme.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), - EmptyTree, { - val branchTrue = { - val t = Ident(nme.t) - val complete = futureSystemOps.completeProm[AnyRef]( - transformState.selectResult, futureSystemOps.tryyFailure[AnyRef](t)) - Block(toStats(complete), Return(literalUnit)) - } - If(Apply(Ident(NonFatalClass), List(Ident(nme.t))), branchTrue, Throw(Ident(nme.t))) - branchTrue - })), EmptyTree) + Bind(nme.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), + EmptyTree, + Block(Apply(currentTransformState.memberRef(currentTransformState.stateCompleteFailure), Ident(nme.t) :: Nil) :: Nil, Return(literalUnit)) + ) + ), EmptyTree) typed(LabelDef(transformState.whileLabel, Nil, Block(stateMatch :: Nil, Apply(Ident(transformState.whileLabel), Nil)))) } @@ -511,31 +508,25 @@ trait ExprBuilder extends TransformUtils { } } } - // Resume execution by extracting the successful value and assigining it to the `awaitable.resultValDef` - private def resumeTree(awaitableResult: ValDef): List[Tree] = { - val futureSystemOps = currentTransformState.ops + private def resumeTree(awaitableResult: ValDef): Tree = { def tryyReference = gen.mkAttributedIdent(currentTransformState.applyTrParam) - val assignTryGet = Assign(gen.mkAttributedIdent(awaitableResult.symbol), futureSystemOps.tryyGet[Any](tryyReference)) - - val vd = deriveValDef(awaitableResult)(_ => gen.mkZero(awaitableResult.symbol.info)) - vd.symbol.setFlag(Flag.MUTABLE) - val assignOrReturn = if (currentTransformState.futureSystem.emitTryCatch) { - If(futureSystemOps.tryyIsFailure(tryyReference), - Block(toStats(futureSystemOps.completeProm[AnyRef](currentTransformState.selectResult, tryyReference)), - Return(literalBoxedUnit).setSymbol(currentTransformState.applySym)), - assignTryGet - ) - } else { - assignTryGet + deriveValDef(awaitableResult) { _ => + val temp = awaitableResult.symbol.newTermSymbol(TermName("tryGetResult$async")).setInfo(definitions.ObjectTpe) + val tempVd = ValDef(temp, gen.mkMethodCall(currentTransformState.memberRef(currentTransformState.stateTryGet), tryyReference :: Nil)) + typed(Block( + tempVd :: Nil, + If(Apply(gen.mkAttributedSelect(gen.mkAttributedThis(currentTransformState.stateMachineClass), definitions.Any_==), gen.mkAttributedIdent(temp) :: Nil), + Return(literalUnit), + gen.mkCast(gen.mkAttributedIdent(temp), tempVd.symbol.info) + ) + )) } - vd :: typed(assignOrReturn) :: Nil } - // Comlete the Promise in the `result` field with the final sucessful result of this async block. + // Comlete the Promise in the `result` field with the final successful result of this async block. private def completeSuccess(expr: Tree): Tree = { - val futureSystemOps = currentTransformState.ops - typed(futureSystemOps.completeWithSuccess(currentTransformState.selectResult, expr)) + typed(Apply(currentTransformState.memberRef(currentTransformState.stateCompleteSuccess), expr :: Nil)) } /** What trailing statements should be added to the code for this state to transition to the nest state? */ @@ -548,7 +539,8 @@ trait ExprBuilder extends TransformUtils { val tree = if (printStateUpdates) { Block( callSetter :: Nil, - gen.mkMethodCall(definitions.PredefModule.info.member(TermName("println")), currentTransformState.localTyper.typed(gen.mkApplyIfNeeded(transformState.memberRef(transformState.stateGetter)), definitions.ObjectTpe) :: Nil) + gen.mkMethodCall(definitions.PredefModule.info.member(TermName("println")), + currentTransformState.localTyper.typed(gen.mkApplyIfNeeded(transformState.memberRef(transformState.stateGetter)), definitions.ObjectTpe) :: Nil) ) } else callSetter @@ -572,28 +564,33 @@ trait ExprBuilder extends TransformUtils { case class UpdateAndAwait(awaitable: Tree) extends StateTransitionStyle { def trees(nextState: Int, stateSet: StateSet): List[Tree] = { stateSet += nextState + val transformState = currentTransformState // Suspend execution by calling `onComplete` with the state machine itself as a callback. // - // If the future is already completed, and the future system allows it, execution will continue - // synchronously. - val futureSystemOps = currentTransformState.ops - val fun = This(tpnme.EMPTY) - val transformState = currentTransformState - if (futureSystemOps.continueCompletedFutureOnSameThread) { + // If the state machine contains a member `getCompleted`, this first be called to see if + // an already-completed result is avaialble. If so, execution will continue on this thread + // (_without_ consuming an extra stack frome!) + + def callOnComplete(fut: Tree): Tree = + Apply(Select(This(currentTransformState.stateMachineClass), transformState.stateOnComplete), fut :: Nil) + + val runCompletedOnSameThread = transformState.stateGetCompleted != NoSymbol + if (runCompletedOnSameThread) { val tempAwaitableSym = transformState.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.tpe) val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable) - val initTempCompleted = Assign(gen.mkAttributedIdent(transformState.applyTrParam), futureSystemOps.getCompleted[Any](gen.mkAttributedIdent(tempAwaitableSym))) - val null_ne = Select(Literal(Constant(null)).setType(definitions.NullTpe), TermName("ne")) - val callOnComplete = futureSystemOps.onComplete[Any, Unit](gen.mkAttributedIdent(tempAwaitableSym), fun, Ident(nme.execContext)) + val initTempCompleted = Assign( + gen.mkAttributedIdent(transformState.applyTrParam), + gen.mkMethodCall(transformState.memberRef(transformState.stateGetCompleted), gen.mkAttributedIdent(tempAwaitableSym) :: Nil) + ) + val null_ne = gen.mkAttributedSelect(Literal(Constant(null)).setType(definitions.NullTpe), definitions.Any_!=) val ifTree = If(Apply(null_ne, Ident(transformState.applyTrParam) :: Nil), - Apply(Ident(currentTransformState.whileLabel), Nil), - Block(toStats(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.applySym))) + Apply(Ident(transformState.whileLabel), Nil), + Block(toStats(callOnComplete(gen.mkAttributedIdent(tempAwaitableSym))), Return(literalUnit).setSymbol(transformState.applySym))) typed(initAwaitableTemp) :: typed(initTempCompleted) :: mkStateTree(nextState) :: typed(ifTree) :: Nil } else { - val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable, fun, Ident(nme.execContext)) - mkStateTree(nextState) :: toStats(typed(callOnComplete)) ::: typed(Return(literalUnit)) :: Nil + mkStateTree(nextState) :: toStats(typed(callOnComplete(awaitable))) ::: typed(Return(literalUnit)) :: Nil } } } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 98898f187031..f00e8290d999 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -15,15 +15,13 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.reflect.internal.Mode -import scala.tools.nsc.transform.TypingTransformers /** * Utilities used in both `ExprBuilder` and `AnfTransform`. */ -private[async] trait TransformUtils extends TypingTransformers { +private[async] trait TransformUtils extends AsyncTransformStates { import global._ - private[async] def currentTransformState: AsyncTransformState[global.type] private[async] val asyncNames: AsyncNames[global.type] object name extends asyncNames.AsyncName { def fresh(name: TermName): TermName = freshenIfNeeded(name) @@ -41,8 +39,7 @@ private[async] trait TransformUtils extends TypingTransformers { lazy val IllegalStateExceptionClass_NEW_String: Symbol = IllegalStateExceptionClass.info.decl(nme.CONSTRUCTOR).suchThat( x => x.paramss.head.size == 1 && x.firstParam.info.typeSymbol == definitions.StringClass) - def isAsync(fun: Tree): Boolean = fun.symbol == currentTransformState.ops.Async_async - def isAwait(fun: Tree): Boolean = fun.symbol == currentTransformState.ops.Async_await + def isAwait(fun: Tree): Boolean = fun.symbol == currentTransformState.Async_await def isBooleanShortCircuit(sym: Symbol): Boolean = sym.owner == definitions.BooleanClass && (sym == definitions.Boolean_and || sym == definitions.Boolean_or) diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala deleted file mode 100644 index c38f3ec44c9d..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.tools.nsc.transform.async.user - -import scala.language.higherKinds -import scala.reflect.internal.{NoPhase, SymbolTable} -import scala.tools.nsc.Global - -/** - * An abstraction over a future system. - * - * Used by the macro implementations in [[scala.tools.nsc.transform.async.AsyncPhase]] to - * customize the code generation. - * - * The API mirrors that of `scala.concurrent.Future`, see the instance - * [[ScalaConcurrentFutureSystem]] for an example of how - * to implement this. - */ -trait FutureSystem { - /** A container to receive the final value of the computation */ - type Prom[A] - /** A (potentially in-progress) computation */ - type Fut[A] - /** An execution context, required to create or register an on completion callback on a Future. */ - type ExecContext - /** Any data type isomorphic to scala.util.Try. */ - type Tryy[T] - - abstract class Ops[Universe <: SymbolTable](val u: Universe) { - import u._ - - final def isPastErasure: Boolean = { - val global = u.asInstanceOf[Global] - val erasurePhase = global.currentRun.erasurePhase - erasurePhase != NoPhase && global.isPast(erasurePhase) - } - def Async_async: Symbol - def Async_await: Symbol - - def tryType(tp: Type): Type - def stateMachineClassParents: List[Type] = Nil - - /** Construct a future to asynchronously compute the given expression -- tree shape should take isPastErasure into account */ - def future(a: Tree, execContext: Tree): Tree - def futureUnit(execContext: Tree): Tree - - type Expr[T] = Tree - - /** Register an call back to run on completion of the given future -- only called when isPastErasure */ - def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[Tryy[A] => B], - execContext: Expr[ExecContext]): Expr[Unit] - - def continueCompletedFutureOnSameThread: Boolean = false - - /** Return `null` if this future is not yet completed, or `Tryy[A]` with the completed result - * otherwise - * - * Only called when isPastErasure - */ - def getCompleted[A](future: Expr[Fut[A]]): Expr[Tryy[A]] = - throw new UnsupportedOperationException("getCompleted not supported by this FutureSystem") - - /** Complete a promise with a value -- only called when isPastErasure */ - def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit] - def completeWithSuccess[A](prom: Expr[Prom[A]], value: Expr[A]): Expr[Unit] = completeProm(prom, tryySuccess(value)) - - def tryyIsFailure[A](tryy: Expr[Tryy[A]]): Expr[Boolean] - - def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] - def tryySuccess[A](a: Expr[A]): Expr[Tryy[A]] - def tryyFailure[A](a: Expr[Throwable]): Expr[Tryy[A]] - - /** A hook for custom macros to transform the tree post-ANF transform */ - def postAnfTransform(tree: Block): Block = tree - - /** A hook for custom macros to selectively generate and process a Graphviz visualization of the transformed state machine */ - def dot(enclosingOwner: Symbol, macroApplication: Tree): Option[(String => Unit)] = None - } - - def mkOps(u: SymbolTable): Ops[u.type] - - def emitTryCatch: Boolean = true -} - -// TODO AM: test the erased version by running the remainder of the test suite post-posterasure (i.e., not LateExpansion, which tests AsyncId) -object ScalaConcurrentFutureSystem extends FutureSystem { - import scala.concurrent._ - - type Prom[A] = Promise[A] - type Fut[A] = Future[A] - type ExecContext = ExecutionContext - type Tryy[A] = scala.util.Try[A] - - def mkOps(u: SymbolTable): Ops[u.type] = new ScalaConcurrentOps[u.type](u) - class ScalaConcurrentOps[Universe <: SymbolTable](u0: Universe) extends Ops[Universe](u0) { - import u._ - - private val global = u.asInstanceOf[Global] - lazy val Async_async: Symbol = global.currentRun.runDefinitions.Async_async.asInstanceOf[Symbol] - lazy val Async_await: Symbol = global.currentRun.runDefinitions.Async_await.asInstanceOf[Symbol] - lazy val Future_class: Symbol = rootMirror.requiredClass[scala.concurrent.Future[_]] - lazy val Option_class: Symbol = rootMirror.requiredClass[scala.Option[_]] - lazy val Promise_class: Symbol = rootMirror.requiredClass[scala.concurrent.Promise[_]] - lazy val Try_class: Symbol = rootMirror.requiredClass[scala.util.Try[_]] - lazy val Success_class: Symbol = rootMirror.requiredClass[scala.util.Success[_]] - lazy val Failure_class: Symbol = rootMirror.requiredClass[scala.util.Failure[_]] - lazy val Future_onComplete: Symbol = Future_class.info.member(TermName("onComplete")) - lazy val Future_value: Symbol = Future_class.info.member(TermName("value")) - lazy val Future_isCompleted: Symbol = Future_class.info.member(TermName("isCompleted")) - lazy val Future_unit: Symbol = Future_class.companionModule.info.member(TermName("unit")) - lazy val Option_get: Symbol = Option_class.info.member(TermName("get")) - lazy val Promise_complete: Symbol = Promise_class.info.member(TermName("complete")) - lazy val Try_isFailure: Symbol = Try_class.info.member(TermName("isFailure")) - lazy val Try_get: Symbol = Try_class.info.member(TermName("get")) - - def tryType(tp: Type): Type = appliedType(Try_class, tp) - - def future(a: Tree, execContext: Tree): Tree = - if (isPastErasure) Apply(Select(gen.mkAttributedStableRef(Future_class.companionModule), nme.apply), List(a, execContext)) - else Apply(Apply(Select(gen.mkAttributedStableRef(Future_class.companionModule), nme.apply), List(a)), List(execContext)) - - def futureUnit(execContext: Tree): Tree = - mkAttributedSelectApplyIfNeeded(gen.mkAttributedStableRef(Future_class.companionModule), Future_unit) - - def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[scala.util.Try[A] => B], - execContext: Expr[ExecContext]): Expr[Unit] = { - val sel = Select(future, Future_onComplete) - if (isPastErasure) - Apply(sel, fun :: execContext :: Nil) - else - Apply(Apply(TypeApply(sel, TypeTree(definitions.UnitTpe) :: Nil), fun :: Nil), execContext :: Nil) - } - - override def continueCompletedFutureOnSameThread: Boolean = true - - def mkAttributedSelectApplyIfNeeded(qual: Tree, sym: Symbol) = { - val sel = gen.mkAttributedSelect(qual, sym) - if (sel.tpe == null) - if (isPastErasure) Apply(sel, Nil) else sel - else - if (isPastErasure) Apply(sel, Nil).setType(sel.tpe.resultType) else sel - } - - override def getCompleted[A](future: Expr[Fut[A]]): Expr[Tryy[A]] = { - val futVal = mkAttributedSelectApplyIfNeeded(future, Future_value) - val futValGet = mkAttributedSelectApplyIfNeeded(futVal, Option_get) - val isCompleted = mkAttributedSelectApplyIfNeeded(future, Future_isCompleted) - If(isCompleted, futValGet, Literal(Constant(null))) - } - - def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = { - gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) - } - - def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = { - mkAttributedSelectApplyIfNeeded(tryy, Try_isFailure) - } - - def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = { - mkAttributedSelectApplyIfNeeded(tryy, Try_get) - } - - def tryySuccess[A](a: Expr[A]): Expr[Tryy[A]] = { - assert(isPastErasure) - New(Success_class, a) - } - - def tryyFailure[A](a: Expr[Throwable]): Expr[Tryy[A]] = { - assert(isPastErasure) - New(Failure_class, a) - } - } -} diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 5dc113282dce..28292ac691aa 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1287,12 +1287,15 @@ abstract class RefChecks extends Transform { } // See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly. // async/await is expanded after erasure - if (sym.isCompileTimeOnly && sym != currentRun.runDefinitions.Async_await && !currentOwner.ownerChain.exists(x => x.isCompileTimeOnly)) { - def defaultMsg = - sm"""Reference to ${sym.fullLocationString} should not have survived past type checking, - |it should have been processed and eliminated during expansion of an enclosing macro.""" - // The getOrElse part should never happen, it's just here as a backstop. - reporter.error(pos, sym.compileTimeOnlyMessage getOrElse defaultMsg) + if (sym.isCompileTimeOnly && !currentOwner.ownerChain.exists(x => x.isCompileTimeOnly)) { + if (!async.deferCompileTimeOnlyError(sym)) { + def defaultMsg = + sm"""Reference to ${sym.fullLocationString} should not have survived past type checking, + |it should have been processed and eliminated during expansion of an enclosing macro.""" + // The getOrElse part should never happen, it's just here as a backstop. + val msg = sym.compileTimeOnlyMessage getOrElse defaultMsg + reporter.error(pos, sym.compileTimeOnlyMessage getOrElse defaultMsg) + } } } @@ -1721,7 +1724,7 @@ abstract class RefChecks extends Transform { } private def checkUnexpandedMacro(t: Tree) = - if (!t.isDef && t.hasSymbolField && t.symbol.isTermMacro && t.symbol != currentRun.runDefinitions.Async_async) // TODO async + if (!t.isDef && t.hasSymbolField && t.symbol.isTermMacro) reporter.error(t.pos, "macro has not been expanded") override def transform(tree: Tree): Tree = { diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 83888bab9020..014d7308191d 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -69,7 +69,6 @@ class FastTrack[MacrosAndAnalyzer <: Macros with Analyzer](val macros: MacrosAnd makeBlackbox(ReflectRuntimeCurrentMirror) { case _ => c => currentMirror(c).tree }, makeWhitebox( QuasiquoteClass_api_apply) { case _ => _.expandQuasiquote }, makeWhitebox(QuasiquoteClass_api_unapply) { case _ => _.expandQuasiquote } - ) ++ makeBlackBoxIfExists(global.async.fastTrackEntry) + ) } - } diff --git a/src/library/scala/async/Async.scala b/src/library/scala/async/Async.scala deleted file mode 100644 index 99a86c7afeb0..000000000000 --- a/src/library/scala/async/Async.scala +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala.async - -import scala.annotation.compileTimeOnly -import scala.concurrent.{ExecutionContext, Future} -import scala.language.experimental.macros - -/** - * Async blocks provide a direct means to work with [[scala.concurrent.Future]]. - * - * For example, to use an API that fetches a web page to fetch - * two pages and add their lengths: - * - * {{{ - * import ExecutionContext.Implicits.global - * import scala.async.{async, await} - * - * def fetchURL(url: URL): Future[String] = ... - * - * val sumLengths: Future[Int] = async { - * val body1 = fetchURL("http://scala-lang.org") - * val body2 = fetchURL("http://docs.scala-lang.org") - * await(body1).length + await(body2).length - * } - * }}} - * - * Note that in the following program, the second fetch does *not* start - * until after the first. If you need to start tasks in parallel, you must do - * so before `await`-ing a result. - * - * {{{ - * val sumLengths: Future[Int] = async { - * await(fetchURL("http://scala-lang.org")).length + await(fetchURL("http://docs.scala-lang.org")).length - * } - * }}} - */ -object Async { - /** - * Run the block of code `body` asynchronously. `body` may contain calls to `await` when the results of - * a `Future` are needed; this is translated into non-blocking code. - */ - def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro ??? - - /** - * Non-blocking await the on result of `awaitable`. This may only be used directly within an enclosing `async` block. - * - * Internally, this will register the remainder of the code in enclosing `async` block as a callback - * in the `onComplete` handler of `awaitable`, and will *not* block a thread. - */ - @compileTimeOnly("`await` must be enclosed in an `async` block") - def await[T](awaitable: Future[T]): T = ??? // No implementation here, as calls to this are translated to `onComplete` by the macro. -} diff --git a/src/partest-extras/scala/tools/partest/async/Async.scala b/src/partest-extras/scala/tools/partest/async/Async.scala new file mode 100644 index 000000000000..25ff026d6c53 --- /dev/null +++ b/src/partest-extras/scala/tools/partest/async/Async.scala @@ -0,0 +1,80 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.tools.partest.async + +import java.util.Objects +import scala.language.experimental.macros + +import scala.annotation.compileTimeOnly +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.reflect.macros.blackbox +import scala.tools.nsc.transform.async.StateAssigner +import scala.util.{Failure, Success, Try} + +object Async { + def async[T](body: T)(implicit executionContext: ExecutionContext): Future[T] = macro impl + @compileTimeOnly("[async] `await` must be enclosed in an `async` block") + def await[T](completableFuture: Future[T]): T = ??? + + def impl(c: blackbox.Context)(body: c.Tree)(executionContext: c.Tree): c.Tree = { + import c.universe._ + val awaitSym = typeOf[Async.type].decl(TermName("await")) + def mark(t: DefDef): Tree = { + c.internal.markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty) + } + val name = TypeName("stateMachine$async_" + body.pos.line) + q""" + final class $name extends _root_.scala.tools.partest.async.AsyncAsMacroStateMachine($executionContext) { + ${mark(q"""override def apply(tr$$async: _root_.scala.util.Try[_root_.scala.AnyRef]) = ${body}""")} + } + new $name().start().asInstanceOf[${c.macroApplication.tpe}] + """ + } +} + +abstract class AsyncAsMacroStateMachine(execContext: ExecutionContext) extends AsyncStateMachine[Future[AnyRef], Try[AnyRef]] with Function1[Try[AnyRef], Unit] { + Objects.requireNonNull(execContext) + + private val result$async: Promise[AnyRef] = Promise[AnyRef](); + + // FSM translated method + def apply(tr$async: Try[AnyRef]): Unit + + // Required methods + protected var state$async: Int = StateAssigner.Initial + + // scala-async accidentally started catching NonFatal exceptions in: + // https://github.com/scala/scala-async/commit/e3ff0382ae4e015fc69da8335450718951714982#diff-136ab0b6ecaee5d240cd109e2b17ccb2R411 + // This follows the new behaviour but should we fix the regression? + protected def completeFailure(t: Throwable): Unit = result$async.complete(Failure(t)) + + protected def completeSuccess(value: AnyRef): Unit = result$async.complete(Success(value)) + protected def onComplete(f: Future[AnyRef]): Unit = f.onComplete(this)(execContext) + protected def getCompleted(f: Future[AnyRef]): Try[AnyRef] = { + if (f.isCompleted) { + f.value.get + } else null + } + protected def tryGet(tr: Try[AnyRef]): AnyRef = tr match { + case Success(value) => + value.asInstanceOf[AnyRef] + case Failure(throwable) => + completeFailure(throwable) + this // sentinel value to indicate the dispatch loop should exit. + } + def start(): Future[AnyRef] = { + // This cast is safe because we know that `def apply` does not consult its argument when `state == 0`. + Future.unit.asInstanceOf[Future[AnyRef]].onComplete(this)(execContext) + result$async.future + } +} diff --git a/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala b/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala new file mode 100644 index 000000000000..2f71ca701392 --- /dev/null +++ b/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala @@ -0,0 +1,35 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.tools.partest.async + +// The async phase expects the state machine class to structurally conform to this interface. +trait AsyncStateMachine[F, R] { + /** Assign `i` to the state variable */ + protected def state$async_=(i: Int): Unit + /** Retrieve the current value of the state variable */ + protected def state$async: Int + /** Complete the state machine with the given failure. */ + protected def completeFailure(t: Throwable): Unit + /** Complete the state machine with the given value. */ + protected def completeSuccess(value: AnyRef): Unit + /** Register the state machine as a completion callback of the given future. */ + protected def onComplete(f: F): Unit + /** Extract the result of the given future if it is complete, or `null` if it is incomplete. */ + protected def getCompleted(f: F): R + /** + * Extract the success value of the given future. If the state machine detects a failure it may + * complete the async block and return `this` as a sentinel value to indicate that the caller + * (the state machine dispatch loop) should immediately exit. + */ + protected def tryGet(tr: R): AnyRef +} diff --git a/src/reflect/scala/reflect/api/Internals.scala b/src/reflect/scala/reflect/api/Internals.scala index 248aba274603..b52d84103904 100644 --- a/src/reflect/scala/reflect/api/Internals.scala +++ b/src/reflect/scala/reflect/api/Internals.scala @@ -374,6 +374,8 @@ trait Internals { self: Universe => */ def boundedWildcardType(bounds: TypeBounds): BoundedWildcardType + def markForAsyncTransform(owner: Symbol, method: DefDef, awaitSymbol: Symbol, config: Map[String, AnyRef]): DefDef = method + /** Syntactic conveniences for additional internal APIs for trees, symbols and types */ type Decorators <: DecoratorApi diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index d36ef4601351..688073755b11 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -644,6 +644,13 @@ trait Definitions extends api.StandardDefinitions { def isFunctionSymbol(sym: Symbol) = FunctionClass contains unspecializedSymbol(sym) def isProductNSymbol(sym: Symbol) = ProductClass contains unspecializedSymbol(sym) + lazy val TryClass = requiredClass[scala.util.Try[_]] + lazy val FailureClass = requiredClass[scala.util.Failure[_]] + lazy val SuccessClass = requiredClass[scala.util.Success[_]] + lazy val FutureClass = requiredClass[scala.concurrent.Future[_]] + lazy val PromiseClass = requiredClass[scala.concurrent.Promise[_]] + lazy val NonFatalClass = requiredClass[scala.util.control.NonFatal.type] + def unspecializedSymbol(sym: Symbol): Symbol = { if (sym hasFlag SPECIALIZED) { // add initialization from its generic class constructor @@ -1541,6 +1548,8 @@ trait Definitions extends api.StandardDefinitions { lazy val Boolean_not = definitions.Boolean_not lazy val Option_apply = getMemberMethod(OptionModule, nme.apply) + lazy val Option_isDefined: Symbol = getMemberMethod(OptionClass, TermName("isDefined")) + lazy val Option_get: Symbol = getMemberMethod(OptionClass, TermName("get")) lazy val List_apply = DefinitionsClass.this.List_apply /** @@ -1622,9 +1631,10 @@ trait Definitions extends api.StandardDefinitions { lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.runtime.java8") - lazy val AsyncModule = getModuleIfDefined("scala.async.Async") - lazy val Async_async = AsyncModule.map(async => getDeclIfDefined(async, nme.async)) - lazy val Async_await = AsyncModule.map(async => getDeclIfDefined(async, nme.await)) + lazy val Future_unit: Symbol = getMemberMethod(FutureClass.companionModule, TermName("unit")) + lazy val Future_onComplete: Symbol = getMemberMethod(FutureClass, TermName("onComplete")) + lazy val Future_value: Symbol = getMemberMethod(FutureClass, TermName("value")) + lazy val Promise_complete: Symbol = getMemberMethod(PromiseClass, TermName("complete")) } } } diff --git a/src/reflect/scala/reflect/internal/Phase.scala b/src/reflect/scala/reflect/internal/Phase.scala index f6cf8dd5d938..c0b4cdcb33b6 100644 --- a/src/reflect/scala/reflect/internal/Phase.scala +++ b/src/reflect/scala/reflect/internal/Phase.scala @@ -64,6 +64,7 @@ abstract class Phase(val prev: Phase) extends Ordered[Phase] { * overridden to false in parser, namer, typer, and erasure. (And NoPhase.) */ def keepsTypeParams = true + def init(): Unit = () def run(): Unit override def toString() = name diff --git a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala index 068dd680c99e..6d50c6ab4176 100644 --- a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala +++ b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala @@ -44,6 +44,7 @@ abstract class MutableSettings extends AbsSettings { def valueSetByUser: Option[T] = if (isSetByUser) Some(value) else None } + def async: BooleanSetting def Xexperimental: BooleanSetting def XfullLubs: BooleanSetting def XnoPatmatAnalysis: BooleanSetting diff --git a/src/reflect/scala/reflect/runtime/Settings.scala b/src/reflect/scala/reflect/runtime/Settings.scala index 85f70d88ee8d..d36e6c8bc6e8 100644 --- a/src/reflect/scala/reflect/runtime/Settings.scala +++ b/src/reflect/scala/reflect/runtime/Settings.scala @@ -42,6 +42,7 @@ private[reflect] class Settings extends MutableSettings { override def value: List[String] = v } + val async = new BooleanSetting(false) val Xexperimental = new BooleanSetting(false) val XfullLubs = new BooleanSetting(false) val XnoPatmatAnalysis = new BooleanSetting(false) diff --git a/test/async/neg/naked_await.check b/test/async/neg/naked_await.check new file mode 100644 index 000000000000..e09a175ed9b4 --- /dev/null +++ b/test/async/neg/naked_await.check @@ -0,0 +1,7 @@ +naked_await.scala:8: error: await must not be used under a nested method. + def foo = await(Future(3)) + ^ +naked_await.scala:10: error: `await` must be enclosed in an `async` block + await(Future(4)) + ^ +two errors found diff --git a/test/async/neg/naked_await.flags b/test/async/neg/naked_await.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/neg/naked_await.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/neg/naked_await.scala b/test/async/neg/naked_await.scala new file mode 100644 index 000000000000..3c09117b4788 --- /dev/null +++ b/test/async/neg/naked_await.scala @@ -0,0 +1,12 @@ +import scala.tools.partest.async.Async._ +import scala.concurrent.{ExecutionContext, Future} + +object Test { + def foo(implicit ec: ExecutionContext) = { + async { + await(Future(1)) + await(Future(2)) + def foo = await(Future(3)) + } + await(Future(4)) + } +} diff --git a/test/async/neg/stark_naked_await.check b/test/async/neg/stark_naked_await.check new file mode 100644 index 000000000000..322467db1175 --- /dev/null +++ b/test/async/neg/stark_naked_await.check @@ -0,0 +1,4 @@ +stark_naked_await.scala:6: error: `await` must be enclosed in an `async` block + await(Future(4)) + ^ +one error found diff --git a/test/async/neg/stark_naked_await.flags b/test/async/neg/stark_naked_await.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/neg/stark_naked_await.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/neg/stark_naked_await.scala b/test/async/neg/stark_naked_await.scala new file mode 100644 index 000000000000..753f636fe2dd --- /dev/null +++ b/test/async/neg/stark_naked_await.scala @@ -0,0 +1,8 @@ +import scala.tools.partest.async.Async._ +import scala.concurrent.{ExecutionContext, Future} + +object Test { + def foo(implicit ec: ExecutionContext) = { + await(Future(4)) + } +} diff --git a/test/async/run/anf.flags b/test/async/run/anf.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/anf.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/anf.scala b/test/async/run/anf.scala index 2195a9a55969..cd2c0361298f 100644 --- a/test/async/run/anf.scala +++ b/test/async/run/anf.scala @@ -5,7 +5,7 @@ package scala.async.run.anf { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import scala.reflect.{ClassTag, classTag} import org.junit.Test @@ -396,7 +396,7 @@ package scala.async.run.anf { // val tree = tb.typeCheck(tb.parse { // """ // | import language.implicitConversions -// | import _root_.scala.async.Async.{async, await} +// | import _root_.scala.tools.partest.async.Async.{async, await} // | import _root_.scala.concurrent._ // | import ExecutionContext.Implicits.global // | implicit def view(a: Int): String = "" diff --git a/test/async/run/await0.flags b/test/async/run/await0.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/await0.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/await0.scala b/test/async/run/await0.scala index b2a9d1767379..b7ce497bfefd 100644 --- a/test/async/run/await0.scala +++ b/test/async/run/await0.scala @@ -10,7 +10,7 @@ package scala.async.run.await0 { import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/block0.flags b/test/async/run/block0.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/block0.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/block0.scala b/test/async/run/block0.scala index 49ec5889b7a3..861ee6c46aac 100644 --- a/test/async/run/block0.scala +++ b/test/async/run/block0.scala @@ -5,7 +5,7 @@ package scala.async.run.block0 { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/block1.flags b/test/async/run/block1.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/block1.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/block1.scala b/test/async/run/block1.scala index ba46a9c83ab8..27fc7d49b6fc 100644 --- a/test/async/run/block1.scala +++ b/test/async/run/block1.scala @@ -5,7 +5,7 @@ package scala.async.run.block1 { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/concurrent_AfterRefchecksIssue.flags b/test/async/run/concurrent_AfterRefchecksIssue.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_AfterRefchecksIssue.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_AfterRefchecksIssue.scala b/test/async/run/concurrent_AfterRefchecksIssue.scala index 3f15651fe9c3..d401b787ed5a 100644 --- a/test/async/run/concurrent_AfterRefchecksIssue.scala +++ b/test/async/run/concurrent_AfterRefchecksIssue.scala @@ -1,4 +1,4 @@ -import scala.concurrent._, ExecutionContext.Implicits.global, scala.async.Async._ +import scala.concurrent._, ExecutionContext.Implicits.global, scala.tools.partest.async.Async._ trait Factory[T] { def create: T diff --git a/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.flags b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala index bc0399b389be..92dd3f70221e 100644 --- a/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala +++ b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration sealed trait Result diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.flags b/test/async/run/concurrent_GenericTypeBoundaryIssue.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_GenericTypeBoundaryIssue.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.scala b/test/async/run/concurrent_GenericTypeBoundaryIssue.scala index cfc77b3ba346..defa019b43ff 100644 --- a/test/async/run/concurrent_GenericTypeBoundaryIssue.scala +++ b/test/async/run/concurrent_GenericTypeBoundaryIssue.scala @@ -2,7 +2,7 @@ import Test.test import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration trait InstrumentOfValue diff --git a/test/async/run/concurrent_MatchEndIssue.flags b/test/async/run/concurrent_MatchEndIssue.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_MatchEndIssue.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_MatchEndIssue.scala b/test/async/run/concurrent_MatchEndIssue.scala index 591805e33599..175a4a912e00 100644 --- a/test/async/run/concurrent_MatchEndIssue.scala +++ b/test/async/run/concurrent_MatchEndIssue.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration sealed trait Subject diff --git a/test/async/run/concurrent_NegativeArraySizeException.flags b/test/async/run/concurrent_NegativeArraySizeException.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_NegativeArraySizeException.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_NegativeArraySizeException.scala b/test/async/run/concurrent_NegativeArraySizeException.scala index d81cada34989..3fdff3d2e9f6 100644 --- a/test/async/run/concurrent_NegativeArraySizeException.scala +++ b/test/async/run/concurrent_NegativeArraySizeException.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.flags b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala index 20e924653fa2..fda8b0331289 100644 --- a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala +++ b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration case class FixedFoo(foo: Int) diff --git a/test/async/run/concurrent_ReturnTupleIssue.flags b/test/async/run/concurrent_ReturnTupleIssue.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_ReturnTupleIssue.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_ReturnTupleIssue.scala b/test/async/run/concurrent_ReturnTupleIssue.scala index e6051c1dd176..b0cc78d1e9f9 100644 --- a/test/async/run/concurrent_ReturnTupleIssue.scala +++ b/test/async/run/concurrent_ReturnTupleIssue.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration class TestReturnExprIssue(str: String) { diff --git a/test/async/run/concurrent_fetch.flags b/test/async/run/concurrent_fetch.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_fetch.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_fetch.scala b/test/async/run/concurrent_fetch.scala index 10d85084225a..ef246e3fb5f1 100644 --- a/test/async/run/concurrent_fetch.scala +++ b/test/async/run/concurrent_fetch.scala @@ -1,6 +1,6 @@ import scala.concurrent.{Await, Future, duration} import scala.concurrent.ExecutionContext.Implicits.global -import scala.async.Async.{async, await} +import scala.tools.partest.async.Async.{async, await} object Test extends App { val out = Console.out diff --git a/test/async/run/concurrent_patternAlternative.flags b/test/async/run/concurrent_patternAlternative.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_patternAlternative.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_patternAlternative.scala b/test/async/run/concurrent_patternAlternative.scala index 759eadc49c26..c8baa10adc14 100644 --- a/test/async/run/concurrent_patternAlternative.scala +++ b/test/async/run/concurrent_patternAlternative.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_patternAlternativeBothAnnotations.flags b/test/async/run/concurrent_patternAlternativeBothAnnotations.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_patternAlternativeBothAnnotations.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_patternAlternativeBothAnnotations.scala b/test/async/run/concurrent_patternAlternativeBothAnnotations.scala index a7dd9b0038ef..6e077edca592 100644 --- a/test/async/run/concurrent_patternAlternativeBothAnnotations.scala +++ b/test/async/run/concurrent_patternAlternativeBothAnnotations.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_polymorphicMethod.flags b/test/async/run/concurrent_polymorphicMethod.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_polymorphicMethod.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_polymorphicMethod.scala b/test/async/run/concurrent_polymorphicMethod.scala index 1a8eac38d127..3d5a8d74c7d9 100644 --- a/test/async/run/concurrent_polymorphicMethod.scala +++ b/test/async/run/concurrent_polymorphicMethod.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { assert(test.toString == "(C,C)") diff --git a/test/async/run/concurrent_shadowing.flags b/test/async/run/concurrent_shadowing.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_shadowing.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_shadowing.scala b/test/async/run/concurrent_shadowing.scala index 7c874c4e6eae..185176e8d1e5 100644 --- a/test/async/run/concurrent_shadowing.scala +++ b/test/async/run/concurrent_shadowing.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_shadowing0.flags b/test/async/run/concurrent_shadowing0.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_shadowing0.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_shadowing0.scala b/test/async/run/concurrent_shadowing0.scala index b9ee0ff4b1ee..e47eafa5ceb6 100644 --- a/test/async/run/concurrent_shadowing0.scala +++ b/test/async/run/concurrent_shadowing0.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_shadowing2.flags b/test/async/run/concurrent_shadowing2.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_shadowing2.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_shadowing2.scala b/test/async/run/concurrent_shadowing2.scala index 764b8b872fab..b24a0f263e88 100644 --- a/test/async/run/concurrent_shadowing2.scala +++ b/test/async/run/concurrent_shadowing2.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_shadowingRefinedTypes.flags b/test/async/run/concurrent_shadowingRefinedTypes.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_shadowingRefinedTypes.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_shadowingRefinedTypes.scala b/test/async/run/concurrent_shadowingRefinedTypes.scala index 123937e8d444..4e711e4d43aa 100644 --- a/test/async/run/concurrent_shadowingRefinedTypes.scala +++ b/test/async/run/concurrent_shadowingRefinedTypes.scala @@ -1,6 +1,6 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration trait Base diff --git a/test/async/run/concurrent_test0.flags b/test/async/run/concurrent_test0.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/concurrent_test0.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/concurrent_test0.scala b/test/async/run/concurrent_test0.scala index 0b4fe8eadbc7..bfafe3f192fd 100644 --- a/test/async/run/concurrent_test0.scala +++ b/test/async/run/concurrent_test0.scala @@ -13,7 +13,7 @@ import scala.concurrent._ import ExecutionContext.Implicits.global -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object Test extends App { assert(test == "foobar") diff --git a/test/async/run/exceptions.flags b/test/async/run/exceptions.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/exceptions.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/exceptions.scala b/test/async/run/exceptions.scala index d129445e62bd..9596d15358f9 100644 --- a/test/async/run/exceptions.scala +++ b/test/async/run/exceptions.scala @@ -2,7 +2,7 @@ object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.except package scala.async.run.exceptions { - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import scala.concurrent.{Future, ExecutionContext, Await} import ExecutionContext.Implicits._ diff --git a/test/async/run/futures.flags b/test/async/run/futures.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/futures.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/futures.scala b/test/async/run/futures.scala index 29220b5c1708..baae0ad08c2f 100644 --- a/test/async/run/futures.scala +++ b/test/async/run/futures.scala @@ -52,7 +52,7 @@ package scala.async.run.futures { import scala.reflect.{ClassTag, classTag} import scala.async.TestLatch - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test diff --git a/test/async/run/hygiene.flags b/test/async/run/hygiene.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/hygiene.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/hygiene.scala b/test/async/run/hygiene.scala index b9f7a9e2b2d8..a5141abf04e9 100644 --- a/test/async/run/hygiene.scala +++ b/test/async/run/hygiene.scala @@ -7,7 +7,7 @@ package scala.async.run.hygiene { import scala.concurrent._ import ExecutionContext.Implicits.global - import scala.async.Async._ + import scala.tools.partest.async.Async._ import scala.concurrent.duration.Duration object TestUtil { import language.implicitConversions diff --git a/test/async/run/ifelse0.flags b/test/async/run/ifelse0.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/ifelse0.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/ifelse0.scala b/test/async/run/ifelse0.scala index 348a1d0d1e96..909d705ca2d3 100644 --- a/test/async/run/ifelse0.scala +++ b/test/async/run/ifelse0.scala @@ -9,7 +9,7 @@ package scala.async.run.ifelse0 { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/ifelse0_while.flags b/test/async/run/ifelse0_while.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/ifelse0_while.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/ifelse0_while.scala b/test/async/run/ifelse0_while.scala index 863a5d04fac0..0e6c1b1e0b47 100644 --- a/test/async/run/ifelse0_while.scala +++ b/test/async/run/ifelse0_while.scala @@ -7,7 +7,7 @@ package scala.async.run.ifelse0 { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/ifelse1.flags b/test/async/run/ifelse1.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/ifelse1.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/ifelse1.scala b/test/async/run/ifelse1.scala index 9424804d1beb..7cefcf674354 100644 --- a/test/async/run/ifelse1.scala +++ b/test/async/run/ifelse1.scala @@ -5,7 +5,7 @@ package scala.async.run.ifelse1 { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/ifelse2.flags b/test/async/run/ifelse2.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/ifelse2.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/ifelse2.scala b/test/async/run/ifelse2.scala index 4c8f57c265f2..8c18b11ddae0 100644 --- a/test/async/run/ifelse2.scala +++ b/test/async/run/ifelse2.scala @@ -5,7 +5,7 @@ package scala.async.run.ifelse2 { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/ifelse3.flags b/test/async/run/ifelse3.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/ifelse3.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/ifelse3.scala b/test/async/run/ifelse3.scala index aa053eac4cdf..ade9ebe80020 100644 --- a/test/async/run/ifelse3.scala +++ b/test/async/run/ifelse3.scala @@ -5,7 +5,7 @@ package scala.async.run.ifelse3 { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/ifelse4.flags b/test/async/run/ifelse4.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/ifelse4.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/ifelse4.scala b/test/async/run/ifelse4.scala index 9d0c1a52fdc8..ec7fe890ef6a 100644 --- a/test/async/run/ifelse4.scala +++ b/test/async/run/ifelse4.scala @@ -5,7 +5,7 @@ package scala.async.run.ifelse4 { import language.{reflectiveCalls, postfixOps, existentials} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import org.junit.Test import org.junit.Assert._ diff --git a/test/async/run/lazyval.flags b/test/async/run/lazyval.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/lazyval.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/lazyval.scala b/test/async/run/lazyval.scala index c94f3e6f1bba..9d08d4ffd0f6 100644 --- a/test/async/run/lazyval.scala +++ b/test/async/run/lazyval.scala @@ -7,7 +7,7 @@ package scala.async.run.lazyval { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/live.flags b/test/async/run/live.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/live.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/live.scala b/test/async/run/live.scala index 7d3da2e35267..e4dc2c37dcf7 100644 --- a/test/async/run/live.scala +++ b/test/async/run/live.scala @@ -6,7 +6,7 @@ package scala.async.run.live { import scala.concurrent._ import duration.Duration - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import scala.collection.immutable object TestUtil { import language.implicitConversions diff --git a/test/async/run/localclasses.flags b/test/async/run/localclasses.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/localclasses.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/localclasses.scala b/test/async/run/localclasses.scala index b3813f2bd836..7f3191675a1f 100644 --- a/test/async/run/localclasses.scala +++ b/test/async/run/localclasses.scala @@ -7,7 +7,7 @@ package scala.async.neg { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/match0.flags b/test/async/run/match0.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/match0.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/match0.scala b/test/async/run/match0.scala index 8af85f7154f6..8b4309a7c6e6 100644 --- a/test/async/run/match0.scala +++ b/test/async/run/match0.scala @@ -10,7 +10,7 @@ package scala.async.run.match0 { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/nesteddef.flags b/test/async/run/nesteddef.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/nesteddef.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/nesteddef.scala b/test/async/run/nesteddef.scala index 854db64c18cc..15e804dcd5e3 100644 --- a/test/async/run/nesteddef.scala +++ b/test/async/run/nesteddef.scala @@ -7,7 +7,7 @@ package scala.async.run.nesteddef { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/noawait.flags b/test/async/run/noawait.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/noawait.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/noawait.scala b/test/async/run/noawait.scala index 6d74c4dd7226..323b48462c84 100644 --- a/test/async/run/noawait.scala +++ b/test/async/run/noawait.scala @@ -8,7 +8,7 @@ package scala.async.run.noawait { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/stackoverflow.flags b/test/async/run/stackoverflow.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/stackoverflow.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/stackoverflow.scala b/test/async/run/stackoverflow.scala index 75f32fc11bfd..37bf09b3bbdc 100644 --- a/test/async/run/stackoverflow.scala +++ b/test/async/run/stackoverflow.scala @@ -1,7 +1,7 @@ import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global -import scala.async.Async.{async, await} +import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) diff --git a/test/async/run/syncOptimization.flags b/test/async/run/syncOptimization.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/syncOptimization.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/syncOptimization.scala b/test/async/run/syncOptimization.scala index e5bd29d86c23..9570a2a0d18d 100644 --- a/test/async/run/syncOptimization.scala +++ b/test/async/run/syncOptimization.scala @@ -1,4 +1,4 @@ -import scala.async.Async._ +import scala.tools.partest.async.Async._ import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits._ diff --git a/test/async/run/toughtype.flags b/test/async/run/toughtype.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/toughtype.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/toughtype.scala b/test/async/run/toughtype.scala index 220654f46d1a..09052b365763 100644 --- a/test/async/run/toughtype.scala +++ b/test/async/run/toughtype.scala @@ -9,7 +9,7 @@ package scala.async.run.toughtype { import scala.concurrent._ import scala.concurrent.duration._ import ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} object TestUtil { import language.implicitConversions implicit def lift[T](t: T): Future[T] = Future.successful(t) @@ -80,7 +80,7 @@ package scala.async.run.toughtype { } @Test def existentialBind2Issue19(): Unit = { - import scala.async.Async._, scala.concurrent.ExecutionContext.Implicits.global + import scala.tools.partest.async.Async._, scala.concurrent.ExecutionContext.Implicits.global def conjure[T]: T = null.asInstanceOf[T] def m3 = async { @@ -151,7 +151,7 @@ package scala.async.run.toughtype { import language.{reflectiveCalls, postfixOps} import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} class Foo[A] @@ -182,7 +182,7 @@ package scala.async.run.toughtype { @Test def ticket63(): Unit = { - import scala.async.Async._ + import scala.tools.partest.async.Async._ import scala.concurrent.{ ExecutionContext, Future } object SomeExecutionContext extends ExecutionContext { @@ -225,7 +225,7 @@ package scala.async.run.toughtype { } @Test def ticket83ValueClass(): Unit = { - import scala.async.Async._ + import scala.tools.partest.async.Async._ import scala.concurrent._, duration._, ExecutionContext.Implicits.global val f = async { val uid = new IntWrapper("foo") diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index c1b17895d6c8..05d4cbdbde69 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -3,21 +3,20 @@ package async import java.io.File import java.nio.file.{Files, Paths} +import java.util.concurrent.CompletableFuture import org.junit.Assert.assertEquals import org.junit.{Assert, Ignore, Test} import scala.annotation.StaticAnnotation import scala.concurrent.duration.Duration -import scala.reflect.internal.SymbolTable import scala.reflect.internal.util.Position import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader import scala.tools.nsc.plugins.{Plugin, PluginComponent} import scala.tools.nsc.reporters.StoreReporter import scala.tools.nsc.transform.TypingTransformers import scala.tools.nsc.transform.async.StateAssigner -import scala.tools.nsc.transform.async.user.FutureSystem -import scala.util.Success +import scala.tools.partest.async.AsyncStateMachine class AnnotationDrivenAsync { @Test @@ -25,7 +24,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | |object Test { | def test: Future[Int] = async { await(f(1)) + await(f(2)) } @@ -35,12 +34,71 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def testBasicScalaConcurrentViaMacroFrontEnd(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + | + |object Test { + | def test: Future[Int] = async { await(f(1)) + await(f(2)) } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals(3, run(code)) + } + + @Test + def testSyncOptimizationScalaConcurrentViaMacroFrontEnd(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + | + |object Test { + | def stackDepth = Thread.currentThread().getStackTrace.length + | + | def test: Future[Unit] = async { + | val thread1 = Thread.currentThread + | val stackDepth1 = stackDepth + | + | val f = await(Future.successful(1)) + | val thread2 = Thread.currentThread + | val stackDepth2 = stackDepth + | assert(thread1 == thread2) + | assert(stackDepth1 == stackDepth2) + | } + | + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals((), run(code)) + } + + @Test + def testBasicScalaConcurrentValueClass(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |class IntWrapper(val value: String) extends AnyVal + |object Test { + | def test: Future[String] = async { await(inner).value } + | def inner: Future[IntWrapper] = async { await(f(new IntWrapper("hola"))) } + |} + |""".stripMargin + assertEquals("hola", run(code)) + } + @Test def testMatchBig(): Unit = { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | | |object Test { @@ -67,7 +125,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | | |object Test { @@ -90,7 +148,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | |object Test { | def test: Future[(String, Int, Int)] = async { var x = "init"; val y = await(f(1)); class C { x = x + "_updated" }; new C; (x, y, await(f(2))) } @@ -105,7 +163,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | |object Test { | def test: Future[(Int, Int, Int)] = async { var i = 0; var z = 0; lazy val foo = { def ii = i; z = -1; ii }; await(f(1)) + await(f(2)); i += 1; (foo, foo, z) } @@ -120,7 +178,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | |object Test { | def p[T](t: T): T = {println(t); t } @@ -148,7 +206,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | |object Test { | def p[T](t: T): T = {println(t); t } @@ -173,7 +231,7 @@ class AnnotationDrivenAsync { // https://github.com/scala/scala/pull/8750 will fix this. val code = """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - import scala.async.Async.{async, await} + import scala.tools.partest.async.Async.{async, await} import Future.{successful => f} object Test { def test = async { @@ -198,7 +256,7 @@ class AnnotationDrivenAsync { val code = """ |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.async.Async.{async, await} + |import scala.tools.partest.async.Async.{async, await} | |object Test { | def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" @@ -236,7 +294,7 @@ class AnnotationDrivenAsync { def testMixedAsync(): Unit = { val code = """ |import scala.tools.nsc.async.{autoawait, customAsync} - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global, scala.async.Async._ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global, scala.tools.partest.async.Async._ | |object Test { | @customAsync @@ -360,6 +418,30 @@ class AnnotationDrivenAsync { assertEquals(true, run(code)) } + @Test + def completableFuture(): Unit = { + val code = """ + |import java.util.concurrent._ + |import scala.tools.nsc.async.CompletableFutureAwait._ + | + |object Test { + | val pool = java.util.concurrent.Executors.newWorkStealingPool() + | def f1 = CompletableFuture.supplyAsync(() => 1, pool) + | def test = { + | async(pool) { + | var i = 0 + | while (i < 100) { + | i += await(f1) + | } + | i + | } + | } + |} + | + |""".stripMargin + assertEquals(100, run(code)) + } + // Handy to debug the compiler @@ -386,12 +468,13 @@ class AnnotationDrivenAsync { } } val settings = new Settings(println(_)) + settings.async.value = true settings.outdir.value = out.getAbsolutePath settings.embeddedDefaults(getClass.getClassLoader) // settings.debug.value = true // settings.uniqid.value = true - // settings.processArgumentString("-Xprint:all -nowarn") + // settings.processArgumentString("-Xprint:async -nowarn") // settings.log.value = List("async") // NOTE: edit ANFTransform.traceAsync to `= true` to get additional diagnostic tracing. @@ -421,6 +504,8 @@ class AnnotationDrivenAsync { scala.concurrent.Await.result(t, Duration.Inf) case cf: CustomFuture[_] => cf._block + case cf: CompletableFuture[_] => + cf.get() case value => value } } finally { @@ -458,24 +543,16 @@ abstract class AnnotationDrivenAsyncPlugin extends Plugin { case dd: DefDef if dd.symbol.hasAnnotation(customAsyncSym) => deriveDefDef(dd) { rhs => - val unit = localTyper.context.unit val applyMethod = - q"""def apply(tr: _root_.scala.util.Either[_root_.scala.Throwable, _root_.scala.AnyRef]): _root_.scala.Unit = {$rhs; () }""" - applyMethod.updateAttachment(ChangeOwnerAttachment(dd.symbol)) - global.async.addFutureSystemAttachment(unit, applyMethod, CustomFutureFutureSystem) + q"""def apply(tr: _root_.scala.util.Either[_root_.scala.Throwable, _root_.scala.AnyRef]): _root_.scala.Unit = $rhs""" + val applyMethodMarked = global.async.markForAsyncTransform(dd.symbol, applyMethod, awaitSym, Map.empty) + val name = TypeName("stateMachine$$async_" + dd.pos.line) val wrapped = q""" - { - val ${nme.execContextTemp} = () - class stateMachine$$async extends _root_.scala.tools.nsc.async.StateMachineBase { - $applyMethod - } - val stateMachine$$async = new stateMachine$$async - _root_.scala.tools.nsc.async.CustomFuture._unit._onComplete( - stateMachine$$async.asInstanceOf[_root_.scala.util.Either[_root_.scala.Throwable, _root_.scala.Unit] => _root_.scala.Unit] - ) - stateMachine$$async.result$$async._future + class $name extends _root_.scala.tools.nsc.async.CustomFutureStateMachine { + $applyMethodMarked } + new $name().start() """ val tree = @@ -515,81 +592,27 @@ final class autoawait extends StaticAnnotation final class customAsync extends StaticAnnotation -abstract class StateMachineBase extends Function1[scala.util.Either[Throwable, AnyRef], Unit] { - def execContext$async = () - var result$async: CustomPromise[AnyRef] = new CustomPromise[AnyRef](scala.concurrent.Promise.apply[AnyRef]); - var state$async: Int = StateAssigner.Initial - def apply(tr$async: scala.util.Either[Throwable, AnyRef]): Unit -} - -object CustomFutureFutureSystem extends FutureSystem { - override type Prom[A] = CustomFuture[A] - override type Fut[A] = CustomPromise[A] - override type ExecContext = Unit - override type Tryy[A] = Either[Throwable, A] - override def mkOps(u: SymbolTable): Ops[u.type] = new Ops[u.type](u) { - import u._ - - private val global = u.asInstanceOf[Global] - lazy val Future_class: Symbol = rootMirror.requiredClass[CustomFuture[_]] - lazy val Promise_class: Symbol = rootMirror.requiredClass[CustomPromise[_]] - lazy val Either_class: Symbol = rootMirror.requiredClass[scala.util.Either[_, _]] - lazy val Right_class: Symbol = rootMirror.requiredClass[scala.util.Right[_, _]] - lazy val Left_class: Symbol = rootMirror.requiredClass[scala.util.Left[_, _]] - lazy val Future_onComplete: Symbol = Future_class.info.member(TermName("_onComplete")).ensuring(_.exists) - lazy val Future_getCompleted: Symbol = Future_class.info.member(TermName("_getCompleted")).ensuring(_.exists) - lazy val Future_unit: Symbol = Future_class.companionModule.info.member(TermName("_unit")).ensuring(_.exists) - lazy val Promise_complete: Symbol = Promise_class.info.member(TermName("_complete")).ensuring(_.exists) - lazy val Either_isFailure: Symbol = Either_class.info.member(TermName("isLeft")).ensuring(_.exists) - lazy val Right_get: Symbol = Right_class.info.member(TermName("value")).ensuring(_.exists) - - lazy val Async_async: Symbol = NoSymbol.newTermSymbol(nme.EMPTY) - lazy val Async_await: Symbol = symbolOf[CustomFuture.type].info.member(TermName("_await")) - - def tryType(tp: Type): Type = appliedType(Either_class, tp) - - def future(a: Tree, execContext: Tree): Tree = - Apply(Select(gen.mkAttributedStableRef(Future_class.companionModule), TermName("_apply")), List(a)) - - def futureUnit(execContext: Tree): Tree = - mkAttributedSelectApplyIfNeeded(gen.mkAttributedStableRef(Future_class.companionModule), Future_unit) - - def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[scala.util.Try[A] => B], - execContext: Expr[ExecContext]): Expr[Unit] = { - Apply(Select(future, Future_onComplete), fun :: Nil) - } - - override def continueCompletedFutureOnSameThread: Boolean = true - - def mkAttributedSelectApplyIfNeeded(qual: Tree, sym: Symbol) = { - val sel = gen.mkAttributedSelect(qual, sym) - if (isPastErasure) Apply(sel, Nil) else sel - } - - override def getCompleted[A](future: Expr[Fut[A]]): Expr[Tryy[A]] = { - mkAttributedSelectApplyIfNeeded(future, Future_getCompleted) - } - - def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = { - gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) - } - - def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = { - mkAttributedSelectApplyIfNeeded(tryy, Either_isFailure) - } - - def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = { - mkAttributedSelectApplyIfNeeded(gen.mkCast(tryy, Right_class.tpe_*), Right_get) - } - - def tryySuccess[A](a: Expr[A]): Expr[Tryy[A]] = { - assert(isPastErasure) - New(Right_class, a) - } - - def tryyFailure[A](a: Expr[Throwable]): Expr[Tryy[A]] = { - assert(isPastErasure) - New(Left_class, a) - } +abstract class CustomFutureStateMachine extends AsyncStateMachine[CustomFuture[AnyRef], scala.util.Either[Throwable, AnyRef]] with Function1[scala.util.Either[Throwable, AnyRef], Unit] { + private val result$async: CustomPromise[AnyRef] = new CustomPromise[AnyRef](scala.concurrent.Promise.apply[AnyRef]); + protected var state$async: Int = StateAssigner.Initial + def apply(tr$async: R[AnyRef]): Unit + + type F[A] = CustomFuture[A] + type R[A] = Either[Throwable, A] + // Adapter methods + protected def completeFailure(t: Throwable): Unit = result$async._complete(Left(t)) + protected def completeSuccess(value: AnyRef): Unit = result$async._complete(Right(value)) + protected def onComplete(f: F[AnyRef]): Unit = f._onComplete(this) + protected def getCompleted(f: F[AnyRef]): R[AnyRef] = f._getCompleted + protected def tryGet(tr: R[AnyRef]): AnyRef = tr match { + case Right(value) => + value + case Left(throwable) => + result$async._complete(tr) + this // sentinel value to indicate the dispatch loop should exit. + } + def start(): CustomFuture[AnyRef] = { + CustomFuture._unit.asInstanceOf[CustomFuture[AnyRef]]._onComplete(this) + result$async._future } } diff --git a/test/junit/scala/tools/nsc/async/Async.scala b/test/junit/scala/tools/nsc/async/Async.scala new file mode 100644 index 000000000000..dcc570948f64 --- /dev/null +++ b/test/junit/scala/tools/nsc/async/Async.scala @@ -0,0 +1,68 @@ +package scala.tools.nsc.async + +import java.util.Objects + +import scala.language.experimental.macros +import scala.annotation.compileTimeOnly +import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.reflect.macros.blackbox +import scala.tools.nsc.transform.async.StateAssigner +import scala.tools.partest.async.AsyncStateMachine +import scala.util.{Failure, Success, Try} + +object Async { + def async[T](body: T)(implicit executionContext: ExecutionContext): Future[T] = macro impl + @compileTimeOnly("[async] `await` must be enclosed in `async`") + def await[T](completableFuture: Future[T]): T = ??? + + def impl(c: blackbox.Context)(body: c.Tree)(executionContext: c.Tree): c.Tree = { + import c.universe._ + val awaitSym = typeOf[Async.type].decl(TermName("await")) + def mark(t: DefDef): Tree = { + c.internal.markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty) + } + val name = TypeName("stateMachine$$async_" + body.pos.line) + q""" + final class $name extends _root_.scala.tools.nsc.async.FutureStateMachine($executionContext) { + ${mark(q"""override def apply(tr$$async: _root_.scala.util.Try[_root_.scala.AnyRef]) = ${body}""")} + } + new $name().start().asInstanceOf[${c.macroApplication.tpe}] + """ + } +} + +abstract class FutureStateMachine(execContext: ExecutionContext) extends AsyncStateMachine[Future[AnyRef], Try[AnyRef]] with Function1[Try[AnyRef], Unit] { + Objects.requireNonNull(execContext) + + private val result$async: Promise[AnyRef] = Promise[AnyRef](); + + // FSM translated method + def apply(tr$async: Try[AnyRef]): Unit + + // Required methods + protected var state$async: Int = StateAssigner.Initial + + // scala-async accidentally started catching NonFatal exceptions in: + // https://github.com/scala/scala-async/commit/e3ff0382ae4e015fc69da8335450718951714982#diff-136ab0b6ecaee5d240cd109e2b17ccb2R411 + // This follows the new behaviour but should we fix the regression? + protected def completeFailure(t: Throwable): Unit = result$async.complete(Failure(t)) + protected def completeSuccess(value: AnyRef): Unit = result$async.complete(Success(value)) + protected def onComplete(f: Future[AnyRef]): Unit = f.onComplete(this)(execContext) + protected def getCompleted(f: Future[AnyRef]): Try[AnyRef] = { + if (f.isCompleted) { + f.value.get + } else null + } + protected def tryGet(tr: Try[AnyRef]): AnyRef = tr match { + case Success(value) => + value.asInstanceOf[AnyRef] + case Failure(throwable) => + completeFailure(throwable) + this // sentinel value to indicate the dispatch loop should exit. + } + def start(): Future[AnyRef] = { + // This cast is safe because we know that `def apply` does not consult its argument when `state == 0`. + Future.unit.asInstanceOf[Future[AnyRef]].onComplete(this)(execContext) + result$async.future + } +} diff --git a/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala b/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala new file mode 100644 index 000000000000..8513602aa46d --- /dev/null +++ b/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala @@ -0,0 +1,77 @@ +package scala.tools.nsc +package async + +import java.util.Objects +import java.util.concurrent.{CompletableFuture, Executor} +import java.util.function.BiConsumer + +import scala.language.experimental.macros +import scala.annotation.compileTimeOnly +import scala.reflect.macros.blackbox +import scala.tools.nsc.transform.async.StateAssigner +import scala.tools.partest.async.AsyncStateMachine +import scala.util.{Failure, Success, Try} + +object CompletableFutureAwait { + def async[T](executor: Executor)(body: T): CompletableFuture[T] = macro impl + @compileTimeOnly("[async] `await` must be enclosed in `async`") + def await[T](completableFuture: CompletableFuture[T]): T = ??? + def impl(c: blackbox.Context)(executor: c.Tree)(body: c.Tree): c.Tree = { + import c.universe._ + val awaitSym = typeOf[CompletableFutureAwait.type].decl(TermName("await")) + def mark(t: DefDef): Tree = { + c.internal.markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty) + } + val name = TypeName("stateMachine$$async_" + body.pos.line) + q""" + final class $name extends _root_.scala.tools.nsc.async.CompletableFutureStateMachine($executor) { + ${mark(q"""override def apply(tr$$async: _root_.scala.util.Try[_root_.scala.AnyRef]) = ${body}""")} + } + new $name().start().asInstanceOf[${c.macroApplication.tpe}] + """ + } +} + +abstract class CompletableFutureStateMachine(executor: Executor) extends AsyncStateMachine[CompletableFuture[AnyRef], Try[AnyRef]] with Runnable with BiConsumer[AnyRef, Throwable] { + Objects.requireNonNull(executor) + + protected var result$async: CompletableFuture[AnyRef] = new CompletableFuture[AnyRef](); + + // Adapters + def accept(value: AnyRef, throwable: Throwable): Unit = { + this(if (throwable != null) Failure(throwable) else Success(value)) + } + def run(): Unit = { + apply(null) + } + + // FSM translated method + def apply(tr$async: Try[AnyRef]): Unit + + // Required methods + protected var state$async: Int = StateAssigner.Initial + protected def completeFailure(t: Throwable): Unit = result$async.completeExceptionally(t) + protected def completeSuccess(value: AnyRef): Unit = result$async.complete(value) + protected def onComplete(f: CompletableFuture[AnyRef]): Unit = f.whenCompleteAsync(this) + protected def getCompleted(f: CompletableFuture[AnyRef]): Try[AnyRef] = { + try { + val r = f.getNow(this) + if (r == this) null + else Success(r) + } catch { + case t: Throwable => + Failure(t) + } + } + protected def tryGet(tr: Try[AnyRef]): AnyRef = tr match { + case Success(value) => + value.asInstanceOf[AnyRef] + case Failure(throwable) => + result$async.completeExceptionally(throwable) + this // sentinel value to indicate the dispatch loop should exit. + } + def start(): CompletableFuture[AnyRef] = { + executor.execute(this) + result$async + } +}