From 9a0cbe8c6d573a6ac88383351c3054d87c36edd3 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 1 Feb 2019 17:03:06 +0100 Subject: [PATCH 01/94] make xprompt work in sbt's scalac task --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 72449f3964fa..e0c513c8fc51 100644 --- a/build.sbt +++ b/build.sbt @@ -516,6 +516,7 @@ lazy val commonSettings = instanceSettings ++ clearSourceAndResourceDirectories cleanFiles += (classDirectory in Compile).value, cleanFiles += (target in Compile in doc).value, fork in run := true, + connectInput in run := true, scalacOptions in Compile += "-Ywarn-unused:imports", scalacOptions in Compile in doc ++= Seq( "-doc-footer", "epfl", From 3d09049061a0373cc5caeb780afb27ddd95b8d64 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 30 Jan 2019 11:10:53 +0100 Subject: [PATCH 02/94] Import async --- .../nsc/transform/async/AnfTransform.scala | 420 +++++++++++ .../nsc/transform/async/AsyncAnalysis.scala | 109 +++ .../nsc/transform/async/AsyncNames.scala | 121 ++++ .../nsc/transform/async/AsyncTransform.scala | 256 +++++++ .../nsc/transform/async/AsyncUtils.scala | 24 + .../nsc/transform/async/ExprBuilder.scala | 659 ++++++++++++++++++ .../tools/nsc/transform/async/Lifter.scala | 163 +++++ .../nsc/transform/async/LiveVariables.scala | 313 +++++++++ .../nsc/transform/async/StateAssigner.scala | 23 + .../tools/nsc/transform/async/StateSet.scala | 35 + .../nsc/transform/async/TransformUtils.scala | 652 +++++++++++++++++ .../nsc/transform/async/user/AsyncBase.scala | 111 +++ .../nsc/transform/async/user/AsyncId.scala | 133 ++++ .../transform/async/user/FutureSystem.scala | 194 ++++++ .../async/user/ScalaConcurrentAsync.scala | 27 + .../scala/tools/reflect/FastTrack.scala | 4 +- src/library/scala/Async.scala | 63 ++ .../scala/reflect/internal/Definitions.scala | 4 + .../scala/reflect/internal/StdNames.scala | 2 + 19 files changed, 3312 insertions(+), 1 deletion(-) create mode 100644 src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/Lifter.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/StateSet.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala create mode 100644 src/library/scala/Async.scala diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala new file mode 100644 index 000000000000..41d39ed29b3f --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -0,0 +1,420 @@ +/* + * 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.reflect.internal.Flags + +private[async] trait AnfTransform extends TransformUtils { + import typingTransformers.typingTransform + import u._ + + def anfTransform(tree: Tree, owner: Symbol): Block = { + // Must prepend the () for issue #31. + val block = typecheck(atPos(tree.pos)(Block(List(literalUnit), tree))).setType(tree.tpe) + + sealed abstract class AnfMode + case object Anf extends AnfMode + case object Linearizing extends AnfMode + + val tree1 = adjustTypeOfTranslatedPatternMatches(block, owner) + + var mode: AnfMode = Anf + + object trace { + private var indent = -1 + + private def indentString = " " * indent + + def apply[T](args: Any)(t: => T): T = { + def prefix = mode.toString.toLowerCase + indent += 1 + def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(127) + try { + if(AsyncUtils.trace) + AsyncUtils.trace(s"$indentString$prefix(${oneLine(args)})") + val result = t + if(AsyncUtils.trace) + AsyncUtils.trace(s"$indentString= ${oneLine(result)}") + result + } finally { + indent -= 1 + } + } + } + + // creates a subclass of TypingTransformer + typingTransform(tree1, owner)((tree, api) => { + import api.{atOwner, currentOwner} + + // localTyper + def typed(tree: Tree) = api.typecheck(tree) + def typedAt(exprPos: Position, tree: Tree) = api.typecheck(atPos(exprPos)(tree)) + + def typedAssign(lhs: Tree, varSym: Symbol) = + typedAt(lhs.pos, Assign(Ident(varSym), mkAttributedCastPreservingAnnotations(lhs, varSym.info))) + + object linearize { + def transformToList(tree: Tree): List[Tree] = { + mode = Linearizing; blockToList(api.recur(tree)) + } + + def transformToBlock(tree: Tree): Block = listToBlock(transformToList(tree)) + + def _transformToList(tree: Tree): List[Tree] = trace(tree) { + val stats :+ expr = _anf.transformToList(tree) + def statsExprUnit = { + stats :+ expr :+ typedAt(expr.pos, literalUnit) + } + def statsExprThrow = + stats :+ expr :+ typedAt(expr.pos, Throw(Apply(Select(New(gen.mkAttributedRef(IllegalStateExceptionClass)), nme.CONSTRUCTOR), Nil))) + expr match { + case Apply(fun, args) if isAwait(fun) => + val awaitResType = transformType(expr.tpe) + val valDef = defineVal(name.await(), expr, tree.pos)(awaitResType) + val ref = gen.mkAttributedStableRef(valDef.symbol).setType(awaitResType) + // https://github.com/scala/async/issues/74 + // Use a cast to hide from "pure expression does nothing" error + // TODO avoid creating a ValDef for the result of this await to avoid this tree shape altogether. + // This will require some deeper changes to the later parts of the macro which currently assume regular + // tree structure around `await` calls. + val refNoPureExpr = + if (!isPastErasure && typeEqualsUnit(ref.tpe)) typedAt(tree.pos, gen.mkCast(ref, ref.tpe)) + else atPos(tree.pos)(ref) + + stats :+ valDef :+ refNoPureExpr + + case If(cond, thenp, elsep) => + // If we run the ANF transform post patmat, deal with trees like `(if (cond) jump1(){String} else jump2(){String}){String}` + // as though it was typed with `Unit`. + def isPatMatGeneratedJump(t: Tree): Boolean = t match { + case Block(_, expr) => isPatMatGeneratedJump(expr) + case If(_, thenp, elsep) => isPatMatGeneratedJump(thenp) && isPatMatGeneratedJump(elsep) + case _: Apply if isLabel(t.symbol) => true + case _ => false + } + if (isPatMatGeneratedJump(expr)) + assignUnitType(expr) + + // if type of if-else is Unit don't introduce assignment, + // but add Unit value to bring it into form expected by async transform + if (typeEqualsUnit(expr.tpe)) { + statsExprUnit + } else if (typeEqualsNothing(expr.tpe)) { + statsExprThrow + } else { + val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos) + + def branchWithAssign(t: Tree): Tree = { + t match { + case MatchEnd(ld) => + deriveLabelDef(ld, branchWithAssign) + case blk @ Block(thenStats, thenExpr) => + assignUnitType(treeCopy.Block(blk, thenStats, branchWithAssign(thenExpr))) + case _ => + typedAssign(t, varDef.symbol) + } + } + val ifWithAssign = assignUnitType(treeCopy.If(tree, cond, branchWithAssign(thenp), branchWithAssign(elsep))) + stats :+ varDef :+ ifWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) + } + case ld @ LabelDef(name, params, rhs) => + if (isUnitType(ld.symbol.info.resultType)) statsExprUnit + else stats :+ expr + + case Match(scrut, cases) => + // if type of match is Unit don't introduce assignment, + // but add Unit value to bring it into form expected by async transform + if (typeEqualsUnit(expr.tpe)) { + statsExprUnit + } else if (typeEqualsNothing(expr.tpe)) { + statsExprThrow + } else { + val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos) + val casesWithAssign = cases map { + case cd@CaseDef(pat, guard, body) => + def bodyWithAssign(t: Tree): Tree = { + t match { + case MatchEnd(ld) => deriveLabelDef(ld, bodyWithAssign) + case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, bodyWithAssign(caseExpr))) + case _ => typedAssign(t, varDef.symbol) + } + } + assignUnitType(treeCopy.CaseDef(cd, pat, guard, bodyWithAssign(body))) + } + val matchWithAssign = assignUnitType(treeCopy.Match(tree, scrut, casesWithAssign)) + require(matchWithAssign.tpe != null, matchWithAssign) + stats :+ varDef :+ matchWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) + } + case _ => + stats :+ expr + } + } + + def defineVar(name: TermName, tp: Type, pos: Position): ValDef = { + val sym = currentOwner.newTermSymbol(name, pos, Flags.MUTABLE | Flags.SYNTHETIC).setInfo(uncheckedBounds(transformType(tp))) + ValDef(sym, mkZero(uncheckedBounds(tp), pos)).setType(NoType).setPos(pos) + } + } + + def defineVal(name: TermName, lhs: Tree, pos: Position)(tp: Type = uncheckedBounds(transformType(lhs.tpe))): ValDef = { + val sym = currentOwner.newTermSymbol(name, pos, Flags.SYNTHETIC).setInfo(tp) + + val lhsOwned = lhs.changeOwner((currentOwner, sym)) + val rhs = + if (isPastErasure && isUnitType(tp)) Block(lhsOwned :: Nil, literalUnit) + else lhsOwned + ValDef(sym, rhs).setType(NoType).setPos(pos) + + } + + object _anf { + import treeInfo.Applied + + def transformToList(tree: Tree): List[Tree] = { + mode = Anf; blockToList(api.recur(tree)) + } + + def _transformToList(tree: Tree): List[Tree] = trace(tree) { + if (!containsAwait(tree)) { + tree match { + case Block(stats, expr) => + // avoids nested block in `while(await(false)) ...`. + // TODO I think `containsAwait` really should return true if the code contains a label jump to an enclosing + // while/doWhile and there is an await *anywhere* inside that construct. + stats :+ expr + case _ => List(tree) + } + } else tree match { + case Select(qual, sel) => + val stats :+ expr = linearize.transformToList(qual) + stats :+ treeCopy.Select(tree, expr, sel) + + case Throw(expr) => + val stats :+ expr1 = linearize.transformToList(expr) + stats :+ treeCopy.Throw(tree, expr1) + + case Typed(expr, tpt) => + val stats :+ expr1 = linearize.transformToList(expr) + stats :+ treeCopy.Typed(tree, expr1, tpt) + + case Applied(fun, targs, argss) if argss.nonEmpty => + // we can assume that no await call appears in a by-name argument position, + // this has already been checked. + val funStats :+ simpleFun = linearize.transformToList(fun) + val (argStatss, argExprss): (List[List[List[Tree]]], List[List[Tree]]) = + mapArgumentss[List[Tree]](fun, argss) { + case Arg(expr, byName, _) if byName /*|| isPure(expr) TODO */ => (Nil, expr) + case Arg(expr, _, argName) => + linearize.transformToList(expr) match { + case stats :+ expr1 => + val valDef = defineVal(name.freshen(argName), expr1, expr1.pos)() + require(valDef.tpe != null, valDef) + val stats1 = stats :+ valDef + (stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol)))) + } + } + + def copyApplied(tree: Tree, depth: Int): Tree = { + tree match { + case TypeApply(_, targs) => treeCopy.TypeApply(tree, simpleFun, targs) + case _ if depth == 0 => simpleFun + case Apply(fun, args) => + val newTypedArgs = map2(args.map(_.pos), argExprss(depth - 1))((pos, arg) => typedAt(pos, arg)) + treeCopy.Apply(tree, copyApplied(fun, depth - 1), newTypedArgs) + } + } + + val typedNewApply = copyApplied(tree, argss.length) + + funStats ++ argStatss.flatten.flatten :+ typedNewApply + + case Block(stats, expr) => + val stats1 = stats.flatMap(linearize.transformToList).filterNot(isLiteralUnit) + val exprs1 = linearize.transformToList(expr) + val trees = stats1 ::: exprs1 + def groupsEndingWith[T](ts: List[T])(f: T => Boolean): List[List[T]] = if (ts.isEmpty) Nil else { + ts.indexWhere(f) match { + case -1 => List(ts) + case i => + val (ts1, ts2) = ts.splitAt(i + 1) + ts1 :: groupsEndingWith(ts2)(f) + } + } + val matchGroups = groupsEndingWith(trees){ case MatchEnd(_) => true; case _ => false } + val trees1 = matchGroups.flatMap(eliminateMatchEndLabelParameter) + val result = trees1 flatMap { + case Block(stats, expr) => stats :+ expr + case t => t :: Nil + } + result + + case ValDef(mods, name, tpt, rhs) => + if (containsAwait(rhs)) { + val stats :+ expr = atOwner(currentOwner.owner)(linearize.transformToList(rhs)) + stats.foreach(_.changeOwner((currentOwner, currentOwner.owner))) + stats :+ treeCopy.ValDef(tree, mods, name, tpt, expr) + } else List(tree) + + case Assign(lhs, rhs) => + val stats :+ expr = linearize.transformToList(rhs) + stats :+ treeCopy.Assign(tree, lhs, expr) + + case If(cond, thenp, elsep) => + val condStats :+ condExpr = linearize.transformToList(cond) + val thenBlock = linearize.transformToBlock(thenp) + val elseBlock = linearize.transformToBlock(elsep) + condStats :+ treeCopy.If(tree, condExpr, thenBlock, elseBlock) + + case Match(scrut, cases) => + val scrutStats :+ scrutExpr = linearize.transformToList(scrut) + val caseDefs = cases map { + case CaseDef(pat, guard, body) => + // extract local variables for all names bound in `pat`, and rewrite `body` + // to refer to these. + // TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`. + val block = linearize.transformToBlock(body) + val (valDefs, mappings) = (pat collect { + case b@Bind(bindName, _) => + val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)() + vd.symbol.updateAttachment(SyntheticBindVal) + (vd, (b.symbol, vd.symbol)) + }).unzip + val (from, to) = mappings.unzip + val b@Block(stats1, expr1) = block.substituteSymbols(from, to).asInstanceOf[Block] + val newBlock = treeCopy.Block(b, valDefs ++ stats1, expr1) + treeCopy.CaseDef(tree, pat, guard, newBlock) + } + scrutStats :+ treeCopy.Match(tree, scrutExpr, caseDefs) + + case LabelDef(name, params, rhs) => + if (!isPastErasure && isUnitType(tree.symbol.info)) // erasure has already inserted unit + List(treeCopy.LabelDef(tree, name, params, typed(Block(linearize.transformToList(rhs), literalUnit))).setSymbol(tree.symbol)) + else + List(treeCopy.LabelDef(tree, name, params, typed(listToBlock(linearize.transformToList(rhs)))).setSymbol(tree.symbol)) + + case TypeApply(fun, targs) => + val funStats :+ simpleFun = linearize.transformToList(fun) + funStats :+ treeCopy.TypeApply(tree, simpleFun, targs) + + case _ => + List(tree) + } + } + } + + // Replace the label parameters on `matchEnd` with use of a `matchRes` temporary variable + // + // CaseDefs are translated to labels without parameters. A terminal label, `matchEnd`, accepts + // a parameter which is the result of the match (this is regular, so even Unit-typed matches have this). + // + // For our purposes, it is easier to: + // - extract a `matchRes` variable + // - rewrite the terminal label def to take no parameters, and instead read this temp variable + // - change jumps to the terminal label to an assignment and a no-arg label application + def eliminateMatchEndLabelParameter(statsExpr: List[Tree]): List[Tree] = { + val caseDefToMatchResult = collection.mutable.Map[Symbol, Symbol]() + + val matchResults = collection.mutable.Buffer[Tree]() + def modifyLabelDef(ld: LabelDef): (Tree, Tree) = { + val param = ld.params.head + + def unitLabelDef = { + setUnitMethodInfo(ld.symbol) + assignUnitType(treeCopy.LabelDef(ld, ld.name, Nil, typed(literalUnit))) + } + + if (isUnitType(ld.params.head.tpe)) { + // Unit typed match: eliminate the label def parameter, but don't create a matchres temp variable to + // store the result for cleaner generated code. + caseDefToMatchResult(ld.symbol) = NoSymbol + (unitLabelDef, substituteTrees(ld.rhs, param.symbol :: Nil, typed(literalUnit) :: Nil)) + } else { + // Otherwise, create the matchres var. We'll callers of the label def below. + // Remember: we're iterating through the statement sequence in reverse, so we'll get + // to the LabelDef and mutate `matchResults` before we'll get to its callers. + val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos) + matchResults += matchResult + caseDefToMatchResult(ld.symbol) = matchResult.symbol + (unitLabelDef, ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)) + } + } + val statsExpr0 = statsExpr.reverse.flatMap { + case ld @ LabelDef(_, param :: Nil, _) => + val (ld1, after) = modifyLabelDef(ld) + List(after, ld1) + case a @ ValDef(mods, name, tpt, ld @ LabelDef(_, param :: Nil, _)) => + val (ld1, after) = modifyLabelDef(ld) + List(treeCopy.ValDef(a, mods, name, tpt, after), ld1) + case t => + if (caseDefToMatchResult.isEmpty) t :: Nil + else typingTransform(t)((tree, api) => { + def typedPos(pos: Position)(t: Tree): Tree = api.typecheck(atPos(pos)(t)) + def transform(tree: Tree) = api.recur(tree) + def superTransform = api.default(tree) + + tree match { + case Apply(fun, arg :: Nil) if isLabel(fun.symbol) && caseDefToMatchResult.contains(fun.symbol) => + val temp = caseDefToMatchResult(fun.symbol) + if (temp == NoSymbol) + typedPos(tree.pos)(Block(transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil))) + else + // setType needed for LateExpansion.shadowingRefinedType test case. There seems to be an inconsistency + // in the trees after pattern matcher. + // TODO miminize the problem in patmat and fix in scalac. + typedPos(tree.pos)(Block(Assign(Ident(temp), transform(arg.setType(transformType(fun.tpe.paramLists.head.head.info)))) :: Nil, treeCopy.Apply(tree, fun, Nil))) + case Block(stats, expr: Apply) if isLabel(expr.symbol) => + superTransform match { + case Block(stats0, Block(stats1, expr1)) => + // flatten the block returned by `case Apply` above into the enclosing block for + // cleaner generated code. + treeCopy.Block(tree, stats0 ::: stats1, expr1) + case t => t + } + case _ => + superTransform + } + }) :: Nil + } + matchResults.toList match { + case _ if caseDefToMatchResult.isEmpty => + statsExpr // return the original trees if nothing changed + case Nil => + statsExpr0.reverse :+ literalUnit // must have been a unit-typed match, no matchRes variable to definne or refer to + case r1 :: Nil => + // { var matchRes = _; ....; matchRes } + (r1 +: statsExpr0.reverse) :+ atPos(tree.pos)(gen.mkAttributedIdent(r1.symbol)) + case _ => error(tree.pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr + } + } + + def anfLinearize(tree: Tree): Block = { + val trees: List[Tree] = mode match { + case Anf => _anf._transformToList(tree) + case Linearizing => linearize._transformToList(tree) + } + listToBlock(trees) + } + + tree match { + case _: ValDef | _: DefDef | _: Function | _: ClassDef | _: TypeDef => + atOwner(tree.symbol)(anfLinearize(tree)) + case _: ModuleDef => + atOwner(tree.symbol.asModule.moduleClass orElse tree.symbol)(anfLinearize(tree)) + case _ => + anfLinearize(tree) + } + }).asInstanceOf[Block] + } +} + +object SyntheticBindVal diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala new file mode 100644 index 000000000000..7fc33e979039 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -0,0 +1,109 @@ +/* + * 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.collection.mutable.ListBuffer +import scala.reflect.internal.Flags + +trait AsyncAnalysis extends TransformUtils { + import u._ + + /** + * Analyze the contents of an `async` block in order to: + * - Report unsupported `await` calls under nested templates, functions, by-name arguments. + * + * Must be called on the original tree, not on the ANF transformed tree. + */ + def reportUnsupportedAwaits(tree: Tree): Unit = { + val analyzer = new UnsupportedAwaitAnalyzer + analyzer.traverse(tree) + // analyzer.hasUnsupportedAwaits // XB: not used?! + } + + private class UnsupportedAwaitAnalyzer extends AsyncTraverser { + var hasUnsupportedAwaits = false + + override def nestedClass(classDef: ClassDef): Unit = { + val kind = if (classDef.symbol.asClass.isTrait) "trait" else "class" + reportUnsupportedAwait(classDef, s"nested $kind") + } + + override def nestedModule(module: ModuleDef): Unit = { + reportUnsupportedAwait(module, "nested object") + } + + override def nestedMethod(defDef: DefDef): Unit = { + reportUnsupportedAwait(defDef, "nested method") + } + + override def byNameArgument(arg: Tree): Unit = { + reportUnsupportedAwait(arg, "by-name argument") + } + + override def function(function: Function): Unit = { + reportUnsupportedAwait(function, "nested function") + } + + override def patMatFunction(tree: Match): Unit = { + reportUnsupportedAwait(tree, "nested function") + } + + override def traverse(tree: Tree): Unit = { + tree match { + case Try(_, _, _) if containsAwait(tree) => + reportUnsupportedAwait(tree, "try/catch") + super.traverse(tree) + case Return(_) => + abort(tree.pos, "return is illegal within a async block") + case DefDef(mods, _, _, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => + reportUnsupportedAwait(tree, "lazy val initializer") + case ValDef(mods, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => + reportUnsupportedAwait(tree, "lazy val initializer") + case CaseDef(_, guard, _) if guard exists isAwait => + // TODO lift this restriction + reportUnsupportedAwait(tree, "pattern guard") + case _ => + super.traverse(tree) + } + } + + /** + * @return true, if the tree contained an unsupported await. + */ + private def reportUnsupportedAwait(tree: Tree, whyUnsupported: String): Boolean = { + val badAwaits = ListBuffer[Tree]() + object traverser extends Traverser { + override def traverse(tree: Tree): Unit = { + if (!isAsync(tree)) + super.traverse(tree) + tree match { + case rt: RefTree if isAwait(rt) => + badAwaits += rt + case _ => + } + } + } + traverser(tree) + badAwaits foreach { + tree => + reportError(tree.pos, s"await must not be used under a $whyUnsupported.") + } + badAwaits.nonEmpty + } + + private def reportError(pos: Position, msg: String): Unit = { + hasUnsupportedAwaits = true + abort(pos, msg) + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala new file mode 100644 index 000000000000..2eabf996ea95 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -0,0 +1,121 @@ +/* + * 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 java.util.concurrent.atomic.AtomicInteger + +import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.reflect.api.Names + +/** + * A per-global cache of names needed by the Async macro. + */ +final class AsyncNames[U <: Names with Singleton](val u: U) { + self => + import u._ + + abstract class NameCache[N <: U#Name](base: String) { + val cached = new ArrayBuffer[N]() + protected def newName(s: String): N + def apply(i: Int): N = { + if (cached.isDefinedAt(i)) cached(i) + else { + assert(cached.length == i) + val name = newName(freshenString(base, i)) + cached += name + name + } + } + } + + final class TermNameCache(base: String) extends NameCache[U#TermName](base) { + override protected def newName(s: String): U#TermName = TermName(s) + } + final class TypeNameCache(base: String) extends NameCache[U#TypeName](base) { + override protected def newName(s: String): U#TypeName = TypeName(s) + } + private val matchRes: TermNameCache = new TermNameCache("match") + private val ifRes: TermNameCache = new TermNameCache("if") + private val await: TermNameCache = new TermNameCache("await") + + private val result = TermName("result$async") + private val completed: TermName = TermName("completed$async") + private val apply = TermName("apply") + private val stateMachine = TermName("stateMachine$async") + private val stateMachineT = stateMachine.toTypeName + private val state: u.TermName = TermName("state$async") + private val execContext = TermName("execContext$async") + private val tr: u.TermName = TermName("tr$async") + private val t: u.TermName = TermName("throwable$async") + + final class NameSource[N <: U#Name](cache: NameCache[N]) { + private val count = new AtomicInteger(0) + def apply(): N = cache(count.getAndIncrement()) + } + + class AsyncName { + final val matchRes = new NameSource[U#TermName](self.matchRes) + final val ifRes = new NameSource[U#TermName](self.ifRes) + final val await = new NameSource[U#TermName](self.await) + final val completed = self.completed + final val result = self.result + final val apply = self.apply + final val stateMachine = self.stateMachine + final val stateMachineT = self.stateMachineT + final val state: u.TermName = self.state + final val execContext = self.execContext + final val tr: u.TermName = self.tr + final val t: u.TermName = self.t + + private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]() + private val freshened = mutable.HashSet[Name]() + + final def freshenIfNeeded(name: TermName): TermName = { + seenPrefixes.getOrNull(name) match { + case null => + seenPrefixes.put(name, new AtomicInteger()) + name + case counter => + freshen(name, counter) + } + } + final def freshenIfNeeded(name: TypeName): TypeName = { + seenPrefixes.getOrNull(name) match { + case null => + seenPrefixes.put(name, new AtomicInteger()) + name + case counter => + freshen(name, counter) + } + } + final def freshen(name: TermName): TermName = { + val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) + freshen(name, counter) + } + final def freshen(name: TypeName): TypeName = { + val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) + freshen(name, counter) + } + private def freshen(name: TermName, counter: AtomicInteger): TermName = { + if (freshened.contains(name)) name + else TermName(freshenString(name.toString, counter.incrementAndGet())) + } + private def freshen(name: TypeName, counter: AtomicInteger): TypeName = { + if (freshened.contains(name)) name + else TypeName(freshenString(name.toString, counter.incrementAndGet())) + } + } + + private def freshenString(name: String, counter: Int): String = name.toString + "$async$" + counter +} diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala new file mode 100644 index 000000000000..149ceb816d76 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -0,0 +1,256 @@ +/* + * 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.AsyncBase +import scala.reflect.internal.{Flags, SymbolTable} +import scala.tools.nsc.Global + +abstract class AsyncTransform(val asyncBase: AsyncBase, val u: SymbolTable) extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables { + import u._ + import typingTransformers.{TypingTransformApi, typingTransform} + + def asyncTransform(body: Tree, execContext: Tree, enclosingOwner: Symbol, asyncPos: Position)(resultType: Type): Tree = { + markContainsAwait(body) // TODO AM: is this needed? + reportUnsupportedAwaits(body) + + // Transform to A-normal form: + // - no await calls in qualifiers or arguments, + // - if/match only used in statement position. + val anfTree0: Block = anfTransform(body, enclosingOwner) + + val anfTree = futureSystemOps.postAnfTransform(anfTree0) + + cleanupContainsAwaitAttachments(anfTree) + markContainsAwait(anfTree) + + // We annotate the type of the whole expression as `T @uncheckedBounds` so as not to introduce + // warnings about non-conformant LUBs. See SI-7694 + val resultTypeTag = WeakTypeTag(uncheckedBounds(transformType(resultType))) + + val applyDefDefDummyBody: DefDef = apply1ToUnitDefDef(tryAny) + + // Create `ClassDef` of state machine with empty method bodies for `resume` and `apply`. + // TODO AM: can we only create the symbol for the state machine class for now and then type check the assembled whole later, + // instead of splicing stuff in (spliceMethodBodies)? + val stateMachine: ClassDef = { + val body: List[Tree] = { + val stateVar = ValDef(Modifiers(Flags.MUTABLE | Flags.PRIVATE | Flags.LOCAL), name.state, TypeTree(definitions.IntTpe), Literal(Constant(StateAssigner.Initial))) + val resultAndAccessors = + mkMutableField(transformType(futureSystemOps.promType(uncheckedBounds(resultType))), name.result, futureSystemOps.createProm[Nothing](resultTypeTag).tree) + val execContextValDef = + mkField(execContext.tpe, name.execContext, execContext) + + List(stateVar) ++ resultAndAccessors ++ execContextValDef ++ List(applyDefDefDummyBody, apply0DefDef) + } + + val customParents = futureSystemOps.stateMachineClassParents map transformType + // 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.asClass.isTrait) + + // We extend () => Unit so we can pass this class as the by-name argument to `Future.apply`. + // See SI-1247 for the the optimization that avoids creation. + val funParents = List(function1ToUnit(tryAny, useClass), function0ToUnit) + + // TODO AM: after erasure we have to change the order of these parents etc + val templ = gen.mkTemplate(transformParentTypes(customParents ::: funParents).map(TypeTree(_)), noSelfType, NoMods, List(Nil), body) + + // TODO AM: can we skip the type checking and just create a symbol? + val classSym = enclosingOwner.newClassSymbol(name.stateMachineT, asyncPos, 0) + val cd = ClassDef(NoMods, name.stateMachineT, Nil, templ).setSymbol(classSym).asInstanceOf[typingTransformers.global.ClassDef] + classSym.setInfo(typingTransformers.callsiteTyper.namer.monoTypeCompleter(cd)) + typingTransformers.callsiteTyper.typedClassDef(atPos(asyncPos)(cd)).asInstanceOf[ClassDef] + } + + val asyncBlock: AsyncBlock = { + val symLookup = SymLookup(stateMachine.symbol, applyDefDefDummyBody.vparamss.head.head.symbol) + buildAsyncBlock(anfTree, symLookup) + } + + val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) + + // live variables analysis + // the result map indicates in which states a given field should be nulled out + val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields) + + for ((state, flds) <- assignsOf) { + val assigns = flds.map { fld => + val fieldSym = fld.symbol + val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info, asyncPos)) + val nulled = nullOut(fieldSym) + if (isLiteralUnit(nulled)) assign + else Block(nulled :: Nil, assign) + } + val asyncState = asyncBlock.asyncStates.find(_.state == state).get + asyncState.stats = assigns ++ asyncState.stats + } + + def startStateMachine: Tree = { + val stateMachineSpliced: Tree = + spliceMethodBodies(liftedFields, stateMachine, atPos(asyncPos)(asyncBlock.onCompleteHandler(resultTypeTag)), enclosingOwner) + + val applyCtor = + typingTransform(Apply(Select(New(Ident(stateMachine.symbol)), nme.CONSTRUCTOR), Nil))((tree, api) => api.typecheck(tree)) + + val (stateMachineUsingOuter, newStateMachine) = + if (!isPastErasure) (stateMachineSpliced, applyCtor) + else { + // Since explicit outers has already run (it happens before erasure), we must run it ourselves on the class we're synthesizing. + // The state machine class is going to be lifted out by the flatten phase, but expressions contained in it + // will likely still need to access the outer class's instance. + // Thus, we add the standard outer argument to the constructor and supply it when instantiating the state machine. + // Lambdalift will also look for this and transform appropriately. + val global: u.type with Global = u.asInstanceOf[u.type with Global] + + stateMachineSpliced foreach { + case dt: DefTree if dt.hasExistingSymbol => // TODO AM: why can't we skip symbols that hasTypeAt(currentRun.explicitouterPhase.id) + val sym = dt.symbol + val classSym = sym.asInstanceOf[global.Symbol] + val newInfo = global.explicitOuter.transformInfo(classSym, classSym.info) + // we can't go back to explicit outer phase to retro-actively un-erase our current info and then add explicit outers, + // we just have to run the explicitOuter info transform now (during erasure) and hope for the best + if (newInfo ne sym.info) + classSym.setInfo(newInfo) + case _ => + } + + val explicitOuters = new global.explicitOuter.ExplicitOuterTransformer(typingTransformers.callsiteTyper.context.unit.asInstanceOf[global.CompilationUnit]) + + val stateMachineWithOuters = explicitOuters.transform(stateMachineSpliced.asInstanceOf[global.Tree]) + + val newStateMachine = explicitOuters.atOwner(stateMachine.symbol.owner.asInstanceOf[global.Symbol]) { + explicitOuters.transform(applyCtor.asInstanceOf[global.Tree]) + } + + (stateMachineWithOuters, newStateMachine) + } + + def selectStateMachine(selection: TermName) = Select(Ident(name.stateMachine), selection) + def selectStateMachineResult = + applyNilAfterUncurry(selectStateMachine(name.result)) + + Block(List[Tree]( + stateMachineUsingOuter, + ValDef(NoMods, name.stateMachine, TypeTree(), newStateMachine), + spawn(Apply(selectStateMachine(name.apply), Nil), selectStateMachine(name.execContext))), + promiseToFuture(selectStateMachineResult, resultType)) + } + + val isSimple = asyncBlock.asyncStates.size == 1 + val result = + if (isSimple) spawn(body, execContext) // generate lean code for the simple case of `async { 1 + 1 }` + else startStateMachine + + if(AsyncUtils.verbose) { + val location = try body.pos.source.path catch { case _: UnsupportedOperationException => body.pos.toString } + logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) + } + futureSystemOps.dot(enclosingOwner, body).foreach(f => f(asyncBlock.toDot)) + cleanupContainsAwaitAttachments(result) + } + + def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { + AsyncUtils.vprintln(s"In file '$location':") + AsyncUtils.vprintln(s"ANF transform expands to:\n $anfTree") + states foreach (s => AsyncUtils.vprintln(s)) + AsyncUtils.vprintln("===== DOT =====") + AsyncUtils.vprintln(block.toDot) + } + + /** + * Build final `ClassDef` tree of state machine class. + * + * @param liftables trees of definitions that are lifted to fields of the state machine class + * @param tree `ClassDef` tree of the state machine class + * @param applyBody tree of onComplete handler (`apply` method) + * @return transformed `ClassDef` tree of the state machine class + */ + def spliceMethodBodies(liftables: List[Tree], tree: ClassDef, applyBody: Tree, enclosingOwner: Symbol): Tree = { + val liftedSyms = liftables.map(_.symbol).toSet + val stateMachineClass = tree.symbol + liftedSyms.foreach { + sym => + if (sym != null) { + sym.owner = stateMachineClass + if (sym.isModule) + sym.asModule.moduleClass.owner = stateMachineClass + } + } + // Replace the ValDefs in the splicee with Assigns to the corresponding lifted + // fields. Similarly, replace references to them with references to the field. + // + // This transform will only be run on the RHS of `def foo`. + val useFields: (Tree, TypingTransformApi) => Tree = (tree, api) => tree match { + case _ if api.currentOwner == stateMachineClass => + api.default(tree) + case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => + api.atOwner(api.currentOwner) { + val fieldSym = tree.symbol + if (fieldSym.asTerm.isLazy) literalUnit + else { + val lhs = atPos(tree.pos) { + gen.mkAttributedStableRef(thisType(fieldSym.owner.asClass), fieldSym) + } + assignUnitType(treeCopy.Assign(tree, lhs, api.recur(rhs))).changeOwner((fieldSym, api.currentOwner)) + } + } + case _: DefTree if liftedSyms(tree.symbol) => + EmptyTree + case Ident(name) if liftedSyms(tree.symbol) => + val fieldSym = tree.symbol + atPos(tree.pos) { + gen.mkAttributedStableRef(thisType(fieldSym.owner.asClass), fieldSym).setType(tree.tpe) + } + case _ => + api.default(tree) + } + + val liftablesUseFields = liftables.map { + case vd: ValDef if !vd.symbol.asTerm.isLazy => vd + case x => typingTransform(x, stateMachineClass)(useFields) + } + + tree.children.foreach(_.changeOwner((enclosingOwner, tree.symbol))) + val treeSubst = tree + + /* Fixes up DefDef: use lifted fields in `body` */ + def fixup(dd: DefDef, body: Tree, api: TypingTransformApi): Tree = { + val spliceeAnfFixedOwnerSyms = body + val newRhs = typingTransform(spliceeAnfFixedOwnerSyms, dd.symbol)(useFields) + val newRhsTyped = api.atOwner(dd, dd.symbol)(api.typecheck(newRhs)) + treeCopy.DefDef(dd, dd.mods, dd.name, dd.tparams, dd.vparamss, dd.tpt, newRhsTyped) + } + + liftablesUseFields.foreach(t => if (t.symbol != null) stateMachineClass.info.decls.enter(t.symbol)) + + // TODO AM: refine the resetting of the lazy flag -- this is so that local lazy vals that are lifted to the class + // actually get their LazyRef allocated to the var that holds the lazy val's reference + if (isPastErasure) + liftablesUseFields.foreach(t => if (t.symbol != null) t.symbol.resetFlag(Flags.LAZY)) + + val result0 = transformAt(treeSubst) { + case t@Template(parents, self, stats) => + (api: TypingTransformApi) => { + treeCopy.Template(t, parents, self, liftablesUseFields ++ stats) + } + } + val result = transformAt(result0) { + case dd@DefDef(_, name.apply, _, List(List(_)), _, _) if dd.symbol.owner == stateMachineClass => + (api: TypingTransformApi) => + val typedTree = fixup(dd, applyBody.changeOwner((enclosingOwner, dd.symbol)), api) + typedTree + } + result + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala new file mode 100644 index 000000000000..8035f1f62759 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala @@ -0,0 +1,24 @@ +/* + * 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 + +object AsyncUtils { + private def enabled(level: String) = sys.props.getOrElse(s"scala.async.$level", "false").equalsIgnoreCase("true") + + private[async] val verbose = enabled("debug") + private[async] val trace = enabled("trace") + + @inline private[async] def vprintln(s: => Any): Unit = if (verbose) println(s"[async] $s") + + @inline private[async] def trace(s: => Any): Unit = if (trace) println(s"[async] $s") +} diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala new file mode 100644 index 000000000000..e730c66332c5 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -0,0 +1,659 @@ +/* + * 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 java.util.function.IntUnaryOperator + +import user.FutureSystem +import scala.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.language.existentials + +trait ExprBuilder extends TransformUtils { + import u._ + + lazy val futureSystem: FutureSystem = asyncBase.futureSystem + lazy val futureSystemOps: futureSystem.Ops[u.type] = futureSystem.mkOps(u, isPastErasure) + + def nullOut(fieldSym: Symbol): Tree = + asyncBase.nullOut(u)(Expr[String](Literal(Constant(fieldSym.name.toString))), Expr[Any](Ident(fieldSym))).tree + + def spawn(tree: Tree, execContext: Tree): Tree = + futureSystemOps.future(Expr[Unit](tree))(Expr[futureSystem.ExecContext](execContext)).tree + + def promiseToFuture(prom: Tree, resTp: Type): Tree = + futureSystemOps.promiseToFuture(Expr[Nothing](prom))(WeakTypeTag(resTp)).tree + + def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = u.Expr[T](rootMirror, FixedMirrorTreeCreator(rootMirror, tree)) + def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = u.WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, tpe)) + + lazy val tryAny= transformType(futureSystemOps.tryType(definitions.AnyTpe)) + + private val stateAssigner = new StateAssigner + private val labelDefStates = collection.mutable.Map[Symbol, Int]() + + trait AsyncState { + def state: Int + + def nextStates: Array[Int] + + def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef + + def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = None + + var stats: List[Tree] + + def treeThenStats(tree: Tree): List[Tree] = + adaptToUnitIgnoringNothing(tree :: stats) :: Nil + + final def allStats: List[Tree] = this match { + case a: AsyncStateWithAwait => treeThenStats(a.awaitable.resultValDef) + case _ => stats + } + + final def body: Tree = stats match { + case stat :: Nil => stat + case init :+ last => Block(init, last) + } + } + + /** A sequence of statements that concludes with a unconditional transition to `nextState` */ + final class SimpleAsyncState(var stats: List[Tree], val state: Int, nextState: Int, symLookup: SymLookup) + extends AsyncState { + + val nextStates: Array[Int] = + Array(nextState) + + def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { + mkHandlerCase(state, treeThenStats(mkStateTree(nextState, symLookup))) + } + + override val toString: String = + s"AsyncState #$state, next = $nextState" + } + + /** A sequence of statements with a conditional transition to the next state, which will represent + * a branch of an `if` or a `match`. + */ + final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: Array[Int]) extends AsyncState { + override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = + mkHandlerCase(state, stats) + + override val toString: String = + s"AsyncStateWithoutAwait #$state, nextStates = ${nextStates.toList}" + } + + /** A sequence of statements that concludes with an `await` call. The `onComplete` + * handler will unconditionally transition to `nextState`. + */ + final class AsyncStateWithAwait(var stats: List[Tree], val state: Int, val onCompleteState: Int, nextState: Int, + val awaitable: Awaitable, symLookup: SymLookup) + extends AsyncState { + + val nextStates: Array[Int] = + Array(nextState) + + override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { + val fun = This(tpnme.EMPTY) + val callOnComplete = futureSystemOps.onComplete[Any, Unit](Expr[futureSystem.Fut[Any]](awaitable.expr), + Expr[futureSystem.Tryy[Any] => Unit](fun), Expr[futureSystem.ExecContext](Ident(name.execContext))).tree + val tryGetOrCallOnComplete: List[Tree] = + if (futureSystemOps.continueCompletedFutureOnSameThread) { + val tempName = name.completed + val initTemp = ValDef(NoMods, tempName, TypeTree(tryAny), futureSystemOps.getCompleted[Any](Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) + val null_ne = Select(Literal(Constant(null)), TermName("ne")) + val ifTree = + If(Apply(null_ne, Ident(tempName) :: Nil), + adaptToUnit(ifIsFailureTree[T](Ident(tempName)) :: Nil), + Block(toList(callOnComplete), Return(literalUnit))) + + initTemp :: ifTree :: Nil + } else + toList(callOnComplete) ::: Return(literalUnit) :: Nil + mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup)) ++ tryGetOrCallOnComplete) + } + + /* if (tr.isFailure) + * result.complete(tr.asInstanceOf[Try[T]]) + * else { + * = tr.get.asInstanceOf[] + * + * + * } + */ + def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) = { + val tryyGet = + // no need to cast past erasure, and in fact we should leave boxing or casting to the erasure typer + if (isPastErasure) futureSystemOps.tryyGet[Any](Expr[futureSystem.Tryy[Any]](tryReference)).tree + else mkAsInstanceOf(futureSystemOps.tryyGet[Any](Expr[futureSystem.Tryy[Any]](tryReference)).tree, awaitable.resultType) + + + val getAndUpdateState = Block(List(Assign(Ident(awaitable.resultName), tryyGet)), mkStateTree(nextState, symLookup)) + if (emitTryCatch) { + If(futureSystemOps.tryyIsFailure(Expr[futureSystem.Tryy[T]](tryReference)).tree, + Block(toList(futureSystemOps.completeProm[T]( + Expr[futureSystem.Prom[T]](symLookup.selectResult), + Expr[futureSystem.Tryy[T]](mkAsInstanceOf(tryReference, transformType(futureSystemOps.tryType(implicitly[WeakTypeTag[T]].tpe))))).tree), // TODO: this is pretty bonkers... implicitly[WeakTypeTag[T]].tpe == resultType + Return(literalUnit)), + getAndUpdateState + ) + } else { + getAndUpdateState + } + } + + override def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = { + Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree[T](Ident(symLookup.applyTrParam))))) + } + + override val toString: String = + s"AsyncStateWithAwait #$state, next = $nextState" + } + + /* + * Builder for a single state of an async expression. + */ + final class AsyncStateBuilder(state: Int, private val symLookup: SymLookup) { + /* Statements preceding an await call. */ + private val stats = ListBuffer[Tree]() + /** The state of the target of a LabelDef application (while loop jump) */ + private var nextJumpState: Option[Int] = None + private var nextJumpSymbol: Symbol = NoSymbol + def effectiveNextState(nextState: Int) = + nextJumpState.orElse(if (nextJumpSymbol == NoSymbol) None + else Some(stateIdForLabel(nextJumpSymbol))).getOrElse(nextState) + + def +=(stat: Tree): this.type = { + // Allow `()` (occurs in do/while) + assert(isLiteralUnit(stat) || nextJumpState.isEmpty, s"statement appeared after a label jump: $stat") + + def addStat() = stats += stat + stat match { + case Apply(fun, args) if isLabel(fun.symbol) => + // labelDefStates belongs to the current ExprBuilder + labelDefStates get fun.symbol match { + case opt@Some(nextState) => + // A backward jump + nextJumpState = opt // re-use object + nextJumpSymbol = fun.symbol + case None => + // We haven't the corresponding LabelDef, this is a forward jump + nextJumpSymbol = fun.symbol + } + case _ => addStat() + } + this + } + + def resultWithAwait(awaitable: Awaitable, + onCompleteState: Int, + nextState: Int): AsyncState = { + new AsyncStateWithAwait(stats.toList, state, onCompleteState, effectiveNextState(nextState), awaitable, symLookup) + } + + def resultSimple(nextState: Int): AsyncState = { + new SimpleAsyncState(stats.toList, state, effectiveNextState(nextState), symLookup) + } + + def resultWithIf(condTree: Tree, thenState: Int, elseState: Int): AsyncState = { + def mkBranch(state: Int) = mkStateTree(state, symLookup) + this += If(condTree, mkBranch(thenState), mkBranch(elseState)) + new AsyncStateWithoutAwait(stats.toList, state, Array(thenState, elseState)) + } + + /** + * Build `AsyncState` ending with a match expression. + * + * The cases of the match simply resume at the state of their corresponding right-hand side. + * + * @param scrutTree tree of the scrutinee + * @param cases list of case definitions + * @param caseStates starting state of the right-hand side of the each case + * @return an `AsyncState` representing the match expression + */ + def resultWithMatch(scrutTree: Tree, cases: List[CaseDef], caseStates: Array[Int], symLookup: SymLookup): AsyncState = { + // 1. build list of changed cases + val newCases = for ((cas, num) <- cases.zipWithIndex) yield cas match { + case CaseDef(pat, guard, rhs) => + val bindAssigns = rhs.children.takeWhile(isSyntheticBindVal) + CaseDef(pat, guard, Block(bindAssigns, mkStateTree(caseStates(num), symLookup))) + } + // 2. insert changed match tree at the end of the current state + this += Match(scrutTree, newCases) + new AsyncStateWithoutAwait(stats.toList, state, caseStates) + } + + def resultWithLabel(startLabelState: Int, symLookup: SymLookup): AsyncState = { + this += mkStateTree(startLabelState, symLookup) + new AsyncStateWithoutAwait(stats.toList, state, Array(startLabelState)) + } + + override def toString: String = { + val statsBeforeAwait = stats.mkString("\n") + s"ASYNC STATE:\n$statsBeforeAwait" + } + } + + /** + * An `AsyncBlockBuilder` builds a `ListBuffer[AsyncState]` based on the expressions of a `Block(stats, expr)` (see `Async.asyncImpl`). + * + * @param stats a list of expressions + * @param expr the last expression of the block + * @param startState the start state + * @param endState the state to continue with + */ + final private class AsyncBlockBuilder(stats: List[Tree], expr: Tree, startState: Int, endState: Int, + private val symLookup: SymLookup) { + val asyncStates = ListBuffer[AsyncState]() + + var stateBuilder = new AsyncStateBuilder(startState, symLookup) + var currState = startState + + def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree)) + abort(tree.pos, "await must not be used in this position") + + def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { + val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) + new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, symLookup) + } + + import stateAssigner.nextState + def directlyAdjacentLabelDefs(t: Tree): List[Tree] = { + def isPatternCaseLabelDef(t: Tree) = t match { + case LabelDef(name, _, _) => name.toString.startsWith("case") + case _ => false + } + val span = (stats :+ expr).filterNot(isLiteralUnit).span(_ ne t) + span match { + case (before, _ :: after) => + before.reverse.takeWhile(isPatternCaseLabelDef) ::: after.takeWhile(isPatternCaseLabelDef) + case _ => + stats :+ expr + } + } + + // `while(await(x))` ... or `do { await(x); ... } while(...)` contain an `If` that loops; + // we must break that `If` into states so that it convert the label jump into a state machine + // transition + private def containsForeignLabelJump(t: Tree): Boolean = { + val labelDefs = t.collect { case ld: LabelDef => ld.symbol }.toSet + t.exists { + case rt: RefTree => rt.symbol != null && isLabel(rt.symbol) && !(labelDefs contains rt.symbol) + case _ => false + } + } + + // unwrap Block(t :: Nil, scala.runtime.BoxedUnit.UNIT) -- erasure will add the expr when await had type Unit + object UnwrapBoxedUnit { + def unapply(tree: Tree): Some[Tree] = tree match { + case Block(t :: Nil, unit) if isLiteralUnit(unit) => Some(t) // is really only going to be BoxedUnit, but hey + case t => Some(t) + } + } + // populate asyncStates + def add(stat: Tree, afterState: Option[Int] = None): Unit = stat match { + // the val name = await(..) pattern + case vd @ ValDef(mods, name, tpt, UnwrapBoxedUnit(Apply(fun, arg :: Nil))) if isAwait(fun) => + val onCompleteState = nextState() + val afterAwaitState = afterState.getOrElse(nextState()) + val awaitable = Awaitable(arg, stat.symbol, tpt.tpe, vd) + asyncStates += stateBuilder.resultWithAwait(awaitable, onCompleteState, afterAwaitState) // complete with await + currState = afterAwaitState + stateBuilder = new AsyncStateBuilder(currState, symLookup) + + case If(cond, thenp, elsep) if containsAwait(stat) || containsForeignLabelJump(stat) => + checkForUnsupportedAwait(cond) + + val thenStartState = nextState() + val elseStartState = nextState() + val afterIfState = afterState.getOrElse(nextState()) + + // the two Int arguments are the start state of the then branch and the else branch, respectively + asyncStates += stateBuilder.resultWithIf(cond, thenStartState, elseStartState) + + List((thenp, thenStartState), (elsep, elseStartState)) foreach { + case (branchTree, state) => + val builder = nestedBlockBuilder(branchTree, state, afterIfState) + asyncStates ++= builder.asyncStates + } + + currState = afterIfState + stateBuilder = new AsyncStateBuilder(currState, symLookup) + + case Match(scrutinee, cases) if containsAwait(stat) => + checkForUnsupportedAwait(scrutinee) + + val caseStates = new Array[Int](cases.length) + java.util.Arrays.setAll(caseStates, new IntUnaryOperator { + override def applyAsInt(operand: Int): Int = nextState() + }) + val afterMatchState = afterState.getOrElse(nextState()) + + asyncStates += stateBuilder.resultWithMatch(scrutinee, cases, caseStates, symLookup) + + for ((cas, num) <- cases.zipWithIndex) { + val (stats, expr) = statsAndExpr(cas.body) + val stats1 = stats.dropWhile(isSyntheticBindVal) + val builder = nestedBlockBuilder(Block(stats1, expr), caseStates(num), afterMatchState) + asyncStates ++= builder.asyncStates + } + + currState = afterMatchState + stateBuilder = new AsyncStateBuilder(currState, symLookup) + case ld @ LabelDef(name, params, rhs) + if containsAwait(rhs) || directlyAdjacentLabelDefs(ld).exists(containsAwait) => + + val startLabelState = stateIdForLabel(ld.symbol) + val afterLabelState = afterState.getOrElse(nextState()) + asyncStates += stateBuilder.resultWithLabel(startLabelState, symLookup) + labelDefStates(ld.symbol) = startLabelState + val builder = nestedBlockBuilder(rhs, startLabelState, afterLabelState) + asyncStates ++= builder.asyncStates + currState = afterLabelState + stateBuilder = new AsyncStateBuilder(currState, symLookup) + case b @ Block(stats, expr) => + for (stat <- stats) add(stat) + add(expr, afterState = Some(endState)) + case _ => + checkForUnsupportedAwait(stat) + stateBuilder += stat + } + for (stat <- (stats :+ expr)) add(stat) + val lastState = stateBuilder.resultSimple(endState) + asyncStates += lastState + } + + trait AsyncBlock { + def asyncStates: List[AsyncState] + + def onCompleteHandler[T: WeakTypeTag]: Tree + + def toDot: String + } + + + case class SymLookup(stateMachineClass: Symbol, applyTrParam: Symbol) { + def stateMachineMember(name: TermName): Symbol = { + stateMachineClass.info.member(name) + } + def memberRef(name: TermName): Tree = + gen.mkAttributedRef(stateMachineMember(name)) + + def selectResult = applyNilAfterUncurry(memberRef(name.result)) + } + + private lazy val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal") + private lazy val ThrowableClass = rootMirror.staticClass("java.lang.Throwable") + + /** + * Uses `AsyncBlockBuilder` to create an instance of `AsyncBlock`. + * + * @param block a `Block` tree in ANF + * @param symLookup helper for looking up members of the state machine class + * @return an `AsyncBlock` + */ + def buildAsyncBlock(block: Block, symLookup: SymLookup): AsyncBlock = { + val Block(stats, expr) = block + val startState = stateAssigner.nextState() + val endState = Int.MaxValue + + val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, symLookup) + + new AsyncBlock { + val switchIds = mutable.AnyRefMap[Integer, Integer]() + + // render with http://graphviz.it/#/new + def toDot: String = { + val states = asyncStates + def toHtmlLabel(label: String, preText: String, builder: StringBuilder): Unit = { + val br = "
" + builder.append("").append(label).append("").append("
") + builder.append("") + preText.split("\n").foreach { + (line: String) => + builder.append(br) + builder.append(line.replaceAllLiterally("\"", """).replaceAllLiterally("<", "<").replaceAllLiterally(">", ">").replaceAllLiterally(" ", " ")) + } + builder.append(br) + builder.append("") + } + val dotBuilder = new StringBuilder() + dotBuilder.append("digraph {\n") + def stateLabel(s: Int) = { + if (s == 0) "INITIAL" else if (s == Int.MaxValue) "TERMINAL" else switchIds.getOrElse[Integer](s, s).toString + } + val length = states.size + for ((state, i) <- asyncStates.zipWithIndex) { + dotBuilder.append(s"""${stateLabel(state.state)} [label=""").append("<") + def show(t: Tree): String = { + (t match { + case Block(stats, expr) => stats ::: expr :: Nil + case t => t :: Nil + }).iterator.map(t => showCode(t)).mkString("\n") + } + if (i != length - 1) { + val CaseDef(_, _, body) = state.mkHandlerCaseForState + toHtmlLabel(stateLabel(state.state), show(compactStateTransform.transform(body)), dotBuilder) + } else { + toHtmlLabel(stateLabel(state.state), state.allStats.map(show(_)).mkString("\n"), dotBuilder) + } + dotBuilder.append("> ]\n") + state match { + case s: AsyncStateWithAwait => + val CaseDef(_, _, body) = s.mkOnCompleteHandler.get + dotBuilder.append(s"""${stateLabel(s.onCompleteState)} [label=""").append("<") + toHtmlLabel(stateLabel(s.onCompleteState), show(compactStateTransform.transform(body)), dotBuilder) + dotBuilder.append("> ]\n") + case _ => + } + } + for (state <- states) { + state match { + case s: AsyncStateWithAwait => + dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(s.onCompleteState)} [style=dashed color=red]""") + dotBuilder.append("\n") + for (succ <- state.nextStates) { + dotBuilder.append(s"""${stateLabel(s.onCompleteState)} -> ${stateLabel(succ)}""") + dotBuilder.append("\n") + } + case _ => + for (succ <- state.nextStates) { + dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(succ)}""") + dotBuilder.append("\n") + } + } + } + dotBuilder.append("}\n") + dotBuilder.toString + } + + lazy val asyncStates: List[AsyncState] = filterStates + + def filterStates = { + val all = blockBuilder.asyncStates.toList + val (initial :: rest) = all + val map = all.iterator.map(x => (x.state, x)).toMap + val seen = mutable.HashSet[Int]() + def loop(state: AsyncState): Unit = { + seen.add(state.state) + for (i <- state.nextStates) { + if (i != Int.MaxValue && !seen.contains(i)) { + loop(map(i)) + } + } + } + loop(initial) + val live = rest.filter(state => seen(state.state)) + var nextSwitchId = 0 + (initial :: live).foreach { state => + val switchId = nextSwitchId + switchIds(state.state) = switchId + nextSwitchId += 1 + state match { + case state: AsyncStateWithAwait => + val switchId = nextSwitchId + switchIds(state.onCompleteState) = switchId + nextSwitchId += 1 + case _ => + } + } + initial :: live + + } + + def mkCombinedHandlerCases[T: WeakTypeTag]: List[CaseDef] = { + val caseForLastState: CaseDef = { + val lastState = asyncStates.last + val lastStateBody = Expr[T](lastState.body) + val rhs = futureSystemOps.completeWithSuccess( + Expr[futureSystem.Prom[T]](symLookup.selectResult), lastStateBody).tree + mkHandlerCase(lastState.state, Block(rhs, Return(literalUnit))) + } + asyncStates match { + case s :: Nil => + List(caseForLastState) + case _ => + val initCases = for (state <- asyncStates.init) yield state.mkHandlerCaseForState[T] + initCases :+ caseForLastState + } + } + + val initStates = asyncStates.init + + /** + * Builds the definition of the `resume` method. + * + * The resulting tree has the following shape: + * + * def resume(): Unit = { + * try { + * state match { + * case 0 => { + * f11 = exprReturningFuture + * f11.onComplete(onCompleteHandler)(context) + * } + * ... + * } + * } catch { + * case NonFatal(t) => result.failure(t) + * } + * } + */ + private def resumeFunTree[T: WeakTypeTag]: Tree = { + val stateMemberRef = symLookup.memberRef(name.state) + val body = + Match(stateMemberRef, + mkCombinedHandlerCases[T] ++ + initStates.flatMap(_.mkOnCompleteHandler[T]) ++ + List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List()))))) + + val body1 = compactStates(body) + + maybeTry( + body1, + List( + CaseDef( + Bind(name.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), + EmptyTree, { + val branchTrue = { + val t = Expr[Throwable](Ident(name.t)) + val complete = futureSystemOps.completeProm[T]( + Expr[futureSystem.Prom[T]](symLookup.selectResult), futureSystemOps.tryyFailure[T](t)).tree + Block(toList(complete), Return(literalUnit)) + } + If(Apply(Ident(NonFatalClass), List(Ident(name.t))), branchTrue, Throw(Ident(name.t))) + branchTrue + })), EmptyTree) + } + + private lazy val stateMemberSymbol = symLookup.stateMachineMember(name.state) + private val compactStateTransform = new Transformer { + override def transform(tree: Tree): Tree = tree match { + case as @ Assign(lhs, Literal(Constant(i: Integer))) if lhs.symbol == stateMemberSymbol => + val replacement = switchIds(i) + treeCopy.Assign(tree, lhs, Literal(Constant(replacement))) + case _: Match | _: CaseDef | _: Block | _: If => + super.transform(tree) + case _ => tree + } + } + + private def compactStates(m: Match): Tree = { + val cases1 = m.cases.flatMap { + case cd @ CaseDef(Literal(Constant(i: Integer)), EmptyTree, rhs) => + val replacement = switchIds(i) + val rhs1 = compactStateTransform.transform(rhs) + treeCopy.CaseDef(cd, Literal(Constant(replacement)), EmptyTree, rhs1) :: Nil + case x => x :: Nil + } + treeCopy.Match(m, m.selector, cases1) + } + + def forever(t: Tree): Tree = { + val labelName = TermName(name.fresh("while$")) + LabelDef(labelName, Nil, Block(toList(t), Apply(Ident(labelName), Nil))) + } + + /** + * Builds a `match` expression used as an onComplete handler. + * + * Assumes `tr: Try[Any]` is in scope. The resulting tree has the following shape: + * + * state match { + * case 0 => + * x11 = tr.get.asInstanceOf[Double] + * state = 1 + * resume() + * } + */ + def onCompleteHandler[T: WeakTypeTag]: Tree = { + // TODO AM: this was unused -- don't understand what this does + // val onCompletes = initStates.flatMap(_.mkOnCompleteHandler[T]) + forever { + adaptToUnit(toList(resumeFunTree)) + } + } + } + } + + private def isSyntheticBindVal(tree: Tree) = tree match { + case vd@ValDef(_, lname, _, Ident(rname)) => vd.symbol.attachments.contains[SyntheticBindVal.type] + case _ => false + } + + case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef) + + private def mkStateTree(nextState: Int, symLookup: SymLookup): Tree = + Assign(symLookup.memberRef(name.state), Literal(Constant(nextState))) + + private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef = + mkHandlerCase(num, adaptToUnit(rhs)) + + // We use the convention that the state machine's ID for a state corresponding to + // a labeldef will a negative number be based on the symbol ID. This allows us + // to translate a forward jump to the label as a state transition to a known state + // ID, even though the state machine transform hasn't yet processed the target label + // def. Negative numbers are used so as as not to clash with regular state IDs, which + // are allocated in ascending order from 0. + private def stateIdForLabel(sym: Symbol): Int = -sym.id + + private def mkHandlerCase(num: Int, rhs: Tree): CaseDef = + CaseDef(Literal(Constant(num)), EmptyTree, rhs) + + // TODO AM: should this explode blocks even when expr is not ()? + private def toList(tree: Tree): List[Tree] = tree match { + case Block(stats, expr) if isLiteralUnit(expr) => stats + case _ => tree :: Nil + } + +} diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala new file mode 100644 index 000000000000..d08a21f5384f --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -0,0 +1,163 @@ +/* + * 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.collection.mutable +import scala.reflect.internal.Flags._ + +trait Lifter extends ExprBuilder { + import u._ + + /** + * Identify which DefTrees are used (including transitively) which are declared + * in some state but used (including transitively) in another state. + * + * These will need to be lifted to class members of the state machine. + */ + def liftables(asyncStates: List[AsyncState]): List[Tree] = { + object companionship { + private val companions = collection.mutable.Map[Symbol, Symbol]() + private val companionsInverse = collection.mutable.Map[Symbol, Symbol]() + private def record(sym1: Symbol, sym2: Symbol): Unit = { + companions(sym1) = sym2 + companions(sym2) = sym1 + } + + def record(defs: List[Tree]): Unit = { + // Keep note of local companions so we rename them consistently + // when lifting. + for { + cd@ClassDef(_, _, _, _) <- defs + md@ModuleDef(_, _, _) <- defs + if (cd.name.toTermName == md.name) + } record(cd.symbol, md.symbol) + } + def companionOf(sym: Symbol): Symbol = { + companions.get(sym).orElse(companionsInverse.get(sym)).getOrElse(NoSymbol) + } + } + + + val defs: mutable.LinkedHashMap[Tree, Int] = { + /** Collect the DefTrees directly enclosed within `t` that have the same owner */ + def collectDirectlyEnclosedDefs(t: Tree): List[DefTree] = t match { + case ld: LabelDef => Nil + case dt: DefTree => dt :: Nil + case _: Function => Nil + case t => + val childDefs = t.children.flatMap(collectDirectlyEnclosedDefs(_)) + companionship.record(childDefs) + childDefs + } + mutable.LinkedHashMap(asyncStates.flatMap { + asyncState => + val defs = collectDirectlyEnclosedDefs(Block(asyncState.allStats: _*)) + defs.map((_, asyncState.state)) + }: _*) + } + + // In which block are these symbols defined? + val symToDefiningState: mutable.LinkedHashMap[Symbol, Int] = defs.map { + case (k, v) => (k.symbol, v) + } + + // The definitions trees + val symToTree: mutable.LinkedHashMap[Symbol, Tree] = defs.map { + case (k, v) => (k.symbol, k) + } + + // The direct references of each definition tree + val defSymToReferenced: mutable.LinkedHashMap[Symbol, List[Symbol]] = defs.map { + case (tree, _) => (tree.symbol, tree.collect { + case rt: RefTree if symToDefiningState.contains(rt.symbol) => rt.symbol + }) + } + + // The direct references of each block, excluding references of `DefTree`-s which + // are already accounted for. + val stateIdToDirectlyReferenced: mutable.LinkedHashMap[Int, List[Symbol]] = { + val refs: List[(Int, Symbol)] = asyncStates.flatMap( + asyncState => asyncState.stats.filterNot(t => t.isDef && !isLabel(t.symbol)).flatMap(_.collect { + case rt: RefTree + if symToDefiningState.contains(rt.symbol) => (asyncState.state, rt.symbol) + }) + ) + toMultiMap(refs) + } + + def liftableSyms: mutable.LinkedHashSet[Symbol] = { + val liftableMutableSet = mutable.LinkedHashSet[Symbol]() + def markForLift(sym: Symbol): Unit = { + if (!liftableMutableSet(sym)) { + liftableMutableSet += sym + + // Only mark transitive references of defs, modules and classes. The RHS of lifted vals/vars + // stays in its original location, so things that it refers to need not be lifted. + if (!(sym.isTerm && !sym.asTerm.isLazy && (sym.asTerm.isVal || sym.asTerm.isVar))) + defSymToReferenced(sym).foreach(sym2 => markForLift(sym2)) + } + } + // Start things with DefTrees directly referenced from statements from other states... + val liftableStatementRefs: List[Symbol] = stateIdToDirectlyReferenced.iterator.flatMap { + case (i, syms) => syms.filter(sym => symToDefiningState(sym) != i) + }.toList + // .. and likewise for DefTrees directly referenced by other DefTrees from other states + val liftableRefsOfDefTrees = defSymToReferenced.toList.flatMap { + case (referee, referents) => referents.filter(sym => symToDefiningState(sym) != symToDefiningState(referee)) + } + // Mark these for lifting, which will follow transitive references. + (liftableStatementRefs ++ liftableRefsOfDefTrees).foreach(markForLift) + liftableMutableSet + } + + liftableSyms.iterator.map(symToTree).map { + t => + val sym = t.symbol + val treeLifted = t match { + case vd@ValDef(_, _, tpt, rhs) => + sym.setFlag(MUTABLE | STABLE | PRIVATE | LOCAL) + sym.setName(name.fresh(sym.name.toTermName)) + sym.setInfo(sym.info.deconst) + val rhs1 = if (sym.asTerm.isLazy) rhs else EmptyTree + treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(sym.info).setPos(t.pos), rhs1) + case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) => + sym.setName(this.name.freshen(sym.name.toTermName)) + sym.setFlag(PRIVATE | LOCAL) + // Was `DefDef(sym, rhs)`, but this ran afoul of `ToughTypeSpec.nestedMethodWithInconsistencyTreeAndInfoParamSymbols` + // due to the handling of type parameter skolems in `thisMethodType` in `Namers` + treeCopy.DefDef(dd, Modifiers(sym.flags), sym.name, tparams, vparamss, tpt, rhs) + case cd@ClassDef(_, _, tparams, impl) => + sym.setName(name.freshen(sym.name.toTypeName)) + companionship.companionOf(cd.symbol) match { + case NoSymbol => + case moduleSymbol => + moduleSymbol.setName(sym.name.toTermName) + moduleSymbol.asModule.moduleClass.setName(moduleSymbol.name.toTypeName) + } + treeCopy.ClassDef(cd, Modifiers(sym.flags), sym.name, tparams, impl) + case md@ModuleDef(_, _, impl) => + companionship.companionOf(md.symbol) match { + case NoSymbol => + sym.setName(name.freshen(sym.name.toTermName)) + sym.asModule.moduleClass.setName(sym.name.toTypeName) + case classSymbol => // will be renamed by `case ClassDef` above. + } + treeCopy.ModuleDef(md, Modifiers(sym.flags), sym.name, impl) + case td@TypeDef(_, _, tparams, rhs) => + sym.setName(name.freshen(sym.name.toTypeName)) + treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs) + } + atPos(t.pos)(treeLifted) + }.toList + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala new file mode 100644 index 000000000000..f4305c96c393 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -0,0 +1,313 @@ +/* + * 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 java.util.function.IntConsumer + +import scala.collection.immutable.IntMap +import scala.collection.mutable + +import scala.reflect.internal.Flags._ + +trait LiveVariables extends ExprBuilder { + import u._ + + /** + * Returns for a given state a list of fields (as trees) that should be nulled out + * upon resuming that state (at the beginning of `resume`). + * + * @param asyncStates the states of an `async` block + * @param liftables the lifted fields + * @return a map mapping a state to the fields that should be nulled out + * upon resuming that state + */ + def fieldsToNullOut(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Int, List[Tree]] = { + // live variables analysis: + // the result map indicates in which states a given field should be nulled out + val liveVarsMap: mutable.LinkedHashMap[Tree, StateSet] = liveVars(asyncStates, liftables) + + val assignsOf = mutable.LinkedHashMap[Int, List[Tree]]() + + for ((fld, where) <- liveVarsMap) { + where.foreach { new IntConsumer { def accept(state: Int): Unit = { + assignsOf get state match { + case None => + assignsOf += (state -> List(fld)) + case Some(trees) if !trees.exists(_.symbol == fld.symbol) => + assignsOf += (state -> (fld +: trees)) + case _ => + // do nothing + } + }}} + } + + assignsOf + } + + /** + * Live variables data-flow analysis. + * + * The goal is to find, for each lifted field, the last state where the field is used. + * In all direct successor states which are not (indirect) predecessors of that last state + * (possible through loops), the corresponding field should be nulled out (at the beginning of + * `resume`). + * + * @param asyncStates the states of an `async` block + * @param liftables the lifted fields + * @return a map which indicates for a given field (the key) the states in which it should be nulled out + */ + def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Tree, StateSet] = { + val liftedSyms: Set[Symbol] = // include only vars + liftables.iterator.filter { + case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE) + case _ => false + }.map(_.symbol).toSet + + // determine which fields should be live also at the end (will not be nulled out) + val noNull: Set[Symbol] = liftedSyms.filter { sym => + val tpSym = sym.info.typeSymbol + (tpSym.isClass && (tpSym.asClass.isPrimitive || isNothingClass(tpSym))) || liftables.exists { tree => + !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym) + } + } + AsyncUtils.vprintln(s"fields never zero-ed out: ${noNull.mkString(", ")}") + + /** + * Traverse statements of an `AsyncState`, collect `Ident`-s referring to lifted fields. + * + * @param as a state of an `async` expression + * @return a set of lifted fields that are used within state `as` + */ + def fieldsUsedIn(as: AsyncState): ReferencedFields = { + class FindUseTraverser extends AsyncTraverser { + var usedFields: Set[Symbol] = Set[Symbol]() + var capturedFields: Set[Symbol] = Set[Symbol]() + private def capturing[A](body: => A): A = { + val saved = capturing + try { + capturing = true + body + } finally capturing = saved + } + private def capturingCheck(tree: Tree) = capturing(tree foreach check) + private var capturing: Boolean = false + private def check(tree: Tree): Unit = { + tree match { + case Ident(_) if liftedSyms(tree.symbol) => + if (capturing) + capturedFields += tree.symbol + else + usedFields += tree.symbol + case _ => + } + } + override def traverse(tree: Tree) = { + check(tree) + super.traverse(tree) + } + + override def nestedClass(classDef: ClassDef): Unit = capturingCheck(classDef) + + override def nestedModule(module: ModuleDef): Unit = capturingCheck(module) + + override def nestedMethod(defdef: DefDef): Unit = capturingCheck(defdef) + + override def byNameArgument(arg: Tree): Unit = capturingCheck(arg) + + override def function(function: Function): Unit = capturingCheck(function) + + override def patMatFunction(tree: Match): Unit = capturingCheck(tree) + } + + val findUses = new FindUseTraverser + findUses.traverse(Block(as.stats: _*)) + ReferencedFields(findUses.usedFields, findUses.capturedFields) + } + case class ReferencedFields(used: Set[Symbol], captured: Set[Symbol]) { + override def toString = s"used: ${used.mkString(",")}\ncaptured: ${captured.mkString(",")}" + } + + /* Build the control-flow graph. + * + * A state `i` is contained in the list that is the value to which + * key `j` maps iff control can flow from state `j` to state `i`. + */ + val cfg: Map[Int, Array[Int]] = { + var res = IntMap.empty[Array[Int]] + + for (as <- asyncStates) res = res.updated(as.state, as.nextStates) + res + } + + /** Tests if `state1` is a predecessor of `state2`. + */ + def isPred(state1: Int, state2: Int): Boolean = { + val seen = new StateSet() + + def isPred0(state1: Int, state2: Int): Boolean = + if(state1 == state2) false + else if (seen.contains(state1)) false // breaks cycles in the CFG + else cfg get state1 match { + case Some(nextStates) => + seen += state1 + var i = 0 + while (i < nextStates.length) { + if (nextStates(i) == state2 || isPred0(nextStates(i), state2)) return true + i += 1 + } + false + case None => + false + } + + isPred0(state1, state2) + } + + val finalState = asyncStates.find(as => !asyncStates.exists(other => isPred(as.state, other.state))).get + + if(AsyncUtils.verbose) { + for (as <- asyncStates) + AsyncUtils.vprintln(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") + } + + /* Backwards data-flow analysis. Computes live variables information at entry and exit + * of each async state. + * + * Compute using a simple fixed point iteration: + * + * 1. currStates = List(finalState) + * 2. for each cs \in currStates, compute LVentry(cs) from LVexit(cs) and used fields information for cs + * 3. record if LVentry(cs) has changed for some cs. + * 4. obtain predecessors pred of each cs \in currStates + * 5. for each p \in pred, compute LVexit(p) as union of the LVentry of its successors + * 6. currStates = pred + * 7. repeat if something has changed + */ + + var LVentry = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]() + var LVexit = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]() + + // All fields are declared to be dead at the exit of the final async state, except for the ones + // that cannot be nulled out at all (those in noNull), because they have been captured by a nested def. + LVexit = LVexit + (finalState.state -> noNull) + + var currStates = List(finalState) // start at final state + var captured: Set[Symbol] = Set() + + def contains(as: Array[Int], a: Int): Boolean = { + var i = 0 + while (i < as.length) { + if (as(i) == a) return true + i += 1 + } + false + } + while (!currStates.isEmpty) { + var entryChanged: List[AsyncState] = Nil + + for (cs <- currStates) { + val LVentryOld = LVentry(cs.state) + val referenced = fieldsUsedIn(cs) + captured ++= referenced.captured + val LVentryNew = LVexit(cs.state) ++ referenced.used + if (!LVentryNew.sameElements(LVentryOld)) { + LVentry = LVentry.updated(cs.state, LVentryNew) + entryChanged ::= cs + } + } + + val pred = entryChanged.flatMap(cs => asyncStates.filter(state => contains(state.nextStates, cs.state))) + var exitChanged: List[AsyncState] = Nil + + for (p <- pred) { + val LVexitOld = LVexit(p.state) + val LVexitNew = p.nextStates.flatMap(succ => LVentry(succ)).toSet + if (!LVexitNew.sameElements(LVexitOld)) { + LVexit = LVexit.updated(p.state, LVexitNew) + exitChanged ::= p + } + } + + currStates = exitChanged + } + + if(AsyncUtils.verbose) { + for (as <- asyncStates) { + AsyncUtils.vprintln(s"LVentry at state #${as.state}: ${LVentry(as.state).mkString(", ")}") + AsyncUtils.vprintln(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}") + } + } + + def lastUsagesOf(field: Tree, at: AsyncState): StateSet = { + val avoid = scala.collection.mutable.HashSet[AsyncState]() + + val result = new StateSet + def lastUsagesOf0(field: Tree, at: AsyncState): Unit = { + if (avoid(at)) () + else if (captured(field.symbol)) { + () + } + else LVentry get at.state match { + case Some(fields) if fields.contains(field.symbol) => + result += at.state + case _ => + avoid += at + for (state <- asyncStates) { + if (contains(state.nextStates, at.state)) { + lastUsagesOf0(field, state) + } + } + } + } + + lastUsagesOf0(field, at) + result + } + + val lastUsages: mutable.LinkedHashMap[Tree, StateSet] = + mutable.LinkedHashMap(liftables.map(fld => fld -> lastUsagesOf(fld, finalState)): _*) + + if(AsyncUtils.verbose) { + for ((fld, lastStates) <- lastUsages) + AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}") + } + + val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] = + for ((fld, lastStates) <- lastUsages) yield { + val result = new StateSet + lastStates.foreach(new IntConsumer { def accept(s: Int): Unit = { + if (s != finalState.state) { + val lastAsyncState = asyncStates.find(_.state == s).get + val succNums = lastAsyncState.nextStates + // all successor states that are not indirect predecessors + // filter out successor states where the field is live at the entry + var i = 0 + while (i < succNums.length) { + val num = succNums(i) + if (!isPred(num, s) && !LVentry(num).contains(fld.symbol)) + result += num + i += 1 + } + } + }}) + (fld, result) + } + + if(AsyncUtils.verbose) { + for ((fld, killAt) <- nullOutAt) + AsyncUtils.vprintln(s"field ${fld.symbol.name} should be nulled out in states ${killAt.iterator.mkString(", ")}") + } + + nullOutAt + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala b/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala new file mode 100644 index 000000000000..84759576a0cf --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala @@ -0,0 +1,23 @@ +/* + * 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 + +private[async] final class StateAssigner { + private var current = StateAssigner.Initial + + def nextState(): Int = try current finally current += 1 +} + +object StateAssigner { + final val Initial = 0 +} diff --git a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala new file mode 100644 index 000000000000..e4c79e250c26 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/StateSet.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.nsc.transform.async + +import java.util +import java.util.function.{Consumer, IntConsumer} + +import scala.collection.JavaConverters.{asScalaIteratorConverter, iterableAsScalaIterableConverter} + +// Set for StateIds, which are either small positive integers or -symbolID. +final class StateSet { + private val bitSet = new java.util.BitSet() + private val caseSet = new util.HashSet[Integer]() + def +=(stateId: Int): Unit = if (stateId > 0) bitSet.set(stateId) else caseSet.add(stateId) + def contains(stateId: Int): Boolean = if (stateId > 0 && stateId < 1024) bitSet.get(stateId) else caseSet.contains(stateId) + def iterator: Iterator[Integer] = { + bitSet.stream().iterator().asScala ++ caseSet.asScala.iterator + } + def foreach(f: IntConsumer): Unit = { + bitSet.stream().forEach(f) + caseSet.stream().forEach(new Consumer[Integer] { + override def accept(value: Integer): Unit = f.accept(value) + }) + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala new file mode 100644 index 000000000000..67df1baae505 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -0,0 +1,652 @@ +/* + * 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.collection.mutable +import scala.collection.mutable.ListBuffer +import scala.reflect.internal.{Flags, SymbolTable} +import user.AsyncBase +import scala.tools.nsc.{Global, NoPhase} +import scala.language.existentials + +private[async] trait AsyncContext { + val asyncBase: AsyncBase + + val u: SymbolTable + import u._ + + val Async_async: Symbol + val Async_await: Symbol + + // macro context interface -- the rest is meant to be independent of our being a macro (planning to move async into the compiler) + def abort(pos: Position, msg: String): Nothing + def error(pos: Position, msg: String): Unit + def typecheck(tree: Tree): Tree + + val asyncNames: AsyncNames[u.type] + object name extends asyncNames.AsyncName { + def fresh(name: TermName): TermName = freshenIfNeeded(name) + def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName + } +} + +// Logic sensitive to where we are in the pipeline +// (intend to move the transformation as late as possible, to avoid lugging all these trees around) +trait PhasedTransform extends AsyncContext { + import u._ + + // We're not that granular, but keeping separate flag for semantics + private lazy val isPastUncurry = isPastErasure + private lazy val emptyParamss: List[Nil.type] = if (isPastUncurry) List(Nil) else Nil + protected def applyNilAfterUncurry(t: Tree) = if (isPastUncurry) Apply(t, Nil) else t + + lazy val isPastErasure = { + val erasurePhase = u.asInstanceOf[Global].currentRun.erasurePhase + erasurePhase != NoPhase && u.asInstanceOf[Global].isPast(erasurePhase) + } + + def literalNull = Literal(Constant(null)) + + def typeEqualsNothing(tp: Type) = tp =:= definitions.NothingTpe + + def typeEqualsUnit(tp: Type) = tp =:= definitions.UnitTpe || (isPastErasure && tp =:= definitions.BoxedUnitTpe) + + def assignUnitType(t: Tree): t.type = + t.setType(definitions.UnitTpe) + + def setUnitMethodInfo(sym: Symbol): sym.type = sym.setInfo(MethodType(Nil, if (isPastErasure) definitions.BoxedUnitTpe else definitions.UnitTpe)) + + def isUnitType(tp: Type) = tp.typeSymbol == definitions.UnitClass || (isPastErasure && tp =:= definitions.BoxedUnitTpe) + def isNothingClass(sym: Symbol) = sym == definitions.NothingClass + + def literalUnit = + if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) + else Literal(Constant(())) // a def to avoid sharing trees + + def isLiteralUnit(t: Tree) = t match { + case Literal(Constant(())) => true + case t if t.symbol == definitions.BoxedUnit_UNIT => true // important to find match labels (which are potential states) + case _ => false + } + + + def function0ToUnit = typeOf[() => Unit] + def apply0DefDef: DefDef = + DefDef(NoMods, name.apply, Nil, emptyParamss, TypeTree(definitions.UnitTpe), Apply(Ident(name.apply), literalNull :: Nil)) + + def function1ToUnit(argTp: Type, useClass: Boolean) = { + val fun = + if (useClass) symbolOf[scala.runtime.AbstractFunction1[Any, Any]] + else symbolOf[scala.Function1[Any, Any]] + + appliedType(fun, argTp, typeOf[Unit]) + } + def apply1ToUnitDefDef(argTp: Type): DefDef = { + val applyVParamss = List(List(ValDef(Modifiers(Flags.PARAM), name.tr, TypeTree(argTp), EmptyTree))) + DefDef(NoMods, name.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), literalUnit) + } + + + def transformParentTypes(tps: List[Type]) = { + val tpsErased = tps.map(transformType) + assert(tpsErased.head.typeSymbol.isClass) + tpsErased + } + + def transformType(tp: Type) = if (isPastErasure) transformedType(tp) else tp + + def mkAsInstanceOf(qual: Tree, tp: Type) = gen.mkCast(qual, tp) + + private def tpeOf(t: Tree): Type = t match { + case _ if t.tpe != null => t.tpe + case Try(body, Nil, _) => tpeOf(body) + case Block(_, expr) => tpeOf(expr) + case Literal(Constant(())) => definitions.UnitTpe + case Return(_) => definitions.NothingTpe + case _ => NoType + } + + def adaptToUnit(rhs: List[Tree]): Block = + rhs match { + case (rhs: Block) :: Nil if { val tp = tpeOf(rhs); tp <:< definitions.UnitTpe || tp <:< definitions.BoxedUnitTpe } => + rhs + case init :+ last if { val tp = tpeOf(last); tp <:< definitions.UnitTpe || tp <:< definitions.BoxedUnitTpe } => + Block(init, last) + case init :+ (last@Literal(Constant(()))) => + Block(init, last) + case init :+ (last@Block(_, Return(_) | Literal(Constant(())))) => + Block(init, last) + case init :+ (last@Block(_, expr)) if expr.symbol == definitions.BoxedUnit_UNIT => + Block(init, last) + case init :+ Block(stats, expr) => + Block(init, Block(stats :+ expr, literalUnit)) + case _ => + Block(rhs, literalUnit) + } + + // TODO AM: why add the :Any type ascription to hide a tree of type Nothing? adaptToUnit doesn't seem to care + def adaptToUnitIgnoringNothing(stats: List[Tree]): Block = + stats match { + case init :+ last if tpeOf(last) =:= definitions.NothingTpe => + adaptToUnit(init :+ Typed(last, TypeTree(definitions.AnyTpe))) + case _ => + adaptToUnit(stats) + } + + private def derivedValueClassUnbox(cls: Symbol) = + (cls.info.decls.find(sym => sym.isMethod && sym.asTerm.isParamAccessor) getOrElse NoSymbol) + + def mkZero(tp: Type, pos: Position): Tree = { + val tpSym = tp.typeSymbol + if (tpSym.isClass && tpSym.asClass.isDerivedValueClass) { + val argZero = mkZero(derivedValueClassUnbox(tpSym).infoIn(tp).resultType, pos) + val baseType = tp.baseType(tpSym) // use base type here to dealias / strip phantom "tagged types" etc. + + // By explicitly attributing the types and symbols here, we subvert privacy. + // Otherwise, ticket86PrivateValueClass would fail. + + // Approximately: + // q"new ${valueClass}[$..targs](argZero)" + val target: Tree = gen.mkAttributedSelect(typecheck(atPos(pos)(New(TypeTree(baseType)))), tpSym.asClass.primaryConstructor) + + val zero = gen.mkMethodCall(target, argZero :: Nil) + // restore the original type which we might otherwise have weakened with `baseType` above + typecheck(atPos(pos)(gen.mkCast(zero, tp))) + } else { + gen.mkZero(tp) + } + } + + final def uncheckedBounds(tp: Type): Type = if (isPastErasure) tp else u.uncheckedBounds(tp) + + def uncheckedBoundsIfNeeded(t: Type): Type = { + var quantified: List[Symbol] = Nil + var badSkolemRefs: List[Symbol] = Nil + t.foreach { + case et: ExistentialType => + quantified :::= et.quantified + case TypeRef(pre, sym, args) => + val illScopedSkolems = args.map(_.typeSymbol).filter(arg => arg.isExistentialSkolem && !quantified.contains(arg)) + badSkolemRefs :::= illScopedSkolems + case _ => + } + if (badSkolemRefs.isEmpty) t + else t.map { + case tp @ TypeRef(pre, sym, args) if args.exists(a => badSkolemRefs.contains(a.typeSymbol)) => + uncheckedBounds(tp) + case t => t + } + } + + + final def mkMutableField(tpt: Type, name: TermName, init: Tree): List[Tree] = { + if (isPastTyper) { + import scala.reflect.internal.Flags._ + // If we are running after the typer phase (ie being called from a compiler plugin) + // we have to create the trio of members manually. + val field = ValDef(Modifiers(MUTABLE | PRIVATE | LOCAL), name.localName, TypeTree(tpt), init) + val paramss = emptyParamss + val getter = DefDef(Modifiers(ACCESSOR | STABLE), name.getterName, Nil, paramss, TypeTree(tpt), Select(This(tpnme.EMPTY), field.name)) + val setter = DefDef(Modifiers(ACCESSOR), name.setterName, Nil, List(List(ValDef(NoMods, TermName("x"), TypeTree(tpt), EmptyTree))), TypeTree(definitions.UnitTpe), Assign(Select(This(tpnme.EMPTY), field.name), Ident(TermName("x")))) + field :: getter :: setter :: Nil + } else { + val result = ValDef(NoMods, name, TypeTree(tpt), init) + result :: Nil + } + } + final def mkField(tpt: Type, name: TermName, init: Tree): List[Tree] = { + if (isPastTyper) { + import scala.reflect.internal.Flags._ + // If we are running after the typer phase (ie being called from a compiler plugin) + // we have to create the trio of members manually. + val field = ValDef(Modifiers(PRIVATE | LOCAL), name.localName, TypeTree(tpt), init) + val paramss = emptyParamss + val getter = DefDef(Modifiers(ACCESSOR | STABLE), name.getterName, Nil, paramss, TypeTree(tpt), Select(This(tpnme.EMPTY), field.name)) + field :: getter :: Nil + } else { + val result = ValDef(NoMods, name, TypeTree(tpt), init) + result :: Nil + } + } + +} + + +/** + * Utilities used in both `ExprBuilder` and `AnfTransform`. + */ +private[async] trait TransformUtils extends PhasedTransform { + import typingTransformers.{TypingTransformApi, typingTransform} + import u._ + + def emitTryCatch: Boolean = asyncBase.futureSystem.emitTryCatch + + def maybeTry(block: Tree, catches: List[CaseDef], finalizer: Tree) = + if (emitTryCatch) Try(block, catches, finalizer) else block + + lazy val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException") + + def isAsync(fun: Tree) = fun.symbol == Async_async + def isAwait(fun: Tree) = fun.symbol == Async_await + + + private lazy val Boolean_ShortCircuits: Set[Symbol] = { + import definitions.BooleanClass + def BooleanTermMember(name: String) = BooleanClass.typeSignature.member(TermName(name).encodedName) + val Boolean_&& = BooleanTermMember("&&") + val Boolean_|| = BooleanTermMember("||") + Set(Boolean_&&, Boolean_||) + } + + private def isByName(fun: Tree): ((Int, Int) => Boolean) = { + if (Boolean_ShortCircuits contains fun.symbol) (i, j) => true + else if (fun.tpe == null) (x, y) => false + else { + val paramss = fun.tpe.paramss + val byNamess = paramss.map(_.map(_.asTerm.isByNameParam)) + (i, j) => util.Try(byNamess(i)(j)).getOrElse(false) + } + } + private def argName(fun: Tree): ((Int, Int) => TermName) = { + val paramss = fun.tpe.paramss + val namess = paramss.map(_.map(_.name.toTermName)) + (i, j) => util.Try(namess(i)(j)).getOrElse(TermName(s"arg_${i}_${j}")) + } + + def isLabel(sym: Symbol): Boolean = sym.isLabel + + def substituteTrees(t: Tree, from: List[Symbol], to: List[Tree]): Tree = + (new TreeSubstituter(from, to)).transform(t) + + /** Map a list of arguments to: + * - A list of argument Trees + * - A list of auxillary results. + * + * The function unwraps and rewraps the `arg :_*` construct. + * + * @param args The original argument trees + * @param f A function from argument (with '_*' unwrapped) and argument index to argument. + * @tparam A The type of the auxillary result + */ + private def mapArguments[A](args: List[Tree])(f: (Tree, Int) => (A, Tree)): (List[A], List[Tree]) = { + args match { + case args :+ Typed(tree, Ident(tpnme.WILDCARD_STAR)) => + val (a, argExprs :+ lastArgExpr) = (args :+ tree).zipWithIndex.map(f.tupled).unzip + val exprs = argExprs :+ atPos(lastArgExpr.pos.makeTransparent)(Typed(lastArgExpr, Ident(tpnme.WILDCARD_STAR))) + (a, exprs) + case args => + args.zipWithIndex.map(f.tupled).unzip + } + } + + case class Arg(expr: Tree, isByName: Boolean, argName: TermName) + + /** + * Transform a list of argument lists, producing the transformed lists, and lists of auxillary + * results. + * + * The function `f` need not concern itself with varargs arguments e.g (`xs : _*`). It will + * receive `xs`, and it's result will be re-wrapped as `f(xs) : _*`. + * + * @param fun The function being applied + * @param argss The argument lists + * @return (auxillary results, mapped argument trees) + */ + def mapArgumentss[A](fun: Tree, argss: List[List[Tree]])(f: Arg => (A, Tree)): (List[List[A]], List[List[Tree]]) = { + val isByNamess: (Int, Int) => Boolean = isByName(fun) + val argNamess: (Int, Int) => TermName = argName(fun) + argss.zipWithIndex.map { case (args, i) => + mapArguments[A](args) { + (tree, j) => f(Arg(tree, isByNamess(i, j), argNamess(i, j))) + } + }.unzip + } + + + def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { + case Block(stats, expr) => (stats, expr) + case _ => (List(tree), Literal(Constant(()))) + } + + def blockToList(tree: Tree): List[Tree] = tree match { + case Block(stats, expr) => stats :+ expr + case t => t :: Nil + } + + def listToBlock(trees: List[Tree]): Block = trees match { + case trees @ (init :+ last) => + val pos = trees.map(_.pos).reduceLeft(_ union _) + Block(init, last).setType(last.tpe).setPos(pos) + } + + def emptyConstructor: DefDef = { + val emptySuperCall = Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), Nil) + DefDef(NoMods, nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(emptySuperCall), Literal(Constant(())))) + } + + /** Descends into the regions of the tree that are subject to the + * translation to a state machine by `async`. When a nested template, + * function, or by-name argument is encountered, the descent stops, + * and `nestedClass` etc are invoked. + */ + trait AsyncTraverser extends Traverser { + def nestedClass(classDef: ClassDef): Unit = { + } + + def nestedModule(module: ModuleDef): Unit = { + } + + def nestedMethod(defdef: DefDef): Unit = { + } + + def byNameArgument(arg: Tree): Unit = { + } + + def function(function: Function): Unit = { + } + + def patMatFunction(tree: Match): Unit = { + } + + override def traverse(tree: Tree): Unit = { + tree match { + case _ if isAsync(tree) => + // Under -Ymacro-expand:discard, used in the IDE, nested async blocks will be visible to the outer blocks + case cd: ClassDef => nestedClass(cd) + case md: ModuleDef => nestedModule(md) + case dd: DefDef => nestedMethod(dd) + case fun: Function => function(fun) + case m@Match(EmptyTree, _) => patMatFunction(m) // Pattern matching anonymous function under -Xoldpatmat of after `restorePatternMatchingFunctions` + case q"$fun[..$targs](...$argss)" if argss.nonEmpty => + val isInByName = isByName(fun) + for ((args, i) <- argss.zipWithIndex) { + for ((arg, j) <- args.zipWithIndex) { + if (!isInByName(i, j)) traverse(arg) + else byNameArgument(arg) + } + } + traverse(fun) + case _ => super.traverse(tree) + } + } + } + + def transformAt(tree: Tree)(f: PartialFunction[Tree, (TypingTransformApi => Tree)]) = { + typingTransform(tree)((tree, api) => { + if (f.isDefinedAt(tree)) f(tree)(api) + else api.default(tree) + }) + } + + def toMultiMap[A, B](abs: Iterable[(A, B)]): mutable.LinkedHashMap[A, List[B]] = { + // LinkedHashMap for stable order of results. + val result = new mutable.LinkedHashMap[A, ListBuffer[B]]() + for ((a, b) <- abs) { + val buffer = result.getOrElseUpdate(a, new ListBuffer[B]) + buffer += b + } + result.map { case (a, b) => (a, b.toList) } + } + + // Attributed version of `TreeGen#mkCastPreservingAnnotations` + def mkAttributedCastPreservingAnnotations(tree: Tree, tp: Type): Tree = { + atPos(tree.pos) { + val casted = typecheck(gen.mkCast(tree, uncheckedBounds(tp.withoutAnnotations).dealias)) + Typed(casted, TypeTree(tp)).setType(tp) + } + } + + def withAnnotation(tp: Type, ann: Annotation): Type = withAnnotations(tp, List(ann)) + + def withAnnotations(tp: Type, anns: List[Annotation]): Type = tp match { + case AnnotatedType(existingAnns, underlying) => annotatedType(anns ::: existingAnns, underlying) + case ExistentialType(quants, underlying) => existentialAbstraction(quants, withAnnotations(underlying, anns)) + case _ => annotatedType(anns, tp) + } + + + def thisType(sym: Symbol): Type = { + if (sym.isClass) sym.asClass.thisPrefix + else NoPrefix + } + + /** + * Efficiently decorate each subtree within `t` with the result of `t exists isAwait`, + * and return a function that can be used on derived trees to efficiently test the + * same condition. + * + * If the derived tree contains synthetic wrapper trees, these will be recursed into + * in search of a sub tree that was decorated with the cached answer. + * + * Requires markContainsAwaitTraverser has previously traversed `t`. + **/ + final def containsAwait(t: Tree): Boolean = { + object traverser extends Traverser { + var containsAwait = false + override def traverse(tree: Tree): Unit = + if (tree.hasAttachment[NoAwait.type]) {} // safe to skip + else if (tree.hasAttachment[ContainsAwait.type]) containsAwait = true + else if (markContainsAwaitTraverser.shouldAttach(t)) super.traverse(tree) + } + traverser.traverse(t) + traverser.containsAwait + } + + def markContainsAwait(t: Tree) = markContainsAwaitTraverser.traverse(t) + + private object markContainsAwaitTraverser extends Traverser { + def shouldAttach(t: Tree) = !treeCannotContainAwait(t) + private def treeCannotContainAwait(t: Tree) = t match { + case _: Ident | _: TypeTree | _: Literal => true + case _ => isAsync(t) + } + private def attachContainsAwait(t: Tree): Unit = if (shouldAttach(t)) { + t.updateAttachment(ContainsAwait) + t.removeAttachment[NoAwait.type] + } + private def attachNoAwait(t: Tree): Unit = if (shouldAttach(t)) { + t.updateAttachment(NoAwait) + } + + var stack: List[Tree] = Nil + + override def traverse(tree: Tree): Unit = { + stack ::= tree + try { + if (isAsync(tree)) { + ; + } else { + if (isAwait(tree)) + stack.foreach(attachContainsAwait) + else + attachNoAwait(tree) + super.traverse(tree) + } + } finally stack = stack.tail + } + } + + final def cleanupContainsAwaitAttachments(t: Tree): t.type = { + t.foreach {t => + t.removeAttachment[ContainsAwait.type] + t.removeAttachment[NoAwait.type] + } + t + } + + // First modification to translated patterns: + // - Set the type of label jumps to `Unit` + // - Propagate this change to trees known to directly enclose them: + // ``If` / `Block`) adjust types of enclosing + final def adjustTypeOfTranslatedPatternMatches(t: Tree, owner: Symbol): Tree = { + import definitions.UnitTpe + + typingTransform(t, owner) { + (tree, api) => + tree match { + case LabelDef(name, params, rhs) => + val rhs1 = api.recur(rhs) + if (rhs1.tpe =:= UnitTpe) { + tree.symbol.info = internal.methodType(tree.symbol.info.paramLists.head, UnitTpe) + treeCopy.LabelDef(tree, name, params, rhs1) + } else { + treeCopy.LabelDef(tree, name, params, rhs1) + } + case Block(stats, expr) => + val stats1 = stats map api.recur + val expr1 = api.recur(expr) + if (expr1.tpe =:= UnitTpe) + treeCopy.Block(tree, stats1, expr1).setType(UnitTpe) + else + treeCopy.Block(tree, stats1, expr1) + case If(cond, thenp, elsep) => + val cond1 = api.recur(cond) + val thenp1 = api.recur(thenp) + val elsep1 = api.recur(elsep) + if (thenp1.tpe =:= definitions.UnitTpe && elsep.tpe =:= UnitTpe) + treeCopy.If(tree, cond1, thenp1, elsep1).setType(UnitTpe) + else + treeCopy.If(tree, cond1, thenp1, elsep1) + case Apply(fun, args) if isLabel(fun.symbol) => + treeCopy.Apply(tree, api.recur(fun), args map api.recur).setType(UnitTpe) + case vd @ ValDef(mods, name, tpt, rhs) if isCaseTempVal(vd.symbol) => + def addUncheckedBounds(t: Tree) = { + typingTransform(t, owner) { + (tree, api) => + if (tree.tpe == null) tree else api.default(tree).setType(uncheckedBoundsIfNeeded(tree.tpe)) + } + + } + val uncheckedRhs = addUncheckedBounds(api.recur(rhs)) + val uncheckedTpt = addUncheckedBounds(tpt) + vd.symbol.info = uncheckedBoundsIfNeeded(vd.symbol.info) + treeCopy.ValDef(vd, mods, name, uncheckedTpt, uncheckedRhs) + case t => api.default(t) + } + } + } + + private def isCaseTempVal(s: Symbol) = { + s.isTerm && s.asTerm.isVal && s.isSynthetic && s.name.toString.startsWith("x") + } + + + def deriveLabelDef(ld: LabelDef, applyToRhs: Tree => Tree): LabelDef = { + val rhs2 = applyToRhs(ld.rhs) + val ld2 = treeCopy.LabelDef(ld, ld.name, ld.params, rhs2) + if (ld eq ld2) ld + else { + val info2 = ld2.symbol.info match { + case MethodType(params, p) => MethodType(params, rhs2.tpe) + case t => t + } + ld2.symbol.info = info2 + ld2 + } + } + object MatchEnd { + def unapply(t: Tree): Option[LabelDef] = t match { + case ValDef(_, _, _, t) => unapply(t) + case ld: LabelDef if ld.name.toString.startsWith("matchEnd") => Some(ld) + case _ => None + } + } + + + val typingTransformers: TypingTransformers + + abstract class TypingTransformers extends scala.tools.nsc.transform.TypingTransformers { + val global: u.type with Global = u.asInstanceOf[u.type with Global] + + def callsiteTyper: global.analyzer.Typer + + trait TransformApi { + /** Calls the current transformer on the given tree. + * Current transformer = argument to the `transform` call. + */ + def recur(tree: Tree): Tree + + /** Calls the default transformer on the given tree. + * Default transformer = recur into tree's children and assemble the results. + */ + def default(tree: Tree): Tree + } + + /** Functions that are available during [[typingTransform]]. + * @see [[typingTransform]] + */ + trait TypingTransformApi extends TransformApi { + /** Temporarily pushes the given symbol onto the owner stack, creating a new local typer, + * invoke the given operation and then rollback the changes to the owner stack. + */ + def atOwner[T](owner: Symbol)(op: => T): T + + /** Temporarily pushes the given tree onto the recursion stack, and then calls `atOwner(symbol)(trans)`. + */ + def atOwner[T](tree: Tree, owner: Symbol)(op: => T): T + + /** Returns the symbol currently on the top of the owner stack. + * If we're not inside any `atOwner` call, then macro application's context owner will be used. + */ + def currentOwner: Symbol + + /** Typechecks the given tree using the local typer currently on the top of the owner stack. + * If we're not inside any `atOwner` call, then macro application's callsite typer will be used. + */ + def typecheck(tree: Tree): Tree + } + + class HofTransformer(hof: (Tree, TransformApi) => Tree) extends Transformer { + val api = new TransformApi { + def recur(tree: Tree): Tree = hof(tree, this) + def default(tree: Tree): Tree = superTransform(tree) + } + def superTransform(tree: Tree) = super.transform(tree) + override def transform(tree: Tree): Tree = hof(tree, api).asInstanceOf[global.Tree] + } + + // def transform(tree: Tree)(transformer: (Tree, TransformApi) => Tree): Tree = new HofTransformer(transformer).transform(tree) + + class HofTypingTransformer(hof: (Tree, TypingTransformApi) => Tree) extends TypingTransformer(callsiteTyper.context.unit) { self => + currentOwner = callsiteTyper.context.owner + curTree = global.EmptyTree + + localTyper = { + val ctx: global.analyzer.Context = callsiteTyper.context.make(unit = callsiteTyper.context.unit) + (if (phase.erasedTypes) global.erasure.newTyper(ctx.asInstanceOf[global.erasure.Context]) else global.analyzer.newTyper(ctx)).asInstanceOf[global.analyzer.Typer] + } + + val api = new TypingTransformApi { + def recur(tree: Tree): Tree = hof(tree, this) + def default(tree: Tree): Tree = superTransform(tree) + def atOwner[T](owner: Symbol)(op: => T): T = self.atOwner(owner.asInstanceOf[global.Symbol])(op) + def atOwner[T](tree: Tree, owner: Symbol)(op: => T): T = self.atOwner(tree.asInstanceOf[global.Tree], owner.asInstanceOf[global.Symbol])(op) + def currentOwner: Symbol = self.currentOwner + def typecheck(tree: Tree): Tree = localTyper.typed(tree.asInstanceOf[global.Tree]) + } + def superTransform(tree: Tree) = super.transform(tree.asInstanceOf[global.Tree]) + override def transform(tree: global.Tree): global.Tree = hof(tree, api).asInstanceOf[global.Tree] + } + + def typingTransform(tree: Tree)(transformer: (Tree, TypingTransformApi) => Tree): Tree = new HofTypingTransformer(transformer).transform(tree.asInstanceOf[global.Tree]) + + def typingTransform(tree: Tree, owner: Symbol)(transformer: (Tree, TypingTransformApi) => Tree): Tree = { + val trans = new HofTypingTransformer(transformer) + trans.atOwner(owner.asInstanceOf[global.Symbol])(trans.transform(tree.asInstanceOf[global.Tree])) + } + } + +} + +case object ContainsAwait +case object NoAwait diff --git a/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala b/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala new file mode 100644 index 000000000000..64994c9f18ba --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala @@ -0,0 +1,111 @@ +/* + * 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 +package user + +import scala.reflect.api.Universe +import scala.reflect.internal.annotations.compileTimeOnly +import scala.reflect.macros.Aliases +import scala.reflect.macros.whitebox.Context + +/** + * A base class for the `async` macro. Subclasses must provide: + * + * - Concrete types for a given future system + * - Tree manipulations to create and complete the equivalent of Future and Promise + * in that system. + * - The `async` macro declaration itself, and a forwarder for the macro implementation. + * (The latter is temporarily needed to workaround bug SI-6650 in the macro system) + * + * The default implementation, [[scala.async.Async]], binds the macro to `scala.concurrent._`. + */ +abstract class AsyncBase { + type FS <: FutureSystem + val futureSystem: FS + + /** + * A call to `await` must be nested in an enclosing `async` block. + * + * A call to `await` does not block the current thread, rather it is a delimiter + * used by the enclosing `async` macro. Code following the `await` + * call is executed asynchronously, when the argument of `await` has been completed. + * + * @param awaitable the future from which a value is awaited. + * @tparam T the type of that value. + * @return the value. + */ + @compileTimeOnly("`await` must be enclosed in an `async` block") + def await[T](awaitable: futureSystem.Fut[T]): T = ??? + + def asyncImpl[T: c.WeakTypeTag](c: Context) + (body: c.Expr[T]) + (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { + + // I think there's a bug in subtyping -- shouldn't be necessary to specify the Aliases parent explicitly + // (but somehow we don't rebind the abstract types otherwise) + val ctx = c.asInstanceOf[Aliases with scala.reflect.macros.whitebox.Context {val universe: asyncMacro.u.type}] + val asyncMacroSymbol = ctx.macroApplication.symbol + + object asyncMacro extends AsyncTransform(AsyncBase.this, c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]) { + // The actual transformation happens in terms of the internal compiler data structures. + // We hide the macro context from the classes in the transform package, + // because they're basically a compiler plugin packaged as a macro. + import u._ + + // TODO AM: rework + val asyncNames: AsyncNames[u.type] = rootMirror.RootClass.attachments.get[AsyncNames[u.type]].getOrElse { + val names = new AsyncNames[u.type](u) + rootMirror.RootClass.attachments.update(names) + names + } + + val Async_async = asyncMethod(u)(asyncMacroSymbol) + val Async_await = awaitMethod(u)(asyncMacroSymbol) + + // a few forwarders to context, since they are not easily available through SymbolTable + def typecheck(tree: Tree): Tree = ctx.typecheck(tree) + def abort(pos: Position, msg: String): Nothing = ctx.abort(pos, msg) + def error(pos: Position, msg: String): Unit = ctx.error(pos, msg) + val typingTransformers = new TypingTransformers { + val callsiteTyper: global.analyzer.Typer = ctx.asInstanceOf[scala.reflect.macros.contexts.Context].callsiteTyper.asInstanceOf[global.analyzer.Typer] + } + } + + val enclosingOwner = ctx.internal.enclosingOwner + val code = + try asyncMacro.asyncTransform(body.tree.asInstanceOf[ctx.Tree], execContext.tree.asInstanceOf[ctx.Tree], enclosingOwner, asyncMacroSymbol.pos.makeTransparent)(ctx.weakTypeTag[T].tpe) + catch { case te: ctx.universe.TypeError => ctx.info(enclosingOwner.pos, te.getStackTrace.mkString("\n"), true); ??? } + + AsyncUtils.vprintln(s"async state machine transform expands to:\n $code") + + // Mark range positions for synthetic code as transparent to allow some wiggle room for overlapping ranges + for (t <- code) t.setPos(t.pos.makeTransparent) + c.Expr[futureSystem.Fut[T]](code.asInstanceOf[c.Tree]) + } + + + protected[async] def asyncMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { + import u._ + if (asyncMacroSymbol == null) NoSymbol + else asyncMacroSymbol.owner.typeSignature.member(TermName("async")) + } + + protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { + import u._ + if (asyncMacroSymbol == null) NoSymbol + else asyncMacroSymbol.owner.typeSignature.member(TermName("await")) + } + + protected[async] def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] = + u.reify { () } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala b/src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala new file mode 100644 index 000000000000..3a7b594f5681 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala @@ -0,0 +1,133 @@ +/* + * 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 +package user + +import scala.language.experimental.macros +import scala.reflect.api +import scala.reflect.internal.SymbolTable +import scala.reflect.macros.whitebox.Context + +object AsyncId extends AsyncBase { + lazy val futureSystem = IdentityFutureSystem + type FS = IdentityFutureSystem.type + + def async[T](body: => T) = macro asyncIdImpl[T] + + def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[futureSystem.Fut[T]] = asyncImpl[T](c)(body)(c.literalUnit) +} + +object AsyncTestLV extends AsyncBase { + lazy val futureSystem = IdentityFutureSystem + type FS = IdentityFutureSystem.type + + def async[T](body: T) = macro asyncIdImpl[T] + + def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[futureSystem.Fut[T]] = asyncImpl[T](c)(body)(c.literalUnit) + + var log: List[(String, Any)] = Nil + def assertNulledOut(a: Any): Unit = assert(log.exists(_._2 == a), AsyncTestLV.log) + def assertNotNulledOut(a: Any): Unit = assert(!log.exists(_._2 == a), AsyncTestLV.log) + def clear(): Unit = log = Nil + + def apply(name: String, v: Any): Unit = + log ::= (name -> v) + + protected[async] override def nullOut(u: api.Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] = + u.reify { AsyncTestLV(name.splice, v.splice) } +} + +/** + * A trivial implementation of [[FutureSystem]] that performs computations + * on the current thread. Useful for testing. + */ +class Box[A] { + var a: A = _ +} +object IdentityFutureSystem extends FutureSystem { + type Prom[A] = Box[A] + + type Fut[A] = A + type ExecContext = Unit + type Tryy[A] = scala.util.Try[A] + + def mkOps(u: SymbolTable, isPastErasure: Boolean = false): Ops[u.type] = new IdentityOps[u.type](u, isPastErasure) + class IdentityOps[Universe <: SymbolTable](u0: Universe, isPastErasure: Boolean) extends Ops[Universe](u0, isPastErasure) { + import u._ + + def promType(tp: Type): Type = phasedAppliedType(weakTypeOf[Box[_]], tp) + def tryType(tp: Type): Type = phasedAppliedType(weakTypeOf[util.Try[_]], tp) + + def createProm[A: WeakTypeTag]: Expr[Prom[A]] = { + val newProm = reify { new Prom[A]() } + if (isPastErasure) + Expr[Prom[A]](newProm.tree match { + // drop type apply + case ap@Apply(sel@Select(nw@New(AppliedTypeTree(newProm, _)), ctor), args) => + treeCopy.Apply(ap, treeCopy.Select(sel, treeCopy.New(nw, newProm), ctor), args) + }) + else newProm + } + + def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = { + val expr = reify { prom.splice.a } + if (isPastErasure) Expr[Fut[A]](Apply(expr.tree, Nil)) + else expr + } + + def future[A: WeakTypeTag](t: Expr[A])(execContext: Expr[ExecContext]) = t + + def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[Tryy[A] => B], + execContext: Expr[ExecContext]): Expr[Unit] = reify { + fun.splice.apply(util.Success(future.splice)) + literalUnitExpr.splice + } + + def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit] = { + val valueGet = reify { value.splice.get } + reify { + prom.splice.a = { if (isPastErasure) Expr[A](Apply(valueGet.tree, Nil)) else valueGet }.splice + literalUnitExpr.splice + } + } + + def tryyIsFailure[A](tryy: Expr[Tryy[A]]): Expr[Boolean] = reify { + tryy.splice.isFailure + } + + def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = { + val expr = reify { tryy.splice.get } + if (isPastErasure) Expr[A](Apply(expr.tree, Nil)) + else expr + } + + def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] = { + val expr = reify { scala.util.Success[A](a.splice) } + if (isPastErasure) + Expr[Tryy[A]](expr.tree match { + // drop type apply + case ap@Apply(TypeApply(succ, _), args) => treeCopy.Apply(ap, succ, args) + }) + else expr + } + def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]] = { + val expr = reify { scala.util.Failure[A](a.splice) } + if (isPastErasure) + Expr[Tryy[A]](expr.tree match { + // drop type apply + case ap@Apply(TypeApply(fail, _), args) => treeCopy.Apply(ap, fail, args) + }) + else expr + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala new file mode 100644 index 000000000000..a8293cc88a51 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -0,0 +1,194 @@ +/* + * 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.SymbolTable + +/** + * An abstraction over a future system. + * + * Used by the macro implementations in [[scala.async.AsyncBase]] 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] + + // We could do with just Universe <: reflect.api.Universe if it wasn't Expr and WeakTypeTag + // (the api effectively doesn't let you call these factories since there's no way to get at the Tree- and TypeCreators) + abstract class Ops[Universe <: SymbolTable](val u: Universe, val isPastErasure: Boolean) { + import u._ + + def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = u.Expr[T](rootMirror, FixedMirrorTreeCreator(rootMirror, tree)) + def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = u.WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, tpe)) + + def literalUnitExpr = Expr[Unit](if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) else Literal(Constant(()))) + + def phasedAppliedType(tycon: Type, tp: Type) = if (isPastErasure) tycon else appliedType(tycon, tp) + + def promType(tp: Type): Type + def tryType(tp: Type): Type + def stateMachineClassParents: List[Type] = Nil + + /** Create an empty promise */ + def createProm[A: WeakTypeTag]: Expr[Prom[A]] + + /** Extract a future from the given promise. */ + def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]): Expr[Fut[A]] + + /** Construct a future to asynchronously compute the given expression */ + def future[A: WeakTypeTag](a: Expr[A])(execContext: Expr[ExecContext]): Expr[Fut[A]] + + /** Register an call back to run on completion of the given future */ + def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[Tryy[A] => B], + execContext: Expr[ExecContext]): Expr[Unit] + + def continueCompletedFutureOnSameThread = false + + /** Return `null` if this future is not yet completed, or `Tryy[A]` with the completed result + * otherwise + */ + def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = + throw new UnsupportedOperationException("getCompleted not supported by this FutureSystem") + + /** Complete a promise with a value */ + def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit] + def completeWithSuccess[A: WeakTypeTag](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: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] + def tryyFailure[A: WeakTypeTag](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, isPastErasure: Boolean = false): Ops[u.type] + + @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7") + def freshenAllNames: Boolean = false + def emitTryCatch: Boolean = true + @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7") + def resultFieldName: String = "result" +} + +// 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, isPastErasure: Boolean = false): Ops[u.type] = new ScalaConcurrentOps[u.type](u, isPastErasure) + class ScalaConcurrentOps[Universe <: SymbolTable](u0: Universe, isPastErasure: Boolean) extends Ops[Universe](u0, isPastErasure) { + import u._ + + def promType(tp: Type): Type = phasedAppliedType(weakTypeOf[Promise[_]], tp) + def tryType(tp: Type): Type = phasedAppliedType(weakTypeOf[scala.util.Try[_]], tp) + + def createProm[A: WeakTypeTag]: Expr[Prom[A]] = { + val newProm = reify { Promise[A]() } + if (isPastErasure) + Expr[Prom[A]](newProm.tree match { // drop type apply + case ap@Apply(TypeApply(prom, _), args) => treeCopy.Apply(ap, prom, args) + }) + else newProm + } + + def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = reify { + prom.splice.future + } + + def future[A: WeakTypeTag](a: Expr[A])(execContext: Expr[ExecContext]) = { + val expr = reify { Future(a.splice)(execContext.splice) } + if (isPastErasure) + expr.tree match { + case ap@Apply(Apply(fut, a), execCtx) => Expr[Future[A]](treeCopy.Apply(ap, fut, a ++ execCtx)) + } + else expr + } + + def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[scala.util.Try[A] => B], + execContext: Expr[ExecContext]): Expr[Unit] = { + val expr = reify { future.splice.onComplete(fun.splice)(execContext.splice) } + if (isPastErasure) + expr.tree match { + case ap@Apply(Apply(fut, fun), execCtx) => Expr[Unit](treeCopy.Apply(ap, fut, fun ++ execCtx)) + } + else expr + } + + override def continueCompletedFutureOnSameThread: Boolean = true + + override def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = { + val valueGet = reify { future.splice.value.get } + val isCompleted = reify { future.splice.isCompleted } + reify { + if ({ if (isPastErasure) Expr[Boolean](Apply(isCompleted.tree, Nil)) else isCompleted }.splice) + { if (isPastErasure) Expr[Tryy[A]](Apply(valueGet.tree, Nil)) else valueGet }.splice else null + } + } + + def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = reify { + prom.splice.complete(value.splice) + literalUnitExpr.splice + } + + def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = { + val expr = reify { tryy.splice.isFailure } + if (isPastErasure) Expr[Boolean](Apply(expr.tree, Nil)) + else expr + } + + def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = { + val expr = reify { tryy.splice.get } + if (isPastErasure) Expr[A](Apply(expr.tree, Nil)) + else expr + } + + def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] = { + val expr = reify { scala.util.Success[A](a.splice) } + if (isPastErasure) + Expr[Tryy[A]](expr.tree match { // drop type apply + case ap@Apply(TypeApply(succ, _), args) => treeCopy.Apply(ap, succ, args) + }) + else expr + } + def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]] = { + val expr = reify { scala.util.Failure[A](a.splice) } + if (isPastErasure) + Expr[Tryy[A]](expr.tree match { // drop type apply + case ap@Apply(TypeApply(fail, _), args) => treeCopy.Apply(ap, fail, args) + }) + else expr + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala b/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala new file mode 100644 index 000000000000..aaa35c6f6eed --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala @@ -0,0 +1,27 @@ +/* + * 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.concurrent.Future +import scala.reflect.macros.whitebox.Context + +object ScalaConcurrentAsync extends AsyncBase { + type FS = ScalaConcurrentFutureSystem.type + val futureSystem: FS = ScalaConcurrentFutureSystem + + override def asyncImpl[T: c.WeakTypeTag](c: Context) + (body: c.Expr[T]) + (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[Future[T]] = { + super.asyncImpl[T](c)(body)(execContext) + } +} diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index fcb204d52253..01ed0fe954f1 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -66,6 +66,8 @@ 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 } - ) + ) ++ (if (!Async_async.exists) Map.empty else Map( + makeBlackbox(Async_async) { case app => c => c.global.analyzer.suppressMacroExpansion(app.tree) } // we'll deal with async much later + )) } } diff --git a/src/library/scala/Async.scala b/src/library/scala/Async.scala new file mode 100644 index 000000000000..e9b18935bb8b --- /dev/null +++ b/src/library/scala/Async.scala @@ -0,0 +1,63 @@ +/* + * 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 + +import scala.language.experimental.macros +import scala.concurrent.{Future, ExecutionContext} +import scala.annotation.compileTimeOnly + +/** + * 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/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 92fed37400ad..0acc73683f5f 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1634,6 +1634,10 @@ trait Definitions extends api.StandardDefinitions { } lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.runtime.java8") + + lazy val AsyncModule = getModuleIfDefined("scala.async") + lazy val Async_async = AsyncModule.map(async => getDeclIfDefined(async, nme.async)) + lazy val Async_await = AsyncModule.map(async => getDeclIfDefined(async, nme.await)) } } } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 4ab19770ee6a..07a43e909f09 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -679,6 +679,8 @@ trait StdNames { val asType: NameType = "asType" val asInstanceOf_ : NameType = "asInstanceOf" val asInstanceOf_Ob : NameType = "$asInstanceOf" + val async : NameType = "async" + val await : NameType = "await" val box: NameType = "box" val bytes: NameType = "bytes" val c: NameType = "c" From 9535c942b967d1275d3c4024ebf1a043f2c2699f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 7 Dec 2018 16:33:36 +0100 Subject: [PATCH 03/94] patch for async debugging --- .../scala/tools/nsc/typechecker/Implicits.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index e06e47d325fb..d156a68e34a8 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -327,10 +327,15 @@ trait Implicits { */ def memberWildcardType(name: Name, tp: Type) = { val result = refinedType(List(WildcardType), NoSymbol) - name match { - case x: TermName => result.typeSymbol.newMethod(x) setInfoAndEnter tp - case x: TypeName => result.typeSymbol.newAbstractType(x) setInfoAndEnter tp + val owner = result.typeSymbol orElse { // after erasure (for async?)... + val clazz = NoSymbol.newRefinementClass(NoPosition) + clazz setInfo RefinedType(Nil, newScope, clazz) } + (name match { + case x: TermName => owner.newMethod(x) + case x: TypeName => owner.newAbstractType(x) + }).setInfoAndEnter(tp) + result } @@ -339,7 +344,7 @@ trait Implicits { object HasMember { private val hasMemberCache = perRunCaches.newMap[Name, Type]() def apply(name: Name): Type = hasMemberCache.getOrElseUpdate(name, memberWildcardType(name, WildcardType)) - } + } /** An extractor for types of the form ? { name: (? >: argtpe <: Any*)restp } */ From 6a665e97f991c1fe039fdb269c5b26399967ccaa Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 30 Jan 2019 12:33:45 +0100 Subject: [PATCH 04/94] Implement async transform with a macro and a post-erasure phase The macro wraps the (untransformed) expression in the skeleton of the state machine class. Compiler phases between this point and the late async transform will correctly add outer pointers, lift captured vars into Ref objects etc. The state machine class itself will also undergo any transforms that it needs, such as specialization. The macro is wired up using the FastTrack facility in line with other macros that are quasi-intrinsic (ie, implemented in the scala-compiler.jar). After post-erasure, we run the ANF and state machine transform. The old code used the macro refletion API -- these usages are now being replaced with direct use of the richer Global API. --- build.sbt | 1 + src/compiler/scala/tools/nsc/Global.scala | 8 + .../nsc/transform/async/AsyncNames.scala | 18 - .../nsc/transform/async/AsyncPhase.scala | 113 +++++ .../nsc/transform/async/AsyncTransform.scala | 398 +++++++++--------- .../nsc/transform/async/ExprBuilder.scala | 41 +- .../nsc/transform/async/TransformUtils.scala | 88 ++-- .../nsc/transform/async/user/AsyncBase.scala | 146 +++---- .../nsc/transform/async/user/AsyncId.scala | 133 ------ .../transform/async/user/FutureSystem.scala | 70 +-- .../async/user/ScalaConcurrentAsync.scala | 10 +- .../tools/nsc/typechecker/Implicits.scala | 2 +- .../tools/nsc/typechecker/RefChecks.scala | 5 +- .../scala/tools/nsc/typechecker/Typers.scala | 16 +- .../scala/tools/reflect/FastTrack.scala | 8 +- src/library/scala/Async.scala | 2 +- .../reflect/internal/StdAttachments.scala | 3 + .../scala/reflect/internal/StdNames.scala | 12 + .../reflect/runtime/JavaUniverseForce.scala | 1 + test/async/run/auto_AfterRefchecksIssue.scala | 18 + .../run/auto_ArrayIndexOutOfBoundIssue.scala | 31 ++ test/async/run/auto_Combo.check | 1 + test/async/run/auto_Combo.scala | 26 ++ test/async/run/auto_Extractor.scala | 13 + .../run/auto_GenericTypeBoundaryIssue.check | 1 + .../run/auto_GenericTypeBoundaryIssue.scala | 32 ++ test/async/run/auto_Guard.scala | 13 + test/async/run/auto_MatchEndIssue.scala | 22 + .../run/auto_NegativeArraySizeException.scala | 12 + ...auto_NegativeArraySizeExceptionFine1.scala | 20 + .../async/run/auto_NestedMatchExtractor.scala | 16 + test/async/run/auto_ReturnTupleIssue.scala | 19 + test/async/run/auto_patternAlternative.scala | 15 + ...to_patternAlternativeBothAnnotations.scala | 15 + test/async/run/auto_polymorphicMethod.check | 3 + test/async/run/auto_polymorphicMethod.scala | 19 + test/async/run/auto_shadowing.check | 3 + test/async/run/auto_shadowing.scala | 22 + test/async/run/auto_shadowing0.scala | 23 + test/async/run/auto_shadowing2.scala | 27 ++ .../run/auto_shadowingRefinedTypes.scala | 27 ++ test/async/run/auto_test0.scala | 23 + test/async/run/concurrent_fetch.check | 3 + test/async/run/concurrent_fetch.scala | 19 + test/async/run/late-run.log | 31 ++ test/async/run/toughtype.scala-run.log | 7 + test/async/run/uncheckedBounds-run.log | 16 + test/files/neg/unit-returns-value.check | 2 +- test/files/run/t5380.check | 2 +- 49 files changed, 992 insertions(+), 564 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala delete mode 100644 src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala create mode 100644 test/async/run/auto_AfterRefchecksIssue.scala create mode 100644 test/async/run/auto_ArrayIndexOutOfBoundIssue.scala create mode 100644 test/async/run/auto_Combo.check create mode 100644 test/async/run/auto_Combo.scala create mode 100644 test/async/run/auto_Extractor.scala create mode 100644 test/async/run/auto_GenericTypeBoundaryIssue.check create mode 100644 test/async/run/auto_GenericTypeBoundaryIssue.scala create mode 100644 test/async/run/auto_Guard.scala create mode 100644 test/async/run/auto_MatchEndIssue.scala create mode 100644 test/async/run/auto_NegativeArraySizeException.scala create mode 100644 test/async/run/auto_NegativeArraySizeExceptionFine1.scala create mode 100644 test/async/run/auto_NestedMatchExtractor.scala create mode 100644 test/async/run/auto_ReturnTupleIssue.scala create mode 100644 test/async/run/auto_patternAlternative.scala create mode 100644 test/async/run/auto_patternAlternativeBothAnnotations.scala create mode 100644 test/async/run/auto_polymorphicMethod.check create mode 100644 test/async/run/auto_polymorphicMethod.scala create mode 100644 test/async/run/auto_shadowing.check create mode 100644 test/async/run/auto_shadowing.scala create mode 100644 test/async/run/auto_shadowing0.scala create mode 100644 test/async/run/auto_shadowing2.scala create mode 100644 test/async/run/auto_shadowingRefinedTypes.scala create mode 100644 test/async/run/auto_test0.scala create mode 100644 test/async/run/concurrent_fetch.check create mode 100644 test/async/run/concurrent_fetch.scala create mode 100644 test/async/run/late-run.log create mode 100644 test/async/run/toughtype.scala-run.log create mode 100644 test/async/run/uncheckedBounds-run.log diff --git a/build.sbt b/build.sbt index e0c513c8fc51..f10889a7a85f 100644 --- a/build.sbt +++ b/build.sbt @@ -1295,6 +1295,7 @@ lazy val root: Project = (project in file(".")) (testOnly in IntegrationTest in testP).toTask(" -- res scalap specialized").result map (_ -> "partest res scalap specialized"), (testOnly in IntegrationTest in testP).toTask(" -- instrumented presentation").result map (_ -> "partest instrumented presentation"), (testOnly in IntegrationTest in testP).toTask(" -- --srcpath scaladoc").result map (_ -> "partest --srcpath scaladoc"), + (testOnly in IntegrationTest in testP).toTask(" -- --srcpath async").result map (_ -> "partest --srcpath async"), (Keys.test in Test in osgiTestFelix).result map (_ -> "osgiTestFelix/test"), (Keys.test in Test in osgiTestEclipse).result map (_ -> "osgiTestEclipse/test"), (mimaReportBinaryIssues in library).result map (_ -> "library/mimaReportBinaryIssues"), diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 6a35a3abfcce..8de915e918c5 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -39,6 +39,7 @@ import scala.language.postfixOps import scala.tools.nsc.ast.{TreeGen => AstTreeGen} import scala.tools.nsc.classpath._ import scala.tools.nsc.profile.Profiler +import scala.tools.nsc.transform.async.AsyncPhase import java.io.Closeable class Global(var currentSettings: Settings, reporter0: Reporter) @@ -568,6 +569,12 @@ class Global(var currentSettings: Settings, reporter0: Reporter) val runsRightAfter = Some("erasure") } with PostErasure + // phaseName = "async" + object async extends { + val global: Global.this.type = Global.this + val runsAfter = List("posterasure") + val runsRightAfter = Some("posterasure") + } with AsyncPhase // phaseName = "lambdalift" object lambdaLift extends { @@ -672,6 +679,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) explicitOuter -> "this refs to outer pointers", erasure -> "erase types, add interfaces for traits", postErasure -> "clean up erased inline classes", + async -> "transform async/await into a state machine", lambdaLift -> "move nested functions to top level", constructors -> "move field definitions into constructors", mixer -> "mixin composition", diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala index 2eabf996ea95..0531b8b45abf 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -49,15 +49,6 @@ final class AsyncNames[U <: Names with Singleton](val u: U) { private val ifRes: TermNameCache = new TermNameCache("if") private val await: TermNameCache = new TermNameCache("await") - private val result = TermName("result$async") - private val completed: TermName = TermName("completed$async") - private val apply = TermName("apply") - private val stateMachine = TermName("stateMachine$async") - private val stateMachineT = stateMachine.toTypeName - private val state: u.TermName = TermName("state$async") - private val execContext = TermName("execContext$async") - private val tr: u.TermName = TermName("tr$async") - private val t: u.TermName = TermName("throwable$async") final class NameSource[N <: U#Name](cache: NameCache[N]) { private val count = new AtomicInteger(0) @@ -68,15 +59,6 @@ final class AsyncNames[U <: Names with Singleton](val u: U) { final val matchRes = new NameSource[U#TermName](self.matchRes) final val ifRes = new NameSource[U#TermName](self.ifRes) final val await = new NameSource[U#TermName](self.await) - final val completed = self.completed - final val result = self.result - final val apply = self.apply - final val stateMachine = self.stateMachine - final val stateMachineT = self.stateMachineT - final val state: u.TermName = self.state - final val execContext = self.execContext - final val tr: u.TermName = self.tr - final val t: u.TermName = self.t private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]() private val freshened = mutable.HashSet[Name]() diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala new file mode 100644 index 000000000000..74bf2c4fbbc5 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -0,0 +1,113 @@ +/* + * 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.Mode +import scala.tools.nsc.transform.{Transform, TypingTransformers} + +abstract class AsyncPhase extends Transform with TypingTransformers { + private val asyncNames_ = new AsyncNames[global.type](global) + import global._ + + val phaseName: String = "async" + override def enabled = true // TODO: should be off by default, enabled by flag +// { +// (currentRun.runDefinitions match { +// case null => new definitions.RunDefinitions +// case rd => rd +// }).Async_async.exists +// } + + + + object macroExpansion extends AsyncEarlyExpansion { + val u: global.type = global + val asyncBase = user.ScalaConcurrentAsync + import treeInfo.Applied + + def fastTrackEntry: (Symbol, PartialFunction[Applied, scala.reflect.macros.contexts.Context { val universe: u.type } => Tree]) = + (currentRun.runDefinitions.Async_async, { + // def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro ??? + case app@Applied(_, resultTp :: Nil, List(asyncBody :: Nil, execContext :: Nil)) => + c => c.global.async.macroExpansion(c.global.analyzer.suppressMacroExpansion(app.tree), execContext, resultTp.tpe, c.internal.enclosingOwner) + }) + + } + + def newTransformer(unit: CompilationUnit): Transformer = new AsyncTransformer(unit) + + // TODO: support more than the original late expansion tests + // 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/...? + class AsyncTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + + abstract class AsyncTransformBase(asyncBase: user.AsyncBase) extends AsyncTransform(asyncBase) { + val u: global.type = global + import u._ + + val asyncNames: AsyncNames[u.type] = asyncNames_ + + def typecheck(tree: Tree): Tree = localTyper.typed(tree) + def abort(pos: Position, msg: String): Nothing = {localTyper.context.reporter.error(pos, msg); ???} + def error(pos: Position, msg: String): Unit = localTyper.context.reporter.error(pos, msg) + + val typingTransformers = + new TypingTransformers { + def callsiteTyper = localTyper + } + } + + object asyncTransformerConcurrent extends AsyncTransformBase(user.ScalaConcurrentAsync) { + val Async_async = currentRun.runDefinitions.Async_async + val Async_await = currentRun.runDefinitions.Async_await + } + + lazy val uncurrier = new uncurry.UnCurryTransformer(unit) + + override def transform(tree: Tree): Tree = + tree match { + // { + // class stateMachine$async extends scala.runtime.AbstractFunction1 with Function0$mcV$sp { + // def apply(tr$async: scala.util.Try): Unit = { // symbol of this def is `applySym`, symbol of its param named "tr$async" is `trParamSym` + // ... + // } + // val stateMachine = ... + // ... + // } + case Block((cd@ClassDef(mods, tpnme.stateMachine, _, impl@Template(parents, self, stats))) :: (vd@ValDef(_, nme.stateMachine, tpt, _)) :: rest, expr) if tpt.tpe.typeSymbol == cd.symbol => + import asyncTransformerConcurrent._ + + stats.collectFirst { + case dd@DefDef(mods, name@nme.apply, tparams, vparamss@List(tr :: Nil), tpt, Block( Apply(asyncMacro, List(asyncBody, execContext)) :: Nil, Literal(Constant(())))) => + asyncTransform(asyncBody, dd.symbol, tr.symbol, execContext) match { + case Some((newRhs, liftables)) => + Right(treeCopy.DefDef(dd, mods, name, tparams, vparamss, tpt, newRhs).setType(null) /* need to retype */ :: liftables) + case None => + val thunkFun = localTyper.typedPos(asyncBody.pos)(Function(Nil, asyncBody)).asInstanceOf[Function] + thunkFun.body.changeOwner(dd.symbol, thunkFun.symbol) + thunkFun.setType(definitions.functionType(Nil, exitingTyper { futureSystemOps.tryTypeToResult(tr.symbol.info) })) // ugh (uncurry normally runs before erasure and wants a full function type) + Left(futureSystemOps.future(uncurrier.transformFunction(thunkFun), execContext)) + } + } match { + case Some(Left(simple)) => localTyper.typed(simple) + case Some(Right(newStats@(newApply :: liftables))) => + val newTempl = treeCopy.Template(impl, parents, self, stats.filterNot(_.symbol == newApply.symbol) ::: newStats) + treeCopy.Block(tree, localTyper.typedClassDef(treeCopy.ClassDef(cd, mods, tpnme.stateMachine, Nil, newTempl)) :: vd :: rest, expr) + } +// case ap@Apply(fun, rhs :: execContext :: Nil) if fun.symbol == asyncTransformerConcurrent.Async_async => transformAsyncStd(rhs, execContext) + case tree => + super.transform(tree) + } + + } +} diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 149ceb816d76..549b3a53fa5f 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -12,152 +12,230 @@ package scala.tools.nsc.transform.async -import user.AsyncBase -import scala.reflect.internal.{Flags, SymbolTable} -import scala.tools.nsc.Global +import user.{AsyncBase, FutureSystem} +import scala.reflect.internal.Flags -abstract class AsyncTransform(val asyncBase: AsyncBase, val u: SymbolTable) extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables { +// TODO: check there's no await outside of an async block + +abstract class AsyncEarlyExpansion extends AsyncContext { import u._ - import typingTransformers.{TypingTransformApi, typingTransform} - def asyncTransform(body: Tree, execContext: Tree, enclosingOwner: Symbol, asyncPos: Position)(resultType: Type): Tree = { - markContainsAwait(body) // TODO AM: is this needed? - reportUnsupportedAwaits(body) + // NOTE: this part runs during typer + lazy val futureSystem: FutureSystem = asyncBase.futureSystem + lazy val futureSystemOps: futureSystem.Ops[u.type] = futureSystem.mkOps(u, false) + + /** 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: + { + class stateMachine$async extends scala.runtime.AbstractFunction1[scala.util.Try[`resultType`],Unit] with () => Unit { + def (): stateMachine$async = { + stateMachine$async.super.(); + () + }; + private[this] var state$async: Int = 0; + 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 = `execContext`; + def execContext$async: scala.concurrent.ExecutionContext = stateMachine$async.this.execContext$async; + def apply(): Unit = stateMachine$async.this.apply(null); + def apply(tr$async: scala.util.Try[`resultType`]): Unit = { + scala.async.async[`resultType`](`asyncBody`)(`execContext`); + () + } + }; + val stateMachine$async: stateMachine$async = new stateMachine$async(); + Future.apply[stateMachine$async](stateMachine$async)(stateMachine$async.execContext$async); + stateMachine$async.result$async.future + } + */ + def apply(asyncBody: Tree, execContext: Tree, resultType: Type, originalOwner: Symbol) = { + val tryResult = futureSystemOps.tryType(resultType) - // Transform to A-normal form: - // - no await calls in qualifiers or arguments, - // - if/match only used in statement position. - val anfTree0: Block = anfTransform(body, enclosingOwner) + 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.asClass.isTrait) + + val fun1Tpe = + if (useClass) definitions.abstractFunctionType(tryResult :: Nil, definitions.UnitTpe) + else definitions.functionType(tryResult :: Nil, definitions.UnitTpe) + + // We extend () => Unit so we can pass this class as the by-name argument to `Future.apply`. + // See SI-1247 for the the optimization that avoids creation. + val funParents = List(fun1Tpe, definitions.functionType(Nil, definitions.UnitTpe)) + (customParents ::: funParents).map(TypeTree(_)) + } - val anfTree = futureSystemOps.postAnfTransform(anfTree0) + val stateVar = + ValDef(Modifiers(Flags.MUTABLE | Flags.PRIVATE | Flags.LOCAL), nme.state, TypeTree(definitions.IntTpe), Literal(Constant(StateAssigner.Initial))) - cleanupContainsAwaitAttachments(anfTree) - markContainsAwait(anfTree) + val resultVal = + ValDef(NoMods, nme.result, TypeTree(futureSystemOps.promType(resultType)), futureSystemOps.createProm(resultType)) - // We annotate the type of the whole expression as `T @uncheckedBounds` so as not to introduce - // warnings about non-conformant LUBs. See SI-7694 - val resultTypeTag = WeakTypeTag(uncheckedBounds(transformType(resultType))) + val execContextVal = + ValDef(NoMods, nme.execContext, TypeTree(execContext.tpe), execContext) - val applyDefDefDummyBody: DefDef = apply1ToUnitDefDef(tryAny) + val apply0Def = + DefDef(NoMods, nme.apply, Nil, List(Nil), TypeTree(definitions.UnitTpe), Apply(Ident(nme.apply), Literal(Constant(null)) :: Nil)) - // Create `ClassDef` of state machine with empty method bodies for `resume` and `apply`. - // TODO AM: can we only create the symbol for the state machine class for now and then type check the assembled whole later, - // instead of splicing stuff in (spliceMethodBodies)? - val stateMachine: ClassDef = { - val body: List[Tree] = { - val stateVar = ValDef(Modifiers(Flags.MUTABLE | Flags.PRIVATE | Flags.LOCAL), name.state, TypeTree(definitions.IntTpe), Literal(Constant(StateAssigner.Initial))) - val resultAndAccessors = - mkMutableField(transformType(futureSystemOps.promType(uncheckedBounds(resultType))), name.result, futureSystemOps.createProm[Nothing](resultTypeTag).tree) - val execContextValDef = - mkField(execContext.tpe, name.execContext, execContext) - - List(stateVar) ++ resultAndAccessors ++ execContextValDef ++ List(applyDefDefDummyBody, apply0DefDef) + 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), asyncBody).updateAttachment(ChangeOwnerAttachment(originalOwner)) } - val customParents = futureSystemOps.stateMachineClassParents map transformType - // 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.asClass.isTrait) + atPos(asyncBody.pos)(ClassDef(NoMods, tpnme.stateMachine, Nil, + gen.mkTemplate(parents, noSelfType, NoMods, List(Nil), + List(stateVar, resultVal, execContextVal, apply0Def, applyFSM)))) + } - // We extend () => Unit so we can pass this class as the by-name argument to `Future.apply`. - // See SI-1247 for the the optimization that avoids creation. - val funParents = List(function1ToUnit(tryAny, useClass), function0ToUnit) + val newStateMachine = ValDef(NoMods, nme.stateMachine, TypeTree(), Apply(Select(New(Ident(tpnme.stateMachine)), nme.CONSTRUCTOR), Nil)) + // Note the invocation `.apply()` in `scala.concurrent.Future.apply[$resultType](stateMachine$async.apply())(stateMachine$async.execContext$async);` + // so that, after uncurry, we get: scala.concurrent.Future.apply[$resultType](stateMachine$async, stateMachine$async.execContext$async()); + val stateMachineToFuture = futureSystemOps.future(Apply(Select(Ident(nme.stateMachine), nme.apply), Nil), Select(Ident(nme.stateMachine), nme.execContext)) + val promToFuture = futureSystemOps.promiseToFuture(Select(Ident(nme.stateMachine), nme.result)) - // TODO AM: after erasure we have to change the order of these parents etc - val templ = gen.mkTemplate(transformParentTypes(customParents ::: funParents).map(TypeTree(_)), noSelfType, NoMods, List(Nil), body) + Block(List(stateMachine, newStateMachine, stateMachineToFuture), promToFuture) + } +} - // TODO AM: can we skip the type checking and just create a symbol? - val classSym = enclosingOwner.newClassSymbol(name.stateMachineT, asyncPos, 0) - val cd = ClassDef(NoMods, name.stateMachineT, Nil, templ).setSymbol(classSym).asInstanceOf[typingTransformers.global.ClassDef] - classSym.setInfo(typingTransformers.callsiteTyper.namer.monoTypeCompleter(cd)) - typingTransformers.callsiteTyper.typedClassDef(atPos(asyncPos)(cd)).asInstanceOf[ClassDef] - } +// This was originally a macro -- TODO: complete integration with compiler universe (use global instead of scala.reflect.internal stuff) +abstract class AsyncTransform(val asyncBase: AsyncBase) extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables { + import u._ + import typingTransformers.{TypingTransformApi, typingTransform} - val asyncBlock: AsyncBlock = { - val symLookup = SymLookup(stateMachine.symbol, applyDefDefDummyBody.vparamss.head.head.symbol) - buildAsyncBlock(anfTree, symLookup) - } + // synthesize the state machine logic -- explode the apply method's rhs and lift local vals to field defs in the state machine + /* + class stateMachine$async extends scala.runtime.AbstractFunction1 with Function0$mcV$sp { + def (): stateMachine$async = { + stateMachine$async.super.(); + stateMachine$async.super./*Function0*/$init$(); + () + }; + private[this] var state$async: Int = 0; + private[this] val result$async: scala.concurrent.Promise = Promise.apply(); + def result$async(): scala.concurrent.Promise = stateMachine$async.this.result$async; + private[this] val execContext$async: scala.concurrent.ExecutionContext = `execContext`; + def execContext$async(): scala.concurrent.ExecutionContext = stateMachine$async.this.execContext$async; + def apply(): Unit = stateMachine$async.this.apply$mcV$sp(); + def apply(tr$async: scala.util.Try): Unit = { // symbol of this def is `applySym`, symbol of its param named "tr$async" is `trParamSym` + scala.async.async(`asyncBody`, `execContext`); + () + }; + def apply$mcV$sp(): Unit = stateMachine$async.this.apply(null); + def apply(v1: Object): Object = { + stateMachine$async.this.apply(v1.$asInstanceOf[scala.util.Try]()); + scala.runtime.BoxedUnit.UNIT + }; + def apply(): Object = { + stateMachine$async.this.apply(); + scala.runtime.BoxedUnit.UNIT + } + }; + */ + def asyncTransform(asyncBody: Tree, applySym: Symbol, trParamSym: Symbol, execContext: Tree): Option[(Tree, List[Tree])] = { + val asyncPos = asyncBody.pos + val stateMachineClass = applySym.owner + val resultType = exitingTyper { futureSystemOps.tryTypeToResult(trParamSym.info) } - val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) + markContainsAwait(asyncBody) // ANF transform also relies on whether something contains await + reportUnsupportedAwaits(asyncBody) - // live variables analysis - // the result map indicates in which states a given field should be nulled out - val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields) + // Transform to A-normal form: + // - no await calls in qualifiers or arguments, + // - if/match only used in statement position. + val anfTree0: Block = anfTransform(asyncBody, applySym) - for ((state, flds) <- assignsOf) { - val assigns = flds.map { fld => - val fieldSym = fld.symbol - val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info, asyncPos)) - val nulled = nullOut(fieldSym) - if (isLiteralUnit(nulled)) assign - else Block(nulled :: Nil, assign) - } - val asyncState = asyncBlock.asyncStates.find(_.state == state).get - asyncState.stats = assigns ++ asyncState.stats - } + val anfTree = futureSystemOps.postAnfTransform(anfTree0) - def startStateMachine: Tree = { - val stateMachineSpliced: Tree = - spliceMethodBodies(liftedFields, stateMachine, atPos(asyncPos)(asyncBlock.onCompleteHandler(resultTypeTag)), enclosingOwner) - - val applyCtor = - typingTransform(Apply(Select(New(Ident(stateMachine.symbol)), nme.CONSTRUCTOR), Nil))((tree, api) => api.typecheck(tree)) - - val (stateMachineUsingOuter, newStateMachine) = - if (!isPastErasure) (stateMachineSpliced, applyCtor) - else { - // Since explicit outers has already run (it happens before erasure), we must run it ourselves on the class we're synthesizing. - // The state machine class is going to be lifted out by the flatten phase, but expressions contained in it - // will likely still need to access the outer class's instance. - // Thus, we add the standard outer argument to the constructor and supply it when instantiating the state machine. - // Lambdalift will also look for this and transform appropriately. - val global: u.type with Global = u.asInstanceOf[u.type with Global] - - stateMachineSpliced foreach { - case dt: DefTree if dt.hasExistingSymbol => // TODO AM: why can't we skip symbols that hasTypeAt(currentRun.explicitouterPhase.id) - val sym = dt.symbol - val classSym = sym.asInstanceOf[global.Symbol] - val newInfo = global.explicitOuter.transformInfo(classSym, classSym.info) - // we can't go back to explicit outer phase to retro-actively un-erase our current info and then add explicit outers, - // we just have to run the explicitOuter info transform now (during erasure) and hope for the best - if (newInfo ne sym.info) - classSym.setInfo(newInfo) - case _ => - } + // TODO: why redo this? + cleanupContainsAwaitAttachments(anfTree) + markContainsAwait(anfTree) - val explicitOuters = new global.explicitOuter.ExplicitOuterTransformer(typingTransformers.callsiteTyper.context.unit.asInstanceOf[global.CompilationUnit]) + val asyncBlock = buildAsyncBlock(anfTree, SymLookup(stateMachineClass, trParamSym)) - val stateMachineWithOuters = explicitOuters.transform(stateMachineSpliced.asInstanceOf[global.Tree]) + // generate lean code for the simple case of `async { 1 + 1 }` + if (asyncBlock.asyncStates.lengthCompare(1) == 0) None + else { + val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) - val newStateMachine = explicitOuters.atOwner(stateMachine.symbol.owner.asInstanceOf[global.Symbol]) { - explicitOuters.transform(applyCtor.asInstanceOf[global.Tree]) - } + // live variables analysis + // the result map indicates in which states a given field should be nulled out + val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields) - (stateMachineWithOuters, newStateMachine) + for ((state, flds) <- assignsOf) { + val assigns = flds.map { fld => + val fieldSym = fld.symbol + val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info, asyncPos)) + val nulled = nullOut(fieldSym) + if (isLiteralUnit(nulled)) assign + else Block(nulled :: Nil, assign) } + val asyncState = asyncBlock.asyncStates.find(_.state == state).get + asyncState.stats = assigns ++ asyncState.stats + } - def selectStateMachine(selection: TermName) = Select(Ident(name.stateMachine), selection) - def selectStateMachineResult = - applyNilAfterUncurry(selectStateMachine(name.result)) + val liftedSyms = liftedFields.map(_.symbol).toSet + liftedSyms.foreach { sym => + if (sym != null) { + sym.owner = stateMachineClass + if (sym.isModule) + sym.asModule.moduleClass.owner = stateMachineClass + } + } - Block(List[Tree]( - stateMachineUsingOuter, - ValDef(NoMods, name.stateMachine, TypeTree(), newStateMachine), - spawn(Apply(selectStateMachine(name.apply), Nil), selectStateMachine(name.execContext))), - promiseToFuture(selectStateMachineResult, resultType)) - } + // Replace the ValDefs in the async block with Assigns to the corresponding lifted + // fields. Similarly, replace references to them with references to the field. + val useFields: (Tree, TypingTransformApi) => Tree = (tree, api) => { + def fieldSel = + atPos(tree.pos)(Select(This(stateMachineClass).setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) + tree match { + case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => + if (tree.symbol.asTerm.isLazy) literalUnit + else assignUnitType(treeCopy.Assign(tree, fieldSel, api.recur(rhs.changeOwner((tree.symbol, api.currentOwner))))) + case _: DefTree if liftedSyms(tree.symbol) => EmptyTree + case Ident(name) if liftedSyms(tree.symbol) => fieldSel.setType(tree.tpe) + case _ => api.default(tree) + } + } + + val liftablesUseFields = liftedFields.map { + case vd: ValDef if !vd.symbol.asTerm.isLazy => vd + case x => typingTransform(x, stateMachineClass)(useFields) + } + + liftablesUseFields.foreach { t => + if (t.symbol != null) { + stateMachineClass.info.decls.enter(t.symbol) + // TODO AM: refine the resetting of the lazy flag -- this is so that local lazy vals that are lifted to the class + // actually get their LazyRef allocated to the var that holds the lazy val's reference + t.symbol.resetFlag(Flags.LAZY) + } + } - val isSimple = asyncBlock.asyncStates.size == 1 - val result = - if (isSimple) spawn(body, execContext) // generate lean code for the simple case of `async { 1 + 1 }` - else startStateMachine + val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler(WeakTypeTag(transformType(resultType)))) + val applyRhs = typingTransform(applyBody, stateMachineClass)(useFields) + if (AsyncUtils.verbose) { + val location = try asyncBody.pos.source.path catch { + case _: UnsupportedOperationException => asyncBody.pos.toString + } + logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) + } + futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) - if(AsyncUtils.verbose) { - val location = try body.pos.source.path catch { case _: UnsupportedOperationException => body.pos.toString } - logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) + Some((cleanupContainsAwaitAttachments(applyRhs), liftablesUseFields)) } - futureSystemOps.dot(enclosingOwner, body).foreach(f => f(asyncBlock.toDot)) - cleanupContainsAwaitAttachments(result) } def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { @@ -167,90 +245,4 @@ abstract class AsyncTransform(val asyncBase: AsyncBase, val u: SymbolTable) exte AsyncUtils.vprintln("===== DOT =====") AsyncUtils.vprintln(block.toDot) } - - /** - * Build final `ClassDef` tree of state machine class. - * - * @param liftables trees of definitions that are lifted to fields of the state machine class - * @param tree `ClassDef` tree of the state machine class - * @param applyBody tree of onComplete handler (`apply` method) - * @return transformed `ClassDef` tree of the state machine class - */ - def spliceMethodBodies(liftables: List[Tree], tree: ClassDef, applyBody: Tree, enclosingOwner: Symbol): Tree = { - val liftedSyms = liftables.map(_.symbol).toSet - val stateMachineClass = tree.symbol - liftedSyms.foreach { - sym => - if (sym != null) { - sym.owner = stateMachineClass - if (sym.isModule) - sym.asModule.moduleClass.owner = stateMachineClass - } - } - // Replace the ValDefs in the splicee with Assigns to the corresponding lifted - // fields. Similarly, replace references to them with references to the field. - // - // This transform will only be run on the RHS of `def foo`. - val useFields: (Tree, TypingTransformApi) => Tree = (tree, api) => tree match { - case _ if api.currentOwner == stateMachineClass => - api.default(tree) - case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => - api.atOwner(api.currentOwner) { - val fieldSym = tree.symbol - if (fieldSym.asTerm.isLazy) literalUnit - else { - val lhs = atPos(tree.pos) { - gen.mkAttributedStableRef(thisType(fieldSym.owner.asClass), fieldSym) - } - assignUnitType(treeCopy.Assign(tree, lhs, api.recur(rhs))).changeOwner((fieldSym, api.currentOwner)) - } - } - case _: DefTree if liftedSyms(tree.symbol) => - EmptyTree - case Ident(name) if liftedSyms(tree.symbol) => - val fieldSym = tree.symbol - atPos(tree.pos) { - gen.mkAttributedStableRef(thisType(fieldSym.owner.asClass), fieldSym).setType(tree.tpe) - } - case _ => - api.default(tree) - } - - val liftablesUseFields = liftables.map { - case vd: ValDef if !vd.symbol.asTerm.isLazy => vd - case x => typingTransform(x, stateMachineClass)(useFields) - } - - tree.children.foreach(_.changeOwner((enclosingOwner, tree.symbol))) - val treeSubst = tree - - /* Fixes up DefDef: use lifted fields in `body` */ - def fixup(dd: DefDef, body: Tree, api: TypingTransformApi): Tree = { - val spliceeAnfFixedOwnerSyms = body - val newRhs = typingTransform(spliceeAnfFixedOwnerSyms, dd.symbol)(useFields) - val newRhsTyped = api.atOwner(dd, dd.symbol)(api.typecheck(newRhs)) - treeCopy.DefDef(dd, dd.mods, dd.name, dd.tparams, dd.vparamss, dd.tpt, newRhsTyped) - } - - liftablesUseFields.foreach(t => if (t.symbol != null) stateMachineClass.info.decls.enter(t.symbol)) - - // TODO AM: refine the resetting of the lazy flag -- this is so that local lazy vals that are lifted to the class - // actually get their LazyRef allocated to the var that holds the lazy val's reference - if (isPastErasure) - liftablesUseFields.foreach(t => if (t.symbol != null) t.symbol.resetFlag(Flags.LAZY)) - - val result0 = transformAt(treeSubst) { - case t@Template(parents, self, stats) => - (api: TypingTransformApi) => { - treeCopy.Template(t, parents, self, liftablesUseFields ++ stats) - } - } - val result = transformAt(result0) { - case dd@DefDef(_, name.apply, _, List(List(_)), _, _) if dd.symbol.owner == stateMachineClass => - (api: TypingTransformApi) => - val typedTree = fixup(dd, applyBody.changeOwner((enclosingOwner, dd.symbol)), api) - typedTree - } - result - } } diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index e730c66332c5..e0c5549d220d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -28,16 +28,10 @@ trait ExprBuilder extends TransformUtils { def nullOut(fieldSym: Symbol): Tree = asyncBase.nullOut(u)(Expr[String](Literal(Constant(fieldSym.name.toString))), Expr[Any](Ident(fieldSym))).tree - def spawn(tree: Tree, execContext: Tree): Tree = - futureSystemOps.future(Expr[Unit](tree))(Expr[futureSystem.ExecContext](execContext)).tree - - def promiseToFuture(prom: Tree, resTp: Type): Tree = - futureSystemOps.promiseToFuture(Expr[Nothing](prom))(WeakTypeTag(resTp)).tree - def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = u.Expr[T](rootMirror, FixedMirrorTreeCreator(rootMirror, tree)) def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = u.WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, tpe)) - lazy val tryAny= transformType(futureSystemOps.tryType(definitions.AnyTpe)) + lazy val tryAny = transformType(futureSystemOps.tryType(definitions.AnyTpe)) private val stateAssigner = new StateAssigner private val labelDefStates = collection.mutable.Map[Symbol, Int]() @@ -106,10 +100,10 @@ trait ExprBuilder extends TransformUtils { override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { val fun = This(tpnme.EMPTY) val callOnComplete = futureSystemOps.onComplete[Any, Unit](Expr[futureSystem.Fut[Any]](awaitable.expr), - Expr[futureSystem.Tryy[Any] => Unit](fun), Expr[futureSystem.ExecContext](Ident(name.execContext))).tree + Expr[futureSystem.Tryy[Any] => Unit](fun), Expr[futureSystem.ExecContext](Ident(nme.execContext))).tree val tryGetOrCallOnComplete: List[Tree] = if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempName = name.completed + val tempName = nme.completed val initTemp = ValDef(NoMods, tempName, TypeTree(tryAny), futureSystemOps.getCompleted[Any](Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) val null_ne = Select(Literal(Constant(null)), TermName("ne")) val ifTree = @@ -389,7 +383,7 @@ trait ExprBuilder extends TransformUtils { def memberRef(name: TermName): Tree = gen.mkAttributedRef(stateMachineMember(name)) - def selectResult = applyNilAfterUncurry(memberRef(name.result)) + def selectResult = applyNilAfterUncurry(memberRef(nme.result)) } private lazy val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal") @@ -535,7 +529,6 @@ trait ExprBuilder extends TransformUtils { * * The resulting tree has the following shape: * - * def resume(): Unit = { * try { * state match { * case 0 => { @@ -547,10 +540,9 @@ trait ExprBuilder extends TransformUtils { * } catch { * case NonFatal(t) => result.failure(t) * } - * } */ private def resumeFunTree[T: WeakTypeTag]: Tree = { - val stateMemberRef = symLookup.memberRef(name.state) + val stateMemberRef = symLookup.memberRef(nme.state) val body = Match(stateMemberRef, mkCombinedHandlerCases[T] ++ @@ -563,20 +555,20 @@ trait ExprBuilder extends TransformUtils { body1, List( CaseDef( - Bind(name.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), + Bind(nme.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), EmptyTree, { val branchTrue = { - val t = Expr[Throwable](Ident(name.t)) + val t = Expr[Throwable](Ident(nme.t)) val complete = futureSystemOps.completeProm[T]( Expr[futureSystem.Prom[T]](symLookup.selectResult), futureSystemOps.tryyFailure[T](t)).tree Block(toList(complete), Return(literalUnit)) } - If(Apply(Ident(NonFatalClass), List(Ident(name.t))), branchTrue, Throw(Ident(name.t))) + If(Apply(Ident(NonFatalClass), List(Ident(nme.t))), branchTrue, Throw(Ident(nme.t))) branchTrue })), EmptyTree) } - private lazy val stateMemberSymbol = symLookup.stateMachineMember(name.state) + private lazy val stateMemberSymbol = symLookup.stateMachineMember(nme.state) private val compactStateTransform = new Transformer { override def transform(tree: Tree): Tree = tree match { case as @ Assign(lhs, Literal(Constant(i: Integer))) if lhs.symbol == stateMemberSymbol => @@ -605,20 +597,9 @@ trait ExprBuilder extends TransformUtils { } /** - * Builds a `match` expression used as an onComplete handler. - * - * Assumes `tr: Try[Any]` is in scope. The resulting tree has the following shape: - * - * state match { - * case 0 => - * x11 = tr.get.asInstanceOf[Double] - * state = 1 - * resume() - * } + * Builds a `match` expression used as an onComplete handler, wrapped in a while(true) loop. */ def onCompleteHandler[T: WeakTypeTag]: Tree = { - // TODO AM: this was unused -- don't understand what this does - // val onCompletes = initStates.flatMap(_.mkOnCompleteHandler[T]) forever { adaptToUnit(toList(resumeFunTree)) } @@ -634,7 +615,7 @@ trait ExprBuilder extends TransformUtils { case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef) private def mkStateTree(nextState: Int, symLookup: SymLookup): Tree = - Assign(symLookup.memberRef(name.state), Literal(Constant(nextState))) + Assign(symLookup.memberRef(nme.state), Literal(Constant(nextState))) private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef = mkHandlerCase(num, adaptToUnit(rhs)) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 67df1baae505..11303af6ffed 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -21,38 +21,27 @@ import scala.language.existentials private[async] trait AsyncContext { val asyncBase: AsyncBase + val u: Global +} - val u: SymbolTable +// Logic sensitive to where we are in the pipeline +// (intend to move the transformation as late as possible, to avoid lugging all these trees around) +trait PhasedTransform extends AsyncContext { import u._ - val Async_async: Symbol - val Async_await: Symbol - // macro context interface -- the rest is meant to be independent of our being a macro (planning to move async into the compiler) def abort(pos: Position, msg: String): Nothing def error(pos: Position, msg: String): Unit def typecheck(tree: Tree): Tree - val asyncNames: AsyncNames[u.type] - object name extends asyncNames.AsyncName { - def fresh(name: TermName): TermName = freshenIfNeeded(name) - def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName - } -} - -// Logic sensitive to where we are in the pipeline -// (intend to move the transformation as late as possible, to avoid lugging all these trees around) -trait PhasedTransform extends AsyncContext { - import u._ - // We're not that granular, but keeping separate flag for semantics private lazy val isPastUncurry = isPastErasure private lazy val emptyParamss: List[Nil.type] = if (isPastUncurry) List(Nil) else Nil protected def applyNilAfterUncurry(t: Tree) = if (isPastUncurry) Apply(t, Nil) else t lazy val isPastErasure = { - val erasurePhase = u.asInstanceOf[Global].currentRun.erasurePhase - erasurePhase != NoPhase && u.asInstanceOf[Global].isPast(erasurePhase) + val erasurePhase = u.currentRun.erasurePhase + erasurePhase != NoPhase && u.isPast(erasurePhase) } def literalNull = Literal(Constant(null)) @@ -80,29 +69,6 @@ trait PhasedTransform extends AsyncContext { } - def function0ToUnit = typeOf[() => Unit] - def apply0DefDef: DefDef = - DefDef(NoMods, name.apply, Nil, emptyParamss, TypeTree(definitions.UnitTpe), Apply(Ident(name.apply), literalNull :: Nil)) - - def function1ToUnit(argTp: Type, useClass: Boolean) = { - val fun = - if (useClass) symbolOf[scala.runtime.AbstractFunction1[Any, Any]] - else symbolOf[scala.Function1[Any, Any]] - - appliedType(fun, argTp, typeOf[Unit]) - } - def apply1ToUnitDefDef(argTp: Type): DefDef = { - val applyVParamss = List(List(ValDef(Modifiers(Flags.PARAM), name.tr, TypeTree(argTp), EmptyTree))) - DefDef(NoMods, name.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), literalUnit) - } - - - def transformParentTypes(tps: List[Type]) = { - val tpsErased = tps.map(transformType) - assert(tpsErased.head.typeSymbol.isClass) - tpsErased - } - def transformType(tp: Type) = if (isPastErasure) transformedType(tp) else tp def mkAsInstanceOf(qual: Tree, tp: Type) = gen.mkCast(qual, tp) @@ -229,6 +195,12 @@ private[async] trait TransformUtils extends PhasedTransform { import typingTransformers.{TypingTransformApi, typingTransform} import u._ + val asyncNames: AsyncNames[u.type] + object name extends asyncNames.AsyncName { + def fresh(name: TermName): TermName = freshenIfNeeded(name) + def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName + } + def emitTryCatch: Boolean = asyncBase.futureSystem.emitTryCatch def maybeTry(block: Tree, catches: List[CaseDef], finalizer: Tree) = @@ -236,6 +208,9 @@ private[async] trait TransformUtils extends PhasedTransform { lazy val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException") + val Async_async: Symbol + val Async_await: Symbol + def isAsync(fun: Tree) = fun.symbol == Async_async def isAwait(fun: Tree) = fun.symbol == Async_await @@ -562,13 +537,12 @@ private[async] trait TransformUtils extends PhasedTransform { } } - - val typingTransformers: TypingTransformers + // TODO get rid of all of this and use compiler's facilities directly + val typingTransformers: TypingTransformers { val global: u.type } abstract class TypingTransformers extends scala.tools.nsc.transform.TypingTransformers { - val global: u.type with Global = u.asInstanceOf[u.type with Global] - - def callsiteTyper: global.analyzer.Typer + val global: u.type = u + def callsiteTyper: analyzer.Typer trait TransformApi { /** Calls the current transformer on the given tree. @@ -612,37 +586,37 @@ private[async] trait TransformUtils extends PhasedTransform { def default(tree: Tree): Tree = superTransform(tree) } def superTransform(tree: Tree) = super.transform(tree) - override def transform(tree: Tree): Tree = hof(tree, api).asInstanceOf[global.Tree] + override def transform(tree: Tree): Tree = hof(tree, api) } // def transform(tree: Tree)(transformer: (Tree, TransformApi) => Tree): Tree = new HofTransformer(transformer).transform(tree) class HofTypingTransformer(hof: (Tree, TypingTransformApi) => Tree) extends TypingTransformer(callsiteTyper.context.unit) { self => currentOwner = callsiteTyper.context.owner - curTree = global.EmptyTree + curTree = EmptyTree localTyper = { - val ctx: global.analyzer.Context = callsiteTyper.context.make(unit = callsiteTyper.context.unit) - (if (phase.erasedTypes) global.erasure.newTyper(ctx.asInstanceOf[global.erasure.Context]) else global.analyzer.newTyper(ctx)).asInstanceOf[global.analyzer.Typer] + val ctx: analyzer.Context = callsiteTyper.context.make(unit = callsiteTyper.context.unit) + if (phase.erasedTypes) erasure.newTyper(ctx.asInstanceOf[erasure.Context]).asInstanceOf[analyzer.Typer] else analyzer.newTyper(ctx) } val api = new TypingTransformApi { def recur(tree: Tree): Tree = hof(tree, this) def default(tree: Tree): Tree = superTransform(tree) - def atOwner[T](owner: Symbol)(op: => T): T = self.atOwner(owner.asInstanceOf[global.Symbol])(op) - def atOwner[T](tree: Tree, owner: Symbol)(op: => T): T = self.atOwner(tree.asInstanceOf[global.Tree], owner.asInstanceOf[global.Symbol])(op) + def atOwner[T](owner: Symbol)(op: => T): T = self.atOwner(owner)(op) + def atOwner[T](tree: Tree, owner: Symbol)(op: => T): T = self.atOwner(tree, owner)(op) def currentOwner: Symbol = self.currentOwner - def typecheck(tree: Tree): Tree = localTyper.typed(tree.asInstanceOf[global.Tree]) + def typecheck(tree: Tree): Tree = localTyper.typed(tree) } - def superTransform(tree: Tree) = super.transform(tree.asInstanceOf[global.Tree]) - override def transform(tree: global.Tree): global.Tree = hof(tree, api).asInstanceOf[global.Tree] + def superTransform(tree: Tree) = super.transform(tree) + override def transform(tree: Tree): Tree = hof(tree, api) } - def typingTransform(tree: Tree)(transformer: (Tree, TypingTransformApi) => Tree): Tree = new HofTypingTransformer(transformer).transform(tree.asInstanceOf[global.Tree]) + def typingTransform(tree: Tree)(transformer: (Tree, TypingTransformApi) => Tree): Tree = new HofTypingTransformer(transformer).transform(tree) def typingTransform(tree: Tree, owner: Symbol)(transformer: (Tree, TypingTransformApi) => Tree): Tree = { val trans = new HofTypingTransformer(transformer) - trans.atOwner(owner.asInstanceOf[global.Symbol])(trans.transform(tree.asInstanceOf[global.Tree])) + trans.atOwner(owner)(trans.transform(tree)) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala b/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala index 64994c9f18ba..4c08a5007cba 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala @@ -27,84 +27,84 @@ import scala.reflect.macros.whitebox.Context * - The `async` macro declaration itself, and a forwarder for the macro implementation. * (The latter is temporarily needed to workaround bug SI-6650 in the macro system) * - * The default implementation, [[scala.async.Async]], binds the macro to `scala.concurrent._`. + * The default implementation, [[scala.async]], binds the macro to `scala.concurrent._`. */ abstract class AsyncBase { type FS <: FutureSystem val futureSystem: FS - /** - * A call to `await` must be nested in an enclosing `async` block. - * - * A call to `await` does not block the current thread, rather it is a delimiter - * used by the enclosing `async` macro. Code following the `await` - * call is executed asynchronously, when the argument of `await` has been completed. - * - * @param awaitable the future from which a value is awaited. - * @tparam T the type of that value. - * @return the value. - */ - @compileTimeOnly("`await` must be enclosed in an `async` block") - def await[T](awaitable: futureSystem.Fut[T]): T = ??? - - def asyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { - - // I think there's a bug in subtyping -- shouldn't be necessary to specify the Aliases parent explicitly - // (but somehow we don't rebind the abstract types otherwise) - val ctx = c.asInstanceOf[Aliases with scala.reflect.macros.whitebox.Context {val universe: asyncMacro.u.type}] - val asyncMacroSymbol = ctx.macroApplication.symbol - - object asyncMacro extends AsyncTransform(AsyncBase.this, c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]) { - // The actual transformation happens in terms of the internal compiler data structures. - // We hide the macro context from the classes in the transform package, - // because they're basically a compiler plugin packaged as a macro. - import u._ - - // TODO AM: rework - val asyncNames: AsyncNames[u.type] = rootMirror.RootClass.attachments.get[AsyncNames[u.type]].getOrElse { - val names = new AsyncNames[u.type](u) - rootMirror.RootClass.attachments.update(names) - names - } - - val Async_async = asyncMethod(u)(asyncMacroSymbol) - val Async_await = awaitMethod(u)(asyncMacroSymbol) - - // a few forwarders to context, since they are not easily available through SymbolTable - def typecheck(tree: Tree): Tree = ctx.typecheck(tree) - def abort(pos: Position, msg: String): Nothing = ctx.abort(pos, msg) - def error(pos: Position, msg: String): Unit = ctx.error(pos, msg) - val typingTransformers = new TypingTransformers { - val callsiteTyper: global.analyzer.Typer = ctx.asInstanceOf[scala.reflect.macros.contexts.Context].callsiteTyper.asInstanceOf[global.analyzer.Typer] - } - } - - val enclosingOwner = ctx.internal.enclosingOwner - val code = - try asyncMacro.asyncTransform(body.tree.asInstanceOf[ctx.Tree], execContext.tree.asInstanceOf[ctx.Tree], enclosingOwner, asyncMacroSymbol.pos.makeTransparent)(ctx.weakTypeTag[T].tpe) - catch { case te: ctx.universe.TypeError => ctx.info(enclosingOwner.pos, te.getStackTrace.mkString("\n"), true); ??? } - - AsyncUtils.vprintln(s"async state machine transform expands to:\n $code") - - // Mark range positions for synthetic code as transparent to allow some wiggle room for overlapping ranges - for (t <- code) t.setPos(t.pos.makeTransparent) - c.Expr[futureSystem.Fut[T]](code.asInstanceOf[c.Tree]) - } - - - protected[async] def asyncMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { - import u._ - if (asyncMacroSymbol == null) NoSymbol - else asyncMacroSymbol.owner.typeSignature.member(TermName("async")) - } - - protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { - import u._ - if (asyncMacroSymbol == null) NoSymbol - else asyncMacroSymbol.owner.typeSignature.member(TermName("await")) - } +// /** +// * A call to `await` must be nested in an enclosing `async` block. +// * +// * A call to `await` does not block the current thread, rather it is a delimiter +// * used by the enclosing `async` macro. Code following the `await` +// * call is executed asynchronously, when the argument of `await` has been completed. +// * +// * @param awaitable the future from which a value is awaited. +// * @tparam T the type of that value. +// * @return the value. +// */ +// @compileTimeOnly("`await` must be enclosed in an `async` block") +// def await[T](awaitable: futureSystem.Fut[T]): T = ??? +// +// def asyncImpl[T: c.WeakTypeTag](c: Context) +// (body: c.Expr[T]) +// (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { +// +// // I think there's a bug in subtyping -- shouldn't be necessary to specify the Aliases parent explicitly +// // (but somehow we don't rebind the abstract types otherwise) +// val ctx = c.asInstanceOf[Aliases with scala.reflect.macros.whitebox.Context {val universe: asyncMacro.u.type}] +// val asyncMacroSymbol = ctx.macroApplication.symbol +// +// object asyncMacro extends AsyncTransform(AsyncBase.this, c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]) { +// // The actual transformation happens in terms of the internal compiler data structures. +// // We hide the macro context from the classes in the transform package, +// // because they're basically a compiler plugin packaged as a macro. +// import u._ +// +// // TODO AM: rework +// val asyncNames: AsyncNames[u.type] = rootMirror.RootClass.attachments.get[AsyncNames[u.type]].getOrElse { +// val names = new AsyncNames[u.type](u) +// rootMirror.RootClass.attachments.update(names) +// names +// } +// +// val Async_async = asyncMethod(u)(asyncMacroSymbol) +// val Async_await = awaitMethod(u)(asyncMacroSymbol) +// +// // a few forwarders to context, since they are not easily available through SymbolTable +// def typecheck(tree: Tree): Tree = ctx.typecheck(tree) +// def abort(pos: Position, msg: String): Nothing = ctx.abort(pos, msg) +// def error(pos: Position, msg: String): Unit = ctx.error(pos, msg) +// val typingTransformers = new TypingTransformers { +// val callsiteTyper: global.analyzer.Typer = ctx.asInstanceOf[scala.reflect.macros.contexts.Context].callsiteTyper.asInstanceOf[global.analyzer.Typer] +// } +// } +// +// val enclosingOwner = ctx.internal.enclosingOwner +// val code = +// try asyncMacro.asyncTransform(body.tree.asInstanceOf[ctx.Tree], execContext.tree.asInstanceOf[ctx.Tree], enclosingOwner, asyncMacroSymbol.pos.makeTransparent)(ctx.weakTypeTag[T].tpe) +// catch { case te: ctx.universe.TypeError => ctx.info(enclosingOwner.pos, te.getStackTrace.mkString("\n"), true); ??? } +// +// AsyncUtils.vprintln(s"async state machine transform expands to:\n $code") +// +// // Mark range positions for synthetic code as transparent to allow some wiggle room for overlapping ranges +// for (t <- code) t.setPos(t.pos.makeTransparent) +// c.Expr[futureSystem.Fut[T]](code.asInstanceOf[c.Tree]) +// } +// +// +// protected[async] def asyncMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { +// import u._ +// if (asyncMacroSymbol == null) NoSymbol +// else asyncMacroSymbol.owner.typeSignature.member(TermName("async")) +// } +// +// protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { +// import u._ +// if (asyncMacroSymbol == null) NoSymbol +// else asyncMacroSymbol.owner.typeSignature.member(TermName("await")) +// } protected[async] def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] = u.reify { () } diff --git a/src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala b/src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala deleted file mode 100644 index 3a7b594f5681..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/user/AsyncId.scala +++ /dev/null @@ -1,133 +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 -package user - -import scala.language.experimental.macros -import scala.reflect.api -import scala.reflect.internal.SymbolTable -import scala.reflect.macros.whitebox.Context - -object AsyncId extends AsyncBase { - lazy val futureSystem = IdentityFutureSystem - type FS = IdentityFutureSystem.type - - def async[T](body: => T) = macro asyncIdImpl[T] - - def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[futureSystem.Fut[T]] = asyncImpl[T](c)(body)(c.literalUnit) -} - -object AsyncTestLV extends AsyncBase { - lazy val futureSystem = IdentityFutureSystem - type FS = IdentityFutureSystem.type - - def async[T](body: T) = macro asyncIdImpl[T] - - def asyncIdImpl[T: c.WeakTypeTag](c: Context)(body: c.Expr[T]): c.Expr[futureSystem.Fut[T]] = asyncImpl[T](c)(body)(c.literalUnit) - - var log: List[(String, Any)] = Nil - def assertNulledOut(a: Any): Unit = assert(log.exists(_._2 == a), AsyncTestLV.log) - def assertNotNulledOut(a: Any): Unit = assert(!log.exists(_._2 == a), AsyncTestLV.log) - def clear(): Unit = log = Nil - - def apply(name: String, v: Any): Unit = - log ::= (name -> v) - - protected[async] override def nullOut(u: api.Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] = - u.reify { AsyncTestLV(name.splice, v.splice) } -} - -/** - * A trivial implementation of [[FutureSystem]] that performs computations - * on the current thread. Useful for testing. - */ -class Box[A] { - var a: A = _ -} -object IdentityFutureSystem extends FutureSystem { - type Prom[A] = Box[A] - - type Fut[A] = A - type ExecContext = Unit - type Tryy[A] = scala.util.Try[A] - - def mkOps(u: SymbolTable, isPastErasure: Boolean = false): Ops[u.type] = new IdentityOps[u.type](u, isPastErasure) - class IdentityOps[Universe <: SymbolTable](u0: Universe, isPastErasure: Boolean) extends Ops[Universe](u0, isPastErasure) { - import u._ - - def promType(tp: Type): Type = phasedAppliedType(weakTypeOf[Box[_]], tp) - def tryType(tp: Type): Type = phasedAppliedType(weakTypeOf[util.Try[_]], tp) - - def createProm[A: WeakTypeTag]: Expr[Prom[A]] = { - val newProm = reify { new Prom[A]() } - if (isPastErasure) - Expr[Prom[A]](newProm.tree match { - // drop type apply - case ap@Apply(sel@Select(nw@New(AppliedTypeTree(newProm, _)), ctor), args) => - treeCopy.Apply(ap, treeCopy.Select(sel, treeCopy.New(nw, newProm), ctor), args) - }) - else newProm - } - - def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = { - val expr = reify { prom.splice.a } - if (isPastErasure) Expr[Fut[A]](Apply(expr.tree, Nil)) - else expr - } - - def future[A: WeakTypeTag](t: Expr[A])(execContext: Expr[ExecContext]) = t - - def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[Tryy[A] => B], - execContext: Expr[ExecContext]): Expr[Unit] = reify { - fun.splice.apply(util.Success(future.splice)) - literalUnitExpr.splice - } - - def completeProm[A](prom: Expr[Prom[A]], value: Expr[Tryy[A]]): Expr[Unit] = { - val valueGet = reify { value.splice.get } - reify { - prom.splice.a = { if (isPastErasure) Expr[A](Apply(valueGet.tree, Nil)) else valueGet }.splice - literalUnitExpr.splice - } - } - - def tryyIsFailure[A](tryy: Expr[Tryy[A]]): Expr[Boolean] = reify { - tryy.splice.isFailure - } - - def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = { - val expr = reify { tryy.splice.get } - if (isPastErasure) Expr[A](Apply(expr.tree, Nil)) - else expr - } - - def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] = { - val expr = reify { scala.util.Success[A](a.splice) } - if (isPastErasure) - Expr[Tryy[A]](expr.tree match { - // drop type apply - case ap@Apply(TypeApply(succ, _), args) => treeCopy.Apply(ap, succ, args) - }) - else expr - } - def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]] = { - val expr = reify { scala.util.Failure[A](a.splice) } - if (isPastErasure) - Expr[Tryy[A]](expr.tree match { - // drop type apply - case ap@Apply(TypeApply(fail, _), args) => treeCopy.Apply(ap, fail, args) - }) - else expr - } - } -} diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index a8293cc88a51..5f4bd9672031 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -25,6 +25,7 @@ import scala.reflect.internal.SymbolTable * [[ScalaConcurrentFutureSystem]] for an example of how * to implement this. */ +// TODO use Tree instead of Expr trait FutureSystem { /** A container to receive the final value of the computation */ type Prom[A] @@ -49,18 +50,19 @@ trait FutureSystem { def promType(tp: Type): Type def tryType(tp: Type): Type + def tryTypeToResult(tp: Type): Type def stateMachineClassParents: List[Type] = Nil - /** Create an empty promise */ - def createProm[A: WeakTypeTag]: Expr[Prom[A]] + /** Create an empty promise -- tree should be suitable for use during typer phase. */ + def createProm(resultType: Type): Tree - /** Extract a future from the given promise. */ - def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]): Expr[Fut[A]] + /** Extract a future from the given promise -- tree should be suitable for use during typer phase. */ + def promiseToFuture(prom: Tree): Tree - /** Construct a future to asynchronously compute the given expression */ - def future[A: WeakTypeTag](a: Expr[A])(execContext: Expr[ExecContext]): Expr[Fut[A]] + /** Construct a future to asynchronously compute the given expression -- tree shape should take isPastErasure into account */ + def future(a: Tree, execContext: Tree): Tree - /** Register an call back to run on completion of the given future */ + /** 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] @@ -68,11 +70,13 @@ trait FutureSystem { /** Return `null` if this future is not yet completed, or `Tryy[A]` with the completed result * otherwise + * + * Only called when isPastErasure */ def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = throw new UnsupportedOperationException("getCompleted not supported by this FutureSystem") - /** Complete a promise with a value */ + /** 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: WeakTypeTag](prom: Expr[Prom[A]], value: Expr[A]): Expr[Unit] = completeProm(prom, tryySuccess(value)) @@ -111,30 +115,25 @@ object ScalaConcurrentFutureSystem extends FutureSystem { class ScalaConcurrentOps[Universe <: SymbolTable](u0: Universe, isPastErasure: Boolean) extends Ops[Universe](u0, isPastErasure) { import u._ - def promType(tp: Type): Type = phasedAppliedType(weakTypeOf[Promise[_]], tp) - def tryType(tp: Type): Type = phasedAppliedType(weakTypeOf[scala.util.Try[_]], tp) + def promType(tp: Type): Type = appliedType(Promise, tp) + def tryType(tp: Type): Type = appliedType(TryClass, tp) + def tryTypeToResult(tp: Type): Type = tp.baseType(TryClass).typeArgs.headOption.getOrElse(NoType) - def createProm[A: WeakTypeTag]: Expr[Prom[A]] = { - val newProm = reify { Promise[A]() } - if (isPastErasure) - Expr[Prom[A]](newProm.tree match { // drop type apply - case ap@Apply(TypeApply(prom, _), args) => treeCopy.Apply(ap, prom, args) - }) - else newProm - } + lazy val future = newTermName("future") + lazy val Future = rootMirror.getRequiredModule("scala.concurrent.Future") + lazy val Promise = rootMirror.requiredClass[scala.concurrent.Promise[_]] + lazy val TryClass = rootMirror.requiredClass[scala.util.Try[_]] - def promiseToFuture[A: WeakTypeTag](prom: Expr[Prom[A]]) = reify { - prom.splice.future - } + // only called to generate a typer-time tree (!isPastErasure) + def createProm(resultType: Type): Tree = + Apply(TypeApply(gen.mkAttributedStableRef(Promise.companionModule), TypeTree(resultType) :: Nil), Nil) - def future[A: WeakTypeTag](a: Expr[A])(execContext: Expr[ExecContext]) = { - val expr = reify { Future(a.splice)(execContext.splice) } - if (isPastErasure) - expr.tree match { - case ap@Apply(Apply(fut, a), execCtx) => Expr[Future[A]](treeCopy.Apply(ap, fut, a ++ execCtx)) - } - else expr - } + // only called to generate a typer-time tree (!isPastErasure) + def promiseToFuture(prom: Tree) = Select(prom, future) + + def future(a: Tree, execContext: Tree): Tree = + if (isPastErasure) Apply(Select(gen.mkAttributedStableRef(Future), nme.apply), List(a, execContext)) + else Apply(Apply(Select(gen.mkAttributedStableRef(Future), nme.apply), List(a)), List(execContext)) def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[scala.util.Try[A] => B], execContext: Expr[ExecContext]): Expr[Unit] = { @@ -149,11 +148,20 @@ object ScalaConcurrentFutureSystem extends FutureSystem { override def continueCompletedFutureOnSameThread: Boolean = true override def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = { - val valueGet = reify { future.splice.value.get } + val valueGet = + if (isPastErasure) { + val futVal = reify { future.splice.value } + val futValGet = reify { + Expr[Option[util.Try[A]]](Apply(futVal.tree, Nil)).splice.get + } + Expr[Tryy[A]](Apply(futValGet.tree, Nil)) + } + else reify { future.splice.value.get } + val isCompleted = reify { future.splice.isCompleted } reify { if ({ if (isPastErasure) Expr[Boolean](Apply(isCompleted.tree, Nil)) else isCompleted }.splice) - { if (isPastErasure) Expr[Tryy[A]](Apply(valueGet.tree, Nil)) else valueGet }.splice else null + valueGet.splice else null } } diff --git a/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala b/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala index aaa35c6f6eed..b6f9ce169d29 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala @@ -19,9 +19,9 @@ object ScalaConcurrentAsync extends AsyncBase { type FS = ScalaConcurrentFutureSystem.type val futureSystem: FS = ScalaConcurrentFutureSystem - override def asyncImpl[T: c.WeakTypeTag](c: Context) - (body: c.Expr[T]) - (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[Future[T]] = { - super.asyncImpl[T](c)(body)(execContext) - } +// override def asyncImpl[T: c.WeakTypeTag](c: Context) +// (body: c.Expr[T]) +// (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[Future[T]] = { +// super.asyncImpl[T](c)(body)(execContext) +// } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index d156a68e34a8..864ba4c55a98 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -327,7 +327,7 @@ trait Implicits { */ def memberWildcardType(name: Name, tp: Type) = { val result = refinedType(List(WildcardType), NoSymbol) - val owner = result.typeSymbol orElse { // after erasure (for async?)... + val owner = result.typeSymbol orElse { // TODO async (when after erasure?)... val clazz = NoSymbol.newRefinementClass(NoPosition) clazz setInfo RefinedType(Nil, newScope, clazz) } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 8c7b5ae12c33..0431fc64791f 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1284,7 +1284,8 @@ abstract class RefChecks extends Transform { reporter.warning(pos, s"${sym.fullLocationString} has changed semantics in version ${sym.migrationVersion.get}:\n${sym.migrationMessage.get}") } // See an explanation of compileTimeOnly in its scaladoc at scala.annotation.compileTimeOnly. - if (sym.isCompileTimeOnly && !currentOwner.ownerChain.exists(x => x.isCompileTimeOnly)) { + // 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.""" @@ -1718,7 +1719,7 @@ abstract class RefChecks extends Transform { } private def checkUnexpandedMacro(t: Tree) = - if (!t.isDef && t.hasSymbolField && t.symbol.isTermMacro) + if (!t.isDef && t.hasSymbolField && t.symbol.isTermMacro && t.symbol != currentRun.runDefinitions.Async_async) // TODO async reporter.error(t.pos, "macro has not been expanded") override def transform(tree: Tree): Tree = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 4469f240357c..03b2377ee29b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2302,6 +2302,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper checkNonCyclic(ddef, tpt1) ddef.tpt.setType(tpt1.tpe) val typedMods = typedModifiers(ddef.mods) + val rhsAtOwner = // introduced for async, but could be universally handy + ddef.getAndRemoveAttachment[ChangeOwnerAttachment] match { + case None => ddef.rhs + case Some(ChangeOwnerAttachment(originalOwner)) => ddef.rhs.changeOwner(originalOwner, ddef.symbol) + } var rhs1 = if (ddef.name == nme.CONSTRUCTOR && !ddef.symbol.hasStaticFlag) { // need this to make it possible to generate static ctors if (!meth.isPrimaryConstructor && @@ -2309,13 +2314,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper meth.owner.isModuleClass || meth.owner.isAnonOrRefinementClass)) InvalidConstructorDefError(ddef) - typed(ddef.rhs) + typed(rhsAtOwner) } else if (meth.isMacro) { // typechecking macro bodies is sort of unconventional // that's why we employ our custom typing scheme orchestrated outside of the typer - transformedOr(ddef.rhs, typedMacroBody(this, ddef)) + transformedOr(rhsAtOwner, typedMacroBody(this, ddef)) } else { - transformedOrTyped(ddef.rhs, EXPRmode, tpt1.tpe) + transformedOrTyped(rhsAtOwner, EXPRmode, tpt1.tpe) } if (meth.isClassConstructor && !isPastTyper && !meth.owner.isSubClass(AnyValClass) && !meth.isJava) { @@ -4575,8 +4580,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // The typing in expr1 says expr is Unit (it has already been coerced if // it is non-Unit) so we have to retype it. Fortunately it won't come up much // unless the warning is legitimate. - if (typed(expr).tpe.typeSymbol != UnitClass) - context.warning(tree.pos, "enclosing method " + name + " has result type Unit: return value discarded") + val typedExpr = typed(expr) + if (!isPastTyper && typedExpr.tpe.typeSymbol != UnitClass) + context.warning(tree.pos, "enclosing method " + name + s" has result type Unit: return value of type ${typedExpr.tpe} discarded") } val res = treeCopy.Return(tree, checkDead(context, expr1)).setSymbol(enclMethod.owner) val tp = pluginsTypedReturn(NothingTpe, this, res, restpt.tpe) diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 01ed0fe954f1..02a7869e4a71 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -44,6 +44,9 @@ class FastTrack[MacrosAndAnalyzer <: Macros with Analyzer](val macros: MacrosAnd private def makeWhitebox(sym: Symbol)(pf: PartialFunction[Applied, MacroContext => Tree]) = sym -> new FastTrackEntry(pf, isBlackbox = false) + private def makeBlackBoxIfExists(sym_pf: (Symbol, PartialFunction[Applied, MacroContext => Tree])) = + sym_pf match { case (sym, _) if !sym.exists => Map.empty case (sym, pf) => Map(makeBlackbox(sym)(pf))} + final class FastTrackEntry(pf: PartialFunction[Applied, MacroContext => Tree], val isBlackbox: Boolean) extends (MacroArgs => Any) { def validate(tree: Tree) = pf isDefinedAt Applied(tree) def apply(margs: MacroArgs): margs.c.Expr[Nothing] = { @@ -66,8 +69,7 @@ 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 } - ) ++ (if (!Async_async.exists) Map.empty else Map( - makeBlackbox(Async_async) { case app => c => c.global.analyzer.suppressMacroExpansion(app.tree) } // we'll deal with async much later - )) + ) ++ makeBlackBoxIfExists(global.async.macroExpansion.fastTrackEntry) } + } diff --git a/src/library/scala/Async.scala b/src/library/scala/Async.scala index e9b18935bb8b..26ea3e68adb5 100644 --- a/src/library/scala/Async.scala +++ b/src/library/scala/Async.scala @@ -50,7 +50,7 @@ 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 ??? + 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. diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 8384b12e96d8..a44626c1e7a7 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -119,4 +119,7 @@ trait StdAttachments { case object KnownDirectSubclassesCalled extends PlainAttachment class QualTypeSymAttachment(val sym: Symbol) + + // When typing a Def with this attachment, change the owner of its RHS from origalOwner to the symbol of the Def + case class ChangeOwnerAttachment(originalOwner: Symbol) } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 07a43e909f09..14ac74e66a12 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -285,6 +285,9 @@ trait StdNames { final val TypeDef: NameType = "TypeDef" final val Quasiquote: NameType = "Quasiquote" + // async + final val stateMachine: NameType = "stateMachine$async" + // quasiquote-specific names final val QUASIQUOTE_FUNCTION: NameType = "$quasiquote$function$" final val QUASIQUOTE_MODS: NameType = "$quasiquote$mods$" @@ -830,6 +833,15 @@ trait StdNames { val xml: NameType = "xml" val zero: NameType = "zero" + // async + val result : NameType = "result$async" + val completed : NameType = "completed$async" + val stateMachine : NameType = "stateMachine$async" + val state : NameType = "state$async" + val execContext : NameType = "execContext$async" + val tr : NameType = "tr$async" + val t : NameType = "throwable$async" + // quasiquote interpolators: val q: NameType = "q" val tq: NameType = "tq" diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 3d7821b253f2..675296af6e5c 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -63,6 +63,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.UseInvokeSpecial this.TypeParamVarargsAttachment this.KnownDirectSubclassesCalled + this.ChangeOwnerAttachment this.noPrint this.typeDebug // inaccessible: this.posAssigner diff --git a/test/async/run/auto_AfterRefchecksIssue.scala b/test/async/run/auto_AfterRefchecksIssue.scala new file mode 100644 index 000000000000..bd8707a86c26 --- /dev/null +++ b/test/async/run/auto_AfterRefchecksIssue.scala @@ -0,0 +1,18 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +trait Factory[T] { + def create: T +} + +sealed trait TimePoint + +class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) { + @autoawait + @async private[TimeLine] val tp: TP = tpInitial.create + @autoawait + @async def timePoint: TP = tp +} + +object Test extends App { test + def test: Unit = () +} diff --git a/test/async/run/auto_ArrayIndexOutOfBoundIssue.scala b/test/async/run/auto_ArrayIndexOutOfBoundIssue.scala new file mode 100644 index 000000000000..ccffb315f949 --- /dev/null +++ b/test/async/run/auto_ArrayIndexOutOfBoundIssue.scala @@ -0,0 +1,31 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +sealed trait Result + +case object A extends Result + +case object B extends Result + +case object C extends Result + +object Test extends App { test + protected def doStuff(res: Result) = { + class C { + @autoawait def needCheck = false + + @async def m = { + if (needCheck) "NO" + else { + res match { + case A => 1 + case _ => 2 + } + } + } + } + } + + + @async + def test() = doStuff(B) +} diff --git a/test/async/run/auto_Combo.check b/test/async/run/auto_Combo.check new file mode 100644 index 000000000000..54c32ce3574e --- /dev/null +++ b/test/async/run/auto_Combo.check @@ -0,0 +1 @@ +Test.test diff --git a/test/async/run/auto_Combo.scala b/test/async/run/auto_Combo.scala new file mode 100644 index 000000000000..eb94c796074a --- /dev/null +++ b/test/async/run/auto_Combo.scala @@ -0,0 +1,26 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { assert(test == "case 3: blerg3") + @async + def test: Any = { + object Extractor1 { + @autoawait def unapply(a: String) = Some((a + 1, a + 2)) + } + object Extractor2 { + @autoawait def unapply(a: String) = Some(a + 3) + } + @autoawait def id(a: String) = a + + println("Test.test") + val r1 = Predef.identity("blerg") match { + case x if " ".isEmpty => "case 2: " + x + case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y + x match { + case Extractor1(Extractor2(x), y: String) => + case _ => + } + case Extractor2(x) => "case 3: " + x + } + r1 + } +} diff --git a/test/async/run/auto_Extractor.scala b/test/async/run/auto_Extractor.scala new file mode 100644 index 000000000000..b53140b78f98 --- /dev/null +++ b/test/async/run/auto_Extractor.scala @@ -0,0 +1,13 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { assert(test) + @async + def test: Boolean = { + object Extractor { + @autoawait def unapply(a: String) = Some((a, a)) + } + "" match { + case Extractor(a, b) if "".isEmpty => a == b + } + } +} diff --git a/test/async/run/auto_GenericTypeBoundaryIssue.check b/test/async/run/auto_GenericTypeBoundaryIssue.check new file mode 100644 index 000000000000..c89f5ee868a1 --- /dev/null +++ b/test/async/run/auto_GenericTypeBoundaryIssue.check @@ -0,0 +1 @@ +process Bound diff --git a/test/async/run/auto_GenericTypeBoundaryIssue.scala b/test/async/run/auto_GenericTypeBoundaryIssue.scala new file mode 100644 index 000000000000..a9823f04beb5 --- /dev/null +++ b/test/async/run/auto_GenericTypeBoundaryIssue.scala @@ -0,0 +1,32 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +trait InstrumentOfValue + +trait Security[T <: InstrumentOfValue] extends InstrumentOfValue + +class Bound extends Security[Bound] + +class Futures extends Security[Futures] + +object TestGenericTypeBoundIssue { + @autoawait + @async def processBound(bound: Bound): Unit = { + println("process Bound") + } + @autoawait + @async def processFutures(futures: Futures): Unit = { + println("process Futures") + } + @autoawait + @async def doStuff(sec: Security[_]): Unit = { + sec match { + case bound: Bound => processBound(bound) + case futures: Futures => processFutures(futures) + case _ => throw new Exception("Unknown Security type: " + sec) + } + } +} + +object Test extends App { test + @async def test: Unit = TestGenericTypeBoundIssue.doStuff(new Bound) +} diff --git a/test/async/run/auto_Guard.scala b/test/async/run/auto_Guard.scala new file mode 100644 index 000000000000..cda03bad2174 --- /dev/null +++ b/test/async/run/auto_Guard.scala @@ -0,0 +1,13 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { assert(test == "okay") + @async + def test: Any = { + @autoawait def id[A](a: A) = a + + "" match { + case _ if id(false) => ???; + case _ => "okay" + } + } +} diff --git a/test/async/run/auto_MatchEndIssue.scala b/test/async/run/auto_MatchEndIssue.scala new file mode 100644 index 000000000000..061eebbe7d8b --- /dev/null +++ b/test/async/run/auto_MatchEndIssue.scala @@ -0,0 +1,22 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +sealed trait Subject + +final class Principal(val name: String) extends Subject + +object Principal { + def unapply(p: Principal): Option[String] = Some(p.name) +} + +object Test extends App { test + @autoawait + @async + def containsPrincipal(search: String, value: Subject): Boolean = value match { + case Principal(name) if name == search => true + case Principal(name) => containsPrincipal(search, value) + case other => false + } + + @async + def test = containsPrincipal("test", new Principal("test")) +} diff --git a/test/async/run/auto_NegativeArraySizeException.scala b/test/async/run/auto_NegativeArraySizeException.scala new file mode 100644 index 000000000000..2332b23c7b10 --- /dev/null +++ b/test/async/run/auto_NegativeArraySizeException.scala @@ -0,0 +1,12 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { test + def foo(foo: Any, bar: Any) = () + @autoawait def getValue = 4.2 + @async def func(f: Any) = { + foo(f match { case _ if "".isEmpty => 2 }, getValue); + } + + @async + def test() = func(4) +} diff --git a/test/async/run/auto_NegativeArraySizeExceptionFine1.scala b/test/async/run/auto_NegativeArraySizeExceptionFine1.scala new file mode 100644 index 000000000000..a2814327840b --- /dev/null +++ b/test/async/run/auto_NegativeArraySizeExceptionFine1.scala @@ -0,0 +1,20 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +case class FixedFoo(foo: Int) + +class Foobar(val foo: Int, val bar: Double) { + @autoawait + @async def getValue = 4.2 + @autoawait + @async def func(f: Any) = { + new Foobar(foo = f match { + case FixedFoo(x) => x + case _ => 2 + }, + bar = getValue) + } +} + +object Test extends App { test + @async def test() = new Foobar(0, 0).func(4) +} diff --git a/test/async/run/auto_NestedMatchExtractor.scala b/test/async/run/auto_NestedMatchExtractor.scala new file mode 100644 index 000000000000..2dfebad175d6 --- /dev/null +++ b/test/async/run/auto_NestedMatchExtractor.scala @@ -0,0 +1,16 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { assert(test) + @async + def test: Boolean = { + object Extractor { + @autoawait def unapply(a: String) = Some((a, a)) + } + "" match { + case _ if "".isEmpty => + "" match { + case Extractor(a, b) => a == b + } + } + } +} diff --git a/test/async/run/auto_ReturnTupleIssue.scala b/test/async/run/auto_ReturnTupleIssue.scala new file mode 100644 index 000000000000..547da1866940 --- /dev/null +++ b/test/async/run/auto_ReturnTupleIssue.scala @@ -0,0 +1,19 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +class TestReturnExprIssue(str: String) { + @autoawait + @async def getTestValue = Some(42) + @autoawait + @async def doStuff: Int = { + val opt: Option[Int] = getTestValue // here we have an async method invoke + opt match { + case Some(li) => li // use the result somehow + case None => + } + 42 // type mismatch; found : AnyVal required: Int + } +} + +object Test extends App { test + @async def test: Unit = new TestReturnExprIssue("").doStuff +} diff --git a/test/async/run/auto_patternAlternative.scala b/test/async/run/auto_patternAlternative.scala new file mode 100644 index 000000000000..1d72f31295f9 --- /dev/null +++ b/test/async/run/auto_patternAlternative.scala @@ -0,0 +1,15 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { test + @async + def test: Any = { + @autoawait def one = 1 + + @async def test = { + Option(true) match { + case null | None => false + case Some(v) => one; v + } + } + } +} diff --git a/test/async/run/auto_patternAlternativeBothAnnotations.scala b/test/async/run/auto_patternAlternativeBothAnnotations.scala new file mode 100644 index 000000000000..0f8e897fba53 --- /dev/null +++ b/test/async/run/auto_patternAlternativeBothAnnotations.scala @@ -0,0 +1,15 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { test + @async + def test: Any = { + @autoawait def func1() = "hello" + + @async def func(a: Option[Boolean]) = a match { + case null | None => func1 + " world" + case _ => "okay" + } + + def test: Any = func(None) + } +} diff --git a/test/async/run/auto_polymorphicMethod.check b/test/async/run/auto_polymorphicMethod.check new file mode 100644 index 000000000000..a5d2b3b91803 --- /dev/null +++ b/test/async/run/auto_polymorphicMethod.check @@ -0,0 +1,3 @@ +auto_polymorphicMethod.scala:13: warning: unreachable code + case _ if false => ???; + ^ diff --git a/test/async/run/auto_polymorphicMethod.scala b/test/async/run/auto_polymorphicMethod.scala new file mode 100644 index 000000000000..e2bd16ec4291 --- /dev/null +++ b/test/async/run/auto_polymorphicMethod.scala @@ -0,0 +1,19 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { assert(test.toString == "(C,C)") + + class C { + override def toString = "C" + } + + @autoawait def foo[A <: C](a: A): A = a + @async + def test1[CC <: C](c: CC): (CC, CC) = { + val x: (CC, CC) = 0 match { + case _ if false => ???; + case _ => (foo(c), foo(c)) + } + x + } + def test(): (C, C) = test1(new C) +} diff --git a/test/async/run/auto_shadowing.check b/test/async/run/auto_shadowing.check new file mode 100644 index 000000000000..a78a6c78443c --- /dev/null +++ b/test/async/run/auto_shadowing.check @@ -0,0 +1,3 @@ +auto_shadowing.scala:16: warning: a pure expression does nothing in statement position + case _ => foo; () + ^ diff --git a/test/async/run/auto_shadowing.scala b/test/async/run/auto_shadowing.scala new file mode 100644 index 000000000000..e1f3e980957b --- /dev/null +++ b/test/async/run/auto_shadowing.scala @@ -0,0 +1,22 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { test + + trait Foo + + trait Bar extends Foo + + @autoawait def boundary = "" + @async + def test: Unit = { + (new Bar {}: Any) match { + case foo: Bar => + boundary + 0 match { + case _ => foo; () + } + () + } + () + } +} diff --git a/test/async/run/auto_shadowing0.scala b/test/async/run/auto_shadowing0.scala new file mode 100644 index 000000000000..efe346891bf5 --- /dev/null +++ b/test/async/run/auto_shadowing0.scala @@ -0,0 +1,23 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { test + + trait Foo + + trait Bar + + def test: Any = test(new C) + @autoawait def asyncBoundary: String = "" + @async + def test(foo: Foo): Foo = foo match { + case foo: Bar => + val foo2: Foo with Bar = new Foo with Bar {} + asyncBoundary + null match { + case _ => foo2 + } + case other => foo + } + + class C extends Foo with Bar +} diff --git a/test/async/run/auto_shadowing2.scala b/test/async/run/auto_shadowing2.scala new file mode 100644 index 000000000000..39afb3a4fdc1 --- /dev/null +++ b/test/async/run/auto_shadowing2.scala @@ -0,0 +1,27 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { test + + trait Base; + + trait Foo[T <: Base] { + @autoawait def func: Option[Foo[T]] = None + } + + class Sub extends Base + + trait Bar extends Foo[Sub] + + def test: Any = test(new Bar {}) + @async + def test[T <: Base](foo: Foo[T]): Foo[T] = foo match { + case foo: Bar => + val res = foo.func + res match { + case _ => + } + foo + case other => foo + } + test(new Bar {}) +} diff --git a/test/async/run/auto_shadowingRefinedTypes.scala b/test/async/run/auto_shadowingRefinedTypes.scala new file mode 100644 index 000000000000..11fadca7066d --- /dev/null +++ b/test/async/run/auto_shadowingRefinedTypes.scala @@ -0,0 +1,27 @@ +import scala.tools.nsc.transform.async.user.{async, autoawait} + +trait Base + +class Sub extends Base + +trait Foo[T <: Base] { + @autoawait def func: Option[Foo[T]] = None +} + +trait Bar extends Foo[Sub] + +object Test extends App { assert(test) + @async def func[T <: Base](foo: Foo[T]): Foo[T] = foo match { // the whole pattern match will be wrapped with async{ } + case foo: Bar => + val res = foo.func // will be rewritten into: await(foo.func) + res match { + case Some(v) => v // this will report type mismtach + case other => foo + } + case other => foo + } + def test: Boolean = { + val b = new Bar {}; + func(b) == b + } +} diff --git a/test/async/run/auto_test0.scala b/test/async/run/auto_test0.scala new file mode 100644 index 000000000000..edba257e7d80 --- /dev/null +++ b/test/async/run/auto_test0.scala @@ -0,0 +1,23 @@ +/* + * 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. + */ + + +import scala.tools.nsc.transform.async.user.{async, autoawait} + +object Test extends App { assert(test == "foobar") + @async + def test: String = { + @autoawait def id(a: String) = a + + id("foo") + id("bar") + } +} diff --git a/test/async/run/concurrent_fetch.check b/test/async/run/concurrent_fetch.check new file mode 100644 index 000000000000..991fe4e4827b --- /dev/null +++ b/test/async/run/concurrent_fetch.check @@ -0,0 +1,3 @@ +fetching +fetching +63 diff --git a/test/async/run/concurrent_fetch.scala b/test/async/run/concurrent_fetch.scala new file mode 100644 index 000000000000..f62d54d5781d --- /dev/null +++ b/test/async/run/concurrent_fetch.scala @@ -0,0 +1,19 @@ +import scala.concurrent.{Await, Future, duration} +import scala.concurrent.ExecutionContext.Implicits.global +import scala.async.{async, await} + +object Test extends App { + def fetchURL(url: String): Future[String] = Future {println("fetching"); s"fetched $url"} + + val sumLengths: Future[Int] = { + println(s"pre fsm") + async { + println(s"huh") + val body1 = fetchURL("http://scala-lang.org") + val body2 = fetchURL("http://docs.scala-lang.org") + await(body1).length + await(body2).length + } + } + + Await.result(async {println(await(sumLengths)) }, duration.Duration.Inf) +} diff --git a/test/async/run/late-run.log b/test/async/run/late-run.log new file mode 100644 index 000000000000..43d7223eea56 --- /dev/null +++ b/test/async/run/late-run.log @@ -0,0 +1,31 @@ +java.lang.ClassNotFoundException: Test + at java.net.URLClassLoader.findClass(URLClassLoader.java:382) + at java.lang.ClassLoader.loadClass(ClassLoader.java:424) + at java.lang.ClassLoader.loadClass(ClassLoader.java:357) + at scala.tools.partest.nest.Runner.$anonfun$execTestInProcess$2(Runner.scala:267) + at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62) + at scala.Console$.withOut(Console.scala:167) + at scala.tools.partest.nest.StreamCapture$.$anonfun$capturingOutErr$2(StreamCapture.scala:32) + at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62) + at scala.Console$.withErr(Console.scala:196) + at scala.tools.partest.nest.StreamCapture$.$anonfun$capturingOutErr$1(StreamCapture.scala:31) + at scala.tools.partest.nest.Runner.$anonfun$execTestInProcess$1(Runner.scala:266) + at scala.tools.partest.nest.StreamCapture$.withExtraProperties(StreamCapture.scala:56) + at scala.tools.partest.nest.Runner.run$2(Runner.scala:262) + at scala.tools.partest.nest.Runner.$anonfun$execTestInProcess$3(Runner.scala:289) + at scala.tools.partest.nest.Runner.execTestInProcess(Runner.scala:289) + at scala.tools.partest.nest.Runner.exec$1(Runner.scala:691) + at scala.tools.partest.nest.Runner.$anonfun$runRunTest$1(Runner.scala:692) + at scala.tools.partest.nest.Runner.$anonfun$runTestCommon$1(Runner.scala:591) + at scala.tools.partest.nest.Runner.runInContext(Runner.scala:478) + at scala.tools.partest.nest.Runner.runTestCommon(Runner.scala:591) + at scala.tools.partest.nest.Runner.runRunTest(Runner.scala:692) + at scala.tools.partest.nest.Runner.run(Runner.scala:681) + at scala.tools.partest.nest.SuiteRunner.liftedTree1$1(Runner.scala:815) + at scala.tools.partest.nest.SuiteRunner.runTest(Runner.scala:815) + at scala.tools.partest.nest.SuiteRunner.$anonfun$runTestsForFiles$2(Runner.scala:833) + at scala.tools.partest.package$$anon$2.call(package.scala:134) + at java.util.concurrent.FutureTask.run(FutureTask.java:266) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) + at java.lang.Thread.run(Thread.java:748) diff --git a/test/async/run/toughtype.scala-run.log b/test/async/run/toughtype.scala-run.log new file mode 100644 index 000000000000..f80e2e844733 --- /dev/null +++ b/test/async/run/toughtype.scala-run.log @@ -0,0 +1,7 @@ +toughtype.scala.scala:205: error: type mismatch; + found : scala.concurrent.Future[Unit] + required: scala.concurrent.Future[W] + Note: implicit method Something to do with List is not applicable here because it comes after the application point and it lacks an explicit result type + }(SomeExecutionContext) + ^ +one error found diff --git a/test/async/run/uncheckedBounds-run.log b/test/async/run/uncheckedBounds-run.log new file mode 100644 index 000000000000..9159d1925e62 --- /dev/null +++ b/test/async/run/uncheckedBounds-run.log @@ -0,0 +1,16 @@ +uncheckedBounds.scala:18: error: object TreeInterrogation is not a member of package async +import scala.async.TreeInterrogation + ^ +uncheckedBounds.scala:22: error: not found: value eval + eval( s""" + ^ +uncheckedBounds.scala:30: error: not found: value compileOptions + """, compileOptions = s"-cp ${toolboxClasspath} ") + ^ +uncheckedBounds.scala:34: error: not found: value eval + eval( s""" + ^ +uncheckedBounds.scala:44: error: not found: value compileOptions + """, compileOptions = s"-cp ${toolboxClasspath} ") + ^ +5 errors found diff --git a/test/files/neg/unit-returns-value.check b/test/files/neg/unit-returns-value.check index 447961976846..a35e38f0a965 100644 --- a/test/files/neg/unit-returns-value.check +++ b/test/files/neg/unit-returns-value.check @@ -1,4 +1,4 @@ -unit-returns-value.scala:4: warning: enclosing method f has result type Unit: return value discarded +unit-returns-value.scala:4: warning: enclosing method f has result type Unit: return value of type Int(5) discarded if (b) return 5 ^ unit-returns-value.scala:4: warning: a pure expression does nothing in statement position diff --git a/test/files/run/t5380.check b/test/files/run/t5380.check index ce453731b5aa..31536e72cf1b 100644 --- a/test/files/run/t5380.check +++ b/test/files/run/t5380.check @@ -1,4 +1,4 @@ -t5380.scala:3: warning: enclosing method main has result type Unit: return value discarded +t5380.scala:3: warning: enclosing method main has result type Unit: return value of type Int discarded val f = () => return try { 1 } catch { case _: Throwable => 0 } ^ t5380.scala:3: warning: a pure expression does nothing in statement position From a7bbec5ef6ed7a2f46e18da517619bc93d52c09e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 6 Feb 2020 17:33:49 +1000 Subject: [PATCH 05/94] Improvements to async phase Passes the correct owner symbol to `useFields` to fix a bug with local modules. Refactors to store state about the current async block in a var which allows us to mix in all the implementation once into AsyncPhase. Remove some testing FutureSystems. We'll refactor the tests to use ScalaConcurrentFutureSystem. Allow a custom front end to supply an alternative FutureSystem attached to the block defining the state machine wrapper class. Demonstrate this with a test case for an annotation driven front end that targets an alternative Future implementation. Remove some now-unused customization points from FutureSystem. For example, we don't need to abstract over the choice of base class for the state machine as a custom front end now creates the state machine tree directly. FutureSystem now deals directly in Trees, rather than in `c.Expr`. I've kept a light weight option (a type alias `type Expr[T] = Tree`) so we can have a little compiler support for getting the types right and documentation for `FutureSystem` implementors. Refactor the ANF transform (and others) to directly be a TypingTransformer. Remove the need to subclass Function0 in the state machine by kicking things off with `Future.unit.onComplete(stateMachine)`. This reduces bloat in the generated code due to Function0 specialization. I've also elminated the macro application in the early transform in favour of wrapping the argument in `Block( :: Nil, q"()"}`. This ran into some "pure expression has no effect" warnings which I've suppressed with a new tree annotation. Most of the `auto_` test cases are converted to use the standard scala.concurrent version of async. Now that the transform is always happening post-erasure, there is no need to keep those tests under the annotation driven async test (which used to be the only way to get post-typer expansion). A few test can only be expressed with the annotation driven version of async, namely those that have an async boundary in an extractor. These have been moved into the `AnnotationDrivenAsyncTest`. --- project/PartestUtil.scala | 2 +- .../nsc/transform/TypingTransformers.scala | 1 + .../nsc/transform/async/AnfTransform.scala | 674 +++++++++--------- .../nsc/transform/async/AsyncAnalysis.scala | 2 +- .../nsc/transform/async/AsyncPhase.scala | 102 ++- .../nsc/transform/async/AsyncTransform.scala | 228 +++--- .../nsc/transform/async/ExprBuilder.scala | 98 +-- .../tools/nsc/transform/async/Lifter.scala | 4 +- .../nsc/transform/async/LiveVariables.scala | 2 +- .../nsc/transform/async/TransformUtils.scala | 269 ++----- .../nsc/transform/async/user/AsyncBase.scala | 111 --- .../transform/async/user/FutureSystem.scala | 157 ++-- .../async/user/ScalaConcurrentAsync.scala | 27 - .../scala/tools/reflect/FastTrack.scala | 2 +- .../reflect/internal/StdAttachments.scala | 2 + .../scala/reflect/internal/StdNames.scala | 17 +- .../scala/reflect/internal/TreeInfo.scala | 1 + .../reflect/runtime/JavaUniverseForce.scala | 1 + test/async/.gitignore | 2 + test/async/run/auto_AfterRefchecksIssue.scala | 18 - test/async/run/auto_Combo.check | 1 - test/async/run/auto_Combo.scala | 26 - test/async/run/auto_Extractor.scala | 13 - .../run/auto_GenericTypeBoundaryIssue.check | 1 - .../run/auto_GenericTypeBoundaryIssue.scala | 32 - test/async/run/auto_Guard.scala | 13 - test/async/run/auto_MatchEndIssue.scala | 22 - .../run/auto_NegativeArraySizeException.scala | 12 - ...auto_NegativeArraySizeExceptionFine1.scala | 20 - .../async/run/auto_NestedMatchExtractor.scala | 16 - test/async/run/auto_ReturnTupleIssue.scala | 19 - test/async/run/auto_patternAlternative.scala | 15 - ...to_patternAlternativeBothAnnotations.scala | 15 - test/async/run/auto_polymorphicMethod.check | 3 - test/async/run/auto_polymorphicMethod.scala | 19 - test/async/run/auto_shadowing.check | 3 - test/async/run/auto_shadowing.scala | 22 - test/async/run/auto_shadowing0.scala | 23 - test/async/run/auto_shadowing2.scala | 27 - .../run/auto_shadowingRefinedTypes.scala | 27 - .../run/concurrent_AfterRefchecksIssue.scala | 16 + ...oncurrent_ArrayIndexOutOfBoundIssue.scala} | 15 +- .../concurrent_GenericTypeBoundaryIssue.check | 0 .../concurrent_GenericTypeBoundaryIssue.scala | 33 + test/async/run/concurrent_MatchEndIssue.scala | 24 + ...oncurrent_NegativeArraySizeException.scala | 14 + ...rent_NegativeArraySizeExceptionFine1.scala | 21 + .../run/concurrent_ReturnTupleIssue.scala | 20 + test/async/run/concurrent_fetch.scala | 7 +- .../run/concurrent_patternAlternative.scala | 18 + ...nt_patternAlternativeBothAnnotations.scala | 14 + .../run/concurrent_polymorphicMethod.check | 3 + .../run/concurrent_polymorphicMethod.scala | 22 + test/async/run/concurrent_shadowing.check | 3 + test/async/run/concurrent_shadowing.scala | 24 + test/async/run/concurrent_shadowing0.scala | 27 + test/async/run/concurrent_shadowing2.scala | 31 + .../concurrent_shadowingRefinedTypes.scala | 32 + ...uto_test0.scala => concurrent_test0.scala} | 15 +- test/async/run/futures.check | 13 + .../nsc/async/AnnotationDrivenAsync.scala | 402 +++++++++++ .../scala/tools/nsc/async/CustomFuture.scala | 39 + 62 files changed, 1501 insertions(+), 1341 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala delete mode 100644 src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala create mode 100644 test/async/.gitignore delete mode 100644 test/async/run/auto_AfterRefchecksIssue.scala delete mode 100644 test/async/run/auto_Combo.check delete mode 100644 test/async/run/auto_Combo.scala delete mode 100644 test/async/run/auto_Extractor.scala delete mode 100644 test/async/run/auto_GenericTypeBoundaryIssue.check delete mode 100644 test/async/run/auto_GenericTypeBoundaryIssue.scala delete mode 100644 test/async/run/auto_Guard.scala delete mode 100644 test/async/run/auto_MatchEndIssue.scala delete mode 100644 test/async/run/auto_NegativeArraySizeException.scala delete mode 100644 test/async/run/auto_NegativeArraySizeExceptionFine1.scala delete mode 100644 test/async/run/auto_NestedMatchExtractor.scala delete mode 100644 test/async/run/auto_ReturnTupleIssue.scala delete mode 100644 test/async/run/auto_patternAlternative.scala delete mode 100644 test/async/run/auto_patternAlternativeBothAnnotations.scala delete mode 100644 test/async/run/auto_polymorphicMethod.check delete mode 100644 test/async/run/auto_polymorphicMethod.scala delete mode 100644 test/async/run/auto_shadowing.check delete mode 100644 test/async/run/auto_shadowing.scala delete mode 100644 test/async/run/auto_shadowing0.scala delete mode 100644 test/async/run/auto_shadowing2.scala delete mode 100644 test/async/run/auto_shadowingRefinedTypes.scala create mode 100644 test/async/run/concurrent_AfterRefchecksIssue.scala rename test/async/run/{auto_ArrayIndexOutOfBoundIssue.scala => concurrent_ArrayIndexOutOfBoundIssue.scala} (52%) create mode 100644 test/async/run/concurrent_GenericTypeBoundaryIssue.check create mode 100644 test/async/run/concurrent_GenericTypeBoundaryIssue.scala create mode 100644 test/async/run/concurrent_MatchEndIssue.scala create mode 100644 test/async/run/concurrent_NegativeArraySizeException.scala create mode 100644 test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala create mode 100644 test/async/run/concurrent_ReturnTupleIssue.scala create mode 100644 test/async/run/concurrent_patternAlternative.scala create mode 100644 test/async/run/concurrent_patternAlternativeBothAnnotations.scala create mode 100644 test/async/run/concurrent_polymorphicMethod.check create mode 100644 test/async/run/concurrent_polymorphicMethod.scala create mode 100644 test/async/run/concurrent_shadowing.check create mode 100644 test/async/run/concurrent_shadowing.scala create mode 100644 test/async/run/concurrent_shadowing0.scala create mode 100644 test/async/run/concurrent_shadowing2.scala create mode 100644 test/async/run/concurrent_shadowingRefinedTypes.scala rename test/async/run/{auto_test0.scala => concurrent_test0.scala} (56%) create mode 100644 test/async/run/futures.check create mode 100644 test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala create mode 100644 test/junit/scala/tools/nsc/async/CustomFuture.scala diff --git a/project/PartestUtil.scala b/project/PartestUtil.scala index 423c08eeb1e3..4d88b8ae6f5b 100644 --- a/project/PartestUtil.scala +++ b/project/PartestUtil.scala @@ -87,7 +87,7 @@ object PartestUtil { token(grepOption <~ Space) ~> token(globOrPattern, tokenCompletion) } - val SrcPath = ((token(srcPathOption) <~ Space) ~ token(StringBasic.examples(Set("files", "scaladoc")))) map { + val SrcPath = ((token(srcPathOption) <~ Space) ~ token(StringBasic.examples(Set("files", "scaladoc", "async")))) map { case opt ~ path => srcPath = path opt + " " + path diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala index d1722c2d0325..d328697f1bd8 100644 --- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala +++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala @@ -51,6 +51,7 @@ trait TypingTransformers { super.transform(tree) } } + def transformAtOwner(owner: Symbol, tree: Tree): Tree = atOwner(tree, owner) { transform(tree) } } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 41d39ed29b3f..313407fc4df2 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -15,18 +15,22 @@ package scala.tools.nsc.transform.async import scala.reflect.internal.Flags private[async] trait AnfTransform extends TransformUtils { - import typingTransformers.typingTransform - import u._ - - def anfTransform(tree: Tree, owner: Symbol): Block = { + import global._ + final def anfTransform(tree: Tree, owner: Symbol): Block = { + val trans = new AnfTransform(owner) // Must prepend the () for issue #31. val block = typecheck(atPos(tree.pos)(Block(List(literalUnit), tree))).setType(tree.tpe) + val tree1 = adjustTypeOfTranslatedPatternMatches(block, owner) + trans.transformAtOwner(owner, tree1).asInstanceOf[Block] + } + + class AnfTransform(owner: Symbol) extends TypingTransformer(currentTransformState.unit) { sealed abstract class AnfMode + case object Anf extends AnfMode - case object Linearizing extends AnfMode - val tree1 = adjustTypeOfTranslatedPatternMatches(block, owner) + case object Linearizing extends AnfMode var mode: AnfMode = Anf @@ -37,13 +41,16 @@ private[async] trait AnfTransform extends TransformUtils { def apply[T](args: Any)(t: => T): T = { def prefix = mode.toString.toLowerCase + indent += 1 + def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(127) + try { - if(AsyncUtils.trace) + if (AsyncUtils.trace) AsyncUtils.trace(s"$indentString$prefix(${oneLine(args)})") val result = t - if(AsyncUtils.trace) + if (AsyncUtils.trace) AsyncUtils.trace(s"$indentString= ${oneLine(result)}") result } finally { @@ -52,368 +59,383 @@ private[async] trait AnfTransform extends TransformUtils { } } - // creates a subclass of TypingTransformer - typingTransform(tree1, owner)((tree, api) => { - import api.{atOwner, currentOwner} + def typed(tree: Tree) = localTyper.typed(tree) + + def typedAt(exprPos: Position, tree: Tree) = localTyper.typed(atPos(exprPos)(tree)) + + def typedAssign(lhs: Tree, varSym: Symbol) = + typedAt(lhs.pos, Assign(Ident(varSym), lhs)) + + object linearize { + def transformToList(tree: Tree): List[Tree] = { + mode = Linearizing; + blockToList(transform(tree)) + } - // localTyper - def typed(tree: Tree) = api.typecheck(tree) - def typedAt(exprPos: Position, tree: Tree) = api.typecheck(atPos(exprPos)(tree)) + def transformToBlock(tree: Tree): Block = listToBlock(transformToList(tree)) - def typedAssign(lhs: Tree, varSym: Symbol) = - typedAt(lhs.pos, Assign(Ident(varSym), mkAttributedCastPreservingAnnotations(lhs, varSym.info))) + def _transformToList(tree: Tree): List[Tree] = trace(tree) { + val stats :+ expr = _anf.transformToList(tree) - object linearize { - def transformToList(tree: Tree): List[Tree] = { - mode = Linearizing; blockToList(api.recur(tree)) + def statsExprUnit = { + stats :+ expr :+ typedAt(expr.pos, literalUnit) } - def transformToBlock(tree: Tree): Block = listToBlock(transformToList(tree)) + def statsExprThrow = + stats :+ expr :+ typedAt(expr.pos, Throw(Apply(Select(New(gen.mkAttributedRef(IllegalStateExceptionClass)), nme.CONSTRUCTOR), Nil))) + + expr match { + case Apply(fun, args) if isAwait(fun) => + val awaitResType = transformType(expr.tpe) + val valDef = defineVal(name.await(), expr, tree.pos)(awaitResType) + val ref = gen.mkAttributedStableRef(valDef.symbol).setType(awaitResType) + // https://github.com/scala/async/issues/74 + // Use a cast to hide from "pure expression does nothing" error + // TODO avoid creating a ValDef for the result of this await to avoid this tree shape altogether. + // This will require some deeper changes to the later parts of the macro which currently assume regular + // tree structure around `await` calls. + val refNoPureExpr = + if (!isPastErasure && typeEqualsUnit(ref.tpe)) typedAt(tree.pos, gen.mkCast(ref, ref.tpe)) + else atPos(tree.pos)(ref) + + stats :+ valDef :+ refNoPureExpr + + case If(cond, thenp, elsep) => + // If we run the ANF transform post patmat, deal with trees like `(if (cond) jump1(){String} else jump2(){String}){String}` + // as though it was typed with `Unit`. + def isPatMatGeneratedJump(t: Tree): Boolean = t match { + case Block(_, expr) => isPatMatGeneratedJump(expr) + case If(_, thenp, elsep) => isPatMatGeneratedJump(thenp) && isPatMatGeneratedJump(elsep) + case _: Apply if isLabel(t.symbol) => true + case _ => false + } - def _transformToList(tree: Tree): List[Tree] = trace(tree) { - val stats :+ expr = _anf.transformToList(tree) - def statsExprUnit = { - stats :+ expr :+ typedAt(expr.pos, literalUnit) - } - def statsExprThrow = - stats :+ expr :+ typedAt(expr.pos, Throw(Apply(Select(New(gen.mkAttributedRef(IllegalStateExceptionClass)), nme.CONSTRUCTOR), Nil))) - expr match { - case Apply(fun, args) if isAwait(fun) => - val awaitResType = transformType(expr.tpe) - val valDef = defineVal(name.await(), expr, tree.pos)(awaitResType) - val ref = gen.mkAttributedStableRef(valDef.symbol).setType(awaitResType) - // https://github.com/scala/async/issues/74 - // Use a cast to hide from "pure expression does nothing" error - // TODO avoid creating a ValDef for the result of this await to avoid this tree shape altogether. - // This will require some deeper changes to the later parts of the macro which currently assume regular - // tree structure around `await` calls. - val refNoPureExpr = - if (!isPastErasure && typeEqualsUnit(ref.tpe)) typedAt(tree.pos, gen.mkCast(ref, ref.tpe)) - else atPos(tree.pos)(ref) - - stats :+ valDef :+ refNoPureExpr - - case If(cond, thenp, elsep) => - // If we run the ANF transform post patmat, deal with trees like `(if (cond) jump1(){String} else jump2(){String}){String}` - // as though it was typed with `Unit`. - def isPatMatGeneratedJump(t: Tree): Boolean = t match { - case Block(_, expr) => isPatMatGeneratedJump(expr) - case If(_, thenp, elsep) => isPatMatGeneratedJump(thenp) && isPatMatGeneratedJump(elsep) - case _: Apply if isLabel(t.symbol) => true - case _ => false - } - if (isPatMatGeneratedJump(expr)) - assignUnitType(expr) - - // if type of if-else is Unit don't introduce assignment, - // but add Unit value to bring it into form expected by async transform - if (typeEqualsUnit(expr.tpe)) { - statsExprUnit - } else if (typeEqualsNothing(expr.tpe)) { - statsExprThrow - } else { - val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos) - - def branchWithAssign(t: Tree): Tree = { - t match { - case MatchEnd(ld) => - deriveLabelDef(ld, branchWithAssign) - case blk @ Block(thenStats, thenExpr) => - assignUnitType(treeCopy.Block(blk, thenStats, branchWithAssign(thenExpr))) - case _ => - typedAssign(t, varDef.symbol) - } + if (isPatMatGeneratedJump(expr)) + assignUnitType(expr) + + // if type of if-else is Unit don't introduce assignment, + // but add Unit value to bring it into form expected by async transform + if (typeEqualsUnit(expr.tpe)) { + statsExprUnit + } else if (typeEqualsNothing(expr.tpe)) { + statsExprThrow + } else { + val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos) + + def branchWithAssign(t: Tree): Tree = { + t match { + case MatchEnd(ld) => + deriveLabelDef(ld, branchWithAssign) + case blk@Block(thenStats, thenExpr) => + assignUnitType(treeCopy.Block(blk, thenStats, branchWithAssign(thenExpr))) + case _ => + typedAssign(t, varDef.symbol) } - val ifWithAssign = assignUnitType(treeCopy.If(tree, cond, branchWithAssign(thenp), branchWithAssign(elsep))) - stats :+ varDef :+ ifWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) } - case ld @ LabelDef(name, params, rhs) => - if (isUnitType(ld.symbol.info.resultType)) statsExprUnit - else stats :+ expr - - case Match(scrut, cases) => - // if type of match is Unit don't introduce assignment, - // but add Unit value to bring it into form expected by async transform - if (typeEqualsUnit(expr.tpe)) { - statsExprUnit - } else if (typeEqualsNothing(expr.tpe)) { - statsExprThrow - } else { - val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos) - val casesWithAssign = cases map { - case cd@CaseDef(pat, guard, body) => - def bodyWithAssign(t: Tree): Tree = { - t match { - case MatchEnd(ld) => deriveLabelDef(ld, bodyWithAssign) - case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, bodyWithAssign(caseExpr))) - case _ => typedAssign(t, varDef.symbol) - } + + val ifWithAssign = assignUnitType(treeCopy.If(tree, cond, branchWithAssign(thenp), branchWithAssign(elsep))) + stats :+ varDef :+ ifWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) + } + case ld@LabelDef(name, params, rhs) => + if (isUnitType(ld.symbol.info.resultType)) statsExprUnit + else stats :+ expr + + case Match(scrut, cases) => + // if type of match is Unit don't introduce assignment, + // but add Unit value to bring it into form expected by async transform + if (typeEqualsUnit(expr.tpe)) { + statsExprUnit + } else if (typeEqualsNothing(expr.tpe)) { + statsExprThrow + } else { + val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos) + val casesWithAssign = cases map { + case cd@CaseDef(pat, guard, body) => + def bodyWithAssign(t: Tree): Tree = { + t match { + case MatchEnd(ld) => deriveLabelDef(ld, bodyWithAssign) + case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, bodyWithAssign(caseExpr))) + case _ => typedAssign(t, varDef.symbol) } - assignUnitType(treeCopy.CaseDef(cd, pat, guard, bodyWithAssign(body))) - } - val matchWithAssign = assignUnitType(treeCopy.Match(tree, scrut, casesWithAssign)) - require(matchWithAssign.tpe != null, matchWithAssign) - stats :+ varDef :+ matchWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) + } + + assignUnitType(treeCopy.CaseDef(cd, pat, guard, bodyWithAssign(body))) } - case _ => - stats :+ expr - } + val matchWithAssign = assignUnitType(treeCopy.Match(tree, scrut, casesWithAssign)) + require(matchWithAssign.tpe != null, matchWithAssign) + stats :+ varDef :+ matchWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) + } + case _ => + stats :+ expr } + } - def defineVar(name: TermName, tp: Type, pos: Position): ValDef = { - val sym = currentOwner.newTermSymbol(name, pos, Flags.MUTABLE | Flags.SYNTHETIC).setInfo(uncheckedBounds(transformType(tp))) - ValDef(sym, mkZero(uncheckedBounds(tp), pos)).setType(NoType).setPos(pos) - } + def defineVar(name: TermName, tp: Type, pos: Position): ValDef = { + val sym = currentOwner.newTermSymbol(name, pos, Flags.MUTABLE | Flags.SYNTHETIC).setInfo(transformType(tp)) + ValDef(sym, mkZero(tp, pos)).setType(NoType).setPos(pos) } + } - def defineVal(name: TermName, lhs: Tree, pos: Position)(tp: Type = uncheckedBounds(transformType(lhs.tpe))): ValDef = { - val sym = currentOwner.newTermSymbol(name, pos, Flags.SYNTHETIC).setInfo(tp) + def defineVal(name: TermName, lhs: Tree, pos: Position)(tp: Type = transformType(lhs.tpe)): ValDef = { + val sym = currentOwner.newTermSymbol(name, pos, Flags.SYNTHETIC).setInfo(tp) - val lhsOwned = lhs.changeOwner((currentOwner, sym)) - val rhs = - if (isPastErasure && isUnitType(tp)) Block(lhsOwned :: Nil, literalUnit) - else lhsOwned - ValDef(sym, rhs).setType(NoType).setPos(pos) + val lhsOwned = lhs.changeOwner((currentOwner, sym)) + val rhs = + if (isPastErasure && isUnitType(tp)) Block(lhsOwned :: Nil, literalUnit) + else lhsOwned + ValDef(sym, rhs).setType(NoType).setPos(pos) - } + } - object _anf { - import treeInfo.Applied + object _anf { + import treeInfo.Applied - def transformToList(tree: Tree): List[Tree] = { - mode = Anf; blockToList(api.recur(tree)) - } + def transformToList(tree: Tree): List[Tree] = { + mode = Anf; + blockToList(transform(tree)) + } - def _transformToList(tree: Tree): List[Tree] = trace(tree) { - if (!containsAwait(tree)) { - tree match { - case Block(stats, expr) => - // avoids nested block in `while(await(false)) ...`. - // TODO I think `containsAwait` really should return true if the code contains a label jump to an enclosing - // while/doWhile and there is an await *anywhere* inside that construct. - stats :+ expr - case _ => List(tree) - } - } else tree match { - case Select(qual, sel) => - val stats :+ expr = linearize.transformToList(qual) - stats :+ treeCopy.Select(tree, expr, sel) - - case Throw(expr) => - val stats :+ expr1 = linearize.transformToList(expr) - stats :+ treeCopy.Throw(tree, expr1) - - case Typed(expr, tpt) => - val stats :+ expr1 = linearize.transformToList(expr) - stats :+ treeCopy.Typed(tree, expr1, tpt) - - case Applied(fun, targs, argss) if argss.nonEmpty => - // we can assume that no await call appears in a by-name argument position, - // this has already been checked. - val funStats :+ simpleFun = linearize.transformToList(fun) - val (argStatss, argExprss): (List[List[List[Tree]]], List[List[Tree]]) = - mapArgumentss[List[Tree]](fun, argss) { - case Arg(expr, byName, _) if byName /*|| isPure(expr) TODO */ => (Nil, expr) - case Arg(expr, _, argName) => - linearize.transformToList(expr) match { - case stats :+ expr1 => - val valDef = defineVal(name.freshen(argName), expr1, expr1.pos)() - require(valDef.tpe != null, valDef) - val stats1 = stats :+ valDef - (stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol)))) - } - } + def _transformToList(tree: Tree): List[Tree] = trace(tree) { + if (!containsAwait(tree)) { + tree match { + case Block(stats, expr) => + // avoids nested block in `while(await(false)) ...`. + // TODO I think `containsAwait` really should return true if the code contains a label jump to an enclosing + // while/doWhile and there is an await *anywhere* inside that construct. + stats :+ expr + case _ => List(tree) + } + } else tree match { + case Select(qual, sel) => + val stats :+ expr = linearize.transformToList(qual) + stats :+ treeCopy.Select(tree, expr, sel) + + case Throw(expr) => + val stats :+ expr1 = linearize.transformToList(expr) + stats :+ treeCopy.Throw(tree, expr1) + + case Typed(expr, tpt) => + val stats :+ expr1 = linearize.transformToList(expr) + stats :+ treeCopy.Typed(tree, expr1, tpt) + + case Applied(fun, targs, argss) if argss.nonEmpty => + // we can assume that no await call appears in a by-name argument position, + // this has already been checked. + val funStats :+ simpleFun = linearize.transformToList(fun) + val (argStatss, argExprss): (List[List[List[Tree]]], List[List[Tree]]) = + mapArgumentss[List[Tree]](fun, argss) { + case Arg(expr, byName, _) if byName /*|| isPure(expr) TODO */ => (Nil, expr) + case Arg(expr, _, argName) => + linearize.transformToList(expr) match { + case stats :+ expr1 => + val valDef = defineVal(name.freshen(argName), expr1, expr1.pos)() + require(valDef.tpe != null, valDef) + val stats1 = stats :+ valDef + (stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol)))) + } + } - def copyApplied(tree: Tree, depth: Int): Tree = { - tree match { - case TypeApply(_, targs) => treeCopy.TypeApply(tree, simpleFun, targs) - case _ if depth == 0 => simpleFun - case Apply(fun, args) => - val newTypedArgs = map2(args.map(_.pos), argExprss(depth - 1))((pos, arg) => typedAt(pos, arg)) - treeCopy.Apply(tree, copyApplied(fun, depth - 1), newTypedArgs) - } + def copyApplied(tree: Tree, depth: Int): Tree = { + tree match { + case TypeApply(_, targs) => treeCopy.TypeApply(tree, simpleFun, targs) + case _ if depth == 0 => simpleFun + case Apply(fun, args) => + val newTypedArgs = map2(args.map(_.pos), argExprss(depth - 1))((pos, arg) => typedAt(pos, arg)) + treeCopy.Apply(tree, copyApplied(fun, depth - 1), newTypedArgs) } + } - val typedNewApply = copyApplied(tree, argss.length) + val typedNewApply = copyApplied(tree, argss.length) - funStats ++ argStatss.flatten.flatten :+ typedNewApply + funStats ++ argStatss.flatten.flatten :+ typedNewApply - case Block(stats, expr) => - val stats1 = stats.flatMap(linearize.transformToList).filterNot(isLiteralUnit) - val exprs1 = linearize.transformToList(expr) - val trees = stats1 ::: exprs1 - def groupsEndingWith[T](ts: List[T])(f: T => Boolean): List[List[T]] = if (ts.isEmpty) Nil else { - ts.indexWhere(f) match { - case -1 => List(ts) - case i => - val (ts1, ts2) = ts.splitAt(i + 1) - ts1 :: groupsEndingWith(ts2)(f) - } - } - val matchGroups = groupsEndingWith(trees){ case MatchEnd(_) => true; case _ => false } - val trees1 = matchGroups.flatMap(eliminateMatchEndLabelParameter) - val result = trees1 flatMap { - case Block(stats, expr) => stats :+ expr - case t => t :: Nil - } - result - - case ValDef(mods, name, tpt, rhs) => - if (containsAwait(rhs)) { - val stats :+ expr = atOwner(currentOwner.owner)(linearize.transformToList(rhs)) - stats.foreach(_.changeOwner((currentOwner, currentOwner.owner))) - stats :+ treeCopy.ValDef(tree, mods, name, tpt, expr) - } else List(tree) - - case Assign(lhs, rhs) => - val stats :+ expr = linearize.transformToList(rhs) - stats :+ treeCopy.Assign(tree, lhs, expr) - - case If(cond, thenp, elsep) => - val condStats :+ condExpr = linearize.transformToList(cond) - val thenBlock = linearize.transformToBlock(thenp) - val elseBlock = linearize.transformToBlock(elsep) - condStats :+ treeCopy.If(tree, condExpr, thenBlock, elseBlock) - - case Match(scrut, cases) => - val scrutStats :+ scrutExpr = linearize.transformToList(scrut) - val caseDefs = cases map { - case CaseDef(pat, guard, body) => - // extract local variables for all names bound in `pat`, and rewrite `body` - // to refer to these. - // TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`. - val block = linearize.transformToBlock(body) - val (valDefs, mappings) = (pat collect { - case b@Bind(bindName, _) => - val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)() - vd.symbol.updateAttachment(SyntheticBindVal) - (vd, (b.symbol, vd.symbol)) - }).unzip - val (from, to) = mappings.unzip - val b@Block(stats1, expr1) = block.substituteSymbols(from, to).asInstanceOf[Block] - val newBlock = treeCopy.Block(b, valDefs ++ stats1, expr1) - treeCopy.CaseDef(tree, pat, guard, newBlock) + case Block(stats, expr) => + val stats1 = stats.flatMap(linearize.transformToList).filterNot(isLiteralUnit) + val exprs1 = linearize.transformToList(expr) + val trees = stats1 ::: exprs1 + + def groupsEndingWith[T](ts: List[T])(f: T => Boolean): List[List[T]] = if (ts.isEmpty) Nil else { + ts.indexWhere(f) match { + case -1 => List(ts) + case i => + val (ts1, ts2) = ts.splitAt(i + 1) + ts1 :: groupsEndingWith(ts2)(f) } - scrutStats :+ treeCopy.Match(tree, scrutExpr, caseDefs) + } - case LabelDef(name, params, rhs) => - if (!isPastErasure && isUnitType(tree.symbol.info)) // erasure has already inserted unit - List(treeCopy.LabelDef(tree, name, params, typed(Block(linearize.transformToList(rhs), literalUnit))).setSymbol(tree.symbol)) - else - List(treeCopy.LabelDef(tree, name, params, typed(listToBlock(linearize.transformToList(rhs)))).setSymbol(tree.symbol)) + val matchGroups = groupsEndingWith(trees) { case MatchEnd(_) => true; case _ => false } + val trees1 = matchGroups.flatMap(group => eliminateMatchEndLabelParameter(tree.pos, group)) + val result = trees1 flatMap { + case Block(stats, expr) => stats :+ expr + case t => t :: Nil + } + result + + case ValDef(mods, name, tpt, rhs) => + if (containsAwait(rhs)) { + val stats :+ expr = atOwner(currentOwner.owner)(linearize.transformToList(rhs)) + stats.foreach(_.changeOwner((currentOwner, currentOwner.owner))) + stats :+ treeCopy.ValDef(tree, mods, name, tpt, expr) + } else List(tree) + + case Assign(lhs, rhs) => + val stats :+ expr = linearize.transformToList(rhs) + stats :+ treeCopy.Assign(tree, lhs, expr) + + case If(cond, thenp, elsep) => + val condStats :+ condExpr = linearize.transformToList(cond) + val thenBlock = linearize.transformToBlock(thenp) + val elseBlock = linearize.transformToBlock(elsep) + condStats :+ treeCopy.If(tree, condExpr, thenBlock, elseBlock) + + case Match(scrut, cases) => + val scrutStats :+ scrutExpr = linearize.transformToList(scrut) + val caseDefs = cases map { + case CaseDef(pat, guard, body) => + // extract local variables for all names bound in `pat`, and rewrite `body` + // to refer to these. + // TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`. + val block = linearize.transformToBlock(body) + val (valDefs, mappings) = (pat collect { + case b@Bind(bindName, _) => + val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)() + vd.symbol.updateAttachment(SyntheticBindVal) + (vd, (b.symbol, vd.symbol)) + }).unzip + val (from, to) = mappings.unzip + val b@Block(stats1, expr1) = block.substituteSymbols(from, to).asInstanceOf[Block] + val newBlock = treeCopy.Block(b, valDefs ++ stats1, expr1) + treeCopy.CaseDef(tree, pat, guard, newBlock) + } + scrutStats :+ treeCopy.Match(tree, scrutExpr, caseDefs) - case TypeApply(fun, targs) => - val funStats :+ simpleFun = linearize.transformToList(fun) - funStats :+ treeCopy.TypeApply(tree, simpleFun, targs) + case LabelDef(name, params, rhs) => + if (!isPastErasure && isUnitType(tree.symbol.info)) // erasure has already inserted unit + List(treeCopy.LabelDef(tree, name, params, typed(Block(linearize.transformToList(rhs), literalUnit))).setSymbol(tree.symbol)) + else + List(treeCopy.LabelDef(tree, name, params, typed(listToBlock(linearize.transformToList(rhs)))).setSymbol(tree.symbol)) - case _ => - List(tree) - } + case TypeApply(fun, targs) => + val funStats :+ simpleFun = linearize.transformToList(fun) + funStats :+ treeCopy.TypeApply(tree, simpleFun, targs) + + case _ => + List(tree) } } + } - // Replace the label parameters on `matchEnd` with use of a `matchRes` temporary variable - // - // CaseDefs are translated to labels without parameters. A terminal label, `matchEnd`, accepts - // a parameter which is the result of the match (this is regular, so even Unit-typed matches have this). - // - // For our purposes, it is easier to: - // - extract a `matchRes` variable - // - rewrite the terminal label def to take no parameters, and instead read this temp variable - // - change jumps to the terminal label to an assignment and a no-arg label application - def eliminateMatchEndLabelParameter(statsExpr: List[Tree]): List[Tree] = { - val caseDefToMatchResult = collection.mutable.Map[Symbol, Symbol]() - - val matchResults = collection.mutable.Buffer[Tree]() - def modifyLabelDef(ld: LabelDef): (Tree, Tree) = { - val param = ld.params.head - - def unitLabelDef = { - setUnitMethodInfo(ld.symbol) - assignUnitType(treeCopy.LabelDef(ld, ld.name, Nil, typed(literalUnit))) - } - - if (isUnitType(ld.params.head.tpe)) { - // Unit typed match: eliminate the label def parameter, but don't create a matchres temp variable to - // store the result for cleaner generated code. - caseDefToMatchResult(ld.symbol) = NoSymbol - (unitLabelDef, substituteTrees(ld.rhs, param.symbol :: Nil, typed(literalUnit) :: Nil)) - } else { - // Otherwise, create the matchres var. We'll callers of the label def below. - // Remember: we're iterating through the statement sequence in reverse, so we'll get - // to the LabelDef and mutate `matchResults` before we'll get to its callers. - val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos) - matchResults += matchResult - caseDefToMatchResult(ld.symbol) = matchResult.symbol - (unitLabelDef, ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)) - } + // Replace the label parameters on `matchEnd` with use of a `matchRes` temporary variable + // + // CaseDefs are translated to labels without parameters. A terminal label, `matchEnd`, accepts + // a parameter which is the result of the match (this is regular, so even Unit-typed matches have this). + // + // For our purposes, it is easier to: + // - extract a `matchRes` variable + // - rewrite the terminal label def to take no parameters, and instead read this temp variable + // - change jumps to the terminal label to an assignment and a no-arg label application + def eliminateMatchEndLabelParameter(pos: Position, statsExpr: List[Tree]): List[Tree] = { + val caseDefToMatchResult = collection.mutable.Map[Symbol, Symbol]() + + val matchResults = collection.mutable.Buffer[Tree]() + + def modifyLabelDef(ld: LabelDef): (Tree, Tree) = { + val param = ld.params.head + + def unitLabelDef = { + setUnitMethodInfo(ld.symbol) + assignUnitType(treeCopy.LabelDef(ld, ld.name, Nil, typed(literalUnit))) } - val statsExpr0 = statsExpr.reverse.flatMap { - case ld @ LabelDef(_, param :: Nil, _) => - val (ld1, after) = modifyLabelDef(ld) - List(after, ld1) - case a @ ValDef(mods, name, tpt, ld @ LabelDef(_, param :: Nil, _)) => - val (ld1, after) = modifyLabelDef(ld) - List(treeCopy.ValDef(a, mods, name, tpt, after), ld1) - case t => - if (caseDefToMatchResult.isEmpty) t :: Nil - else typingTransform(t)((tree, api) => { - def typedPos(pos: Position)(t: Tree): Tree = api.typecheck(atPos(pos)(t)) - def transform(tree: Tree) = api.recur(tree) - def superTransform = api.default(tree) - tree match { - case Apply(fun, arg :: Nil) if isLabel(fun.symbol) && caseDefToMatchResult.contains(fun.symbol) => - val temp = caseDefToMatchResult(fun.symbol) - if (temp == NoSymbol) - typedPos(tree.pos)(Block(transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil))) - else - // setType needed for LateExpansion.shadowingRefinedType test case. There seems to be an inconsistency - // in the trees after pattern matcher. - // TODO miminize the problem in patmat and fix in scalac. - typedPos(tree.pos)(Block(Assign(Ident(temp), transform(arg.setType(transformType(fun.tpe.paramLists.head.head.info)))) :: Nil, treeCopy.Apply(tree, fun, Nil))) - case Block(stats, expr: Apply) if isLabel(expr.symbol) => - superTransform match { - case Block(stats0, Block(stats1, expr1)) => - // flatten the block returned by `case Apply` above into the enclosing block for - // cleaner generated code. - treeCopy.Block(tree, stats0 ::: stats1, expr1) - case t => t - } - case _ => - superTransform - } - }) :: Nil - } - matchResults.toList match { - case _ if caseDefToMatchResult.isEmpty => - statsExpr // return the original trees if nothing changed - case Nil => - statsExpr0.reverse :+ literalUnit // must have been a unit-typed match, no matchRes variable to definne or refer to - case r1 :: Nil => - // { var matchRes = _; ....; matchRes } - (r1 +: statsExpr0.reverse) :+ atPos(tree.pos)(gen.mkAttributedIdent(r1.symbol)) - case _ => error(tree.pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr + if (isUnitType(ld.params.head.tpe)) { + // Unit typed match: eliminate the label def parameter, but don't create a matchres temp variable to + // store the result for cleaner generated code. + caseDefToMatchResult(ld.symbol) = NoSymbol + (unitLabelDef, substituteTrees(ld.rhs, param.symbol :: Nil, typed(literalUnit) :: Nil)) + } else { + // Otherwise, create the matchres var. We'll callers of the label def below. + // Remember: we're iterating through the statement sequence in reverse, so we'll get + // to the LabelDef and mutate `matchResults` before we'll get to its callers. + val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos) + matchResults += matchResult + caseDefToMatchResult(ld.symbol) = matchResult.symbol + (unitLabelDef, ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)) } } - def anfLinearize(tree: Tree): Block = { - val trees: List[Tree] = mode match { - case Anf => _anf._transformToList(tree) - case Linearizing => linearize._transformToList(tree) - } - listToBlock(trees) + val statsExpr0 = statsExpr.reverse.flatMap { + case ld@LabelDef(_, param :: Nil, _) => + val (ld1, after) = modifyLabelDef(ld) + List(after, ld1) + case a@ValDef(mods, name, tpt, ld@LabelDef(_, param :: Nil, _)) => + val (ld1, after) = modifyLabelDef(ld) + List(treeCopy.ValDef(a, mods, name, tpt, after), ld1) + case t => + if (caseDefToMatchResult.isEmpty) t :: Nil + else { + val matchResultTransformer = new MatchResultTransformer(caseDefToMatchResult) + val tree1 = matchResultTransformer.transformAtOwner(owner, t) + tree1 :: Nil + } + } + matchResults.toList match { + case _ if caseDefToMatchResult.isEmpty => + statsExpr // return the original trees if nothing changed + case Nil => + statsExpr0.reverse :+ literalUnit // must have been a unit-typed match, no matchRes variable to definne or refer to + case r1 :: Nil => + // { var matchRes = _; ....; matchRes } + (r1 +: statsExpr0.reverse) :+ atPos(pos)(gen.mkAttributedIdent(r1.symbol)) + case _ => error(pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr } + } + def anfLinearize(tree: Tree): Block = { + val trees: List[Tree] = mode match { + case Anf => _anf._transformToList(tree) + case Linearizing => linearize._transformToList(tree) + } + listToBlock(trees) + } + + override def transform(tree: Tree): Tree = { tree match { case _: ValDef | _: DefDef | _: Function | _: ClassDef | _: TypeDef => atOwner(tree.symbol)(anfLinearize(tree)) - case _: ModuleDef => + case _: ModuleDef => atOwner(tree.symbol.asModule.moduleClass orElse tree.symbol)(anfLinearize(tree)) - case _ => + case _ => anfLinearize(tree) } - }).asInstanceOf[Block] + } + } + + final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) extends TypingTransformer(currentTransformState.unit) { + override def transform(tree: Tree): Tree = { + def typedPos(pos: Position)(t: Tree): Tree = localTyper.typed(atPos(pos)(t)) + + tree match { + case Apply(fun, arg :: Nil) if isLabel(fun.symbol) && caseDefToMatchResult.contains(fun.symbol) => + val temp = caseDefToMatchResult(fun.symbol) + if (temp == NoSymbol) + typedPos(tree.pos)(Block(transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil))) + else + // setType needed for LateExpansion.shadowingRefinedType test case. There seems to be an inconsistency + // in the trees after pattern matcher. + // TODO miminize the problem in patmat and fix in scalac. + typedPos(tree.pos)(Block(Assign(Ident(temp), transform(arg.setType(transformType(fun.tpe.paramLists.head.head.info)))) :: Nil, treeCopy.Apply(tree, fun, Nil))) + case Block(stats, expr: Apply) if isLabel(expr.symbol) => + super.transform(tree) match { + case Block(stats0, Block(stats1, expr1)) => + // flatten the block returned by `case Apply` above into the enclosing block for + // cleaner generated code. + treeCopy.Block(tree, stats0 ::: stats1, expr1) + case t => t + } + case _ => + super.transform(tree) + } + } } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index 7fc33e979039..fba59849f575 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -16,7 +16,7 @@ import scala.collection.mutable.ListBuffer import scala.reflect.internal.Flags trait AsyncAnalysis extends TransformUtils { - import u._ + import global._ /** * Analyze the contents of an `async` block in order to: diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 74bf2c4fbbc5..0989816b73ef 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -12,13 +12,17 @@ package scala.tools.nsc.transform.async +import scala.collection.mutable.ListBuffer import scala.tools.nsc.Mode +import scala.tools.nsc.transform.async.user.{FutureSystem, ScalaConcurrentFutureSystem} import scala.tools.nsc.transform.{Transform, TypingTransformers} -abstract class AsyncPhase extends Transform with TypingTransformers { - private val asyncNames_ = new AsyncNames[global.type](global) +abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTransform{ + self => import global._ + val asyncNames = new AsyncNames[global.type](global) + val phaseName: String = "async" override def enabled = true // TODO: should be off by default, enabled by flag // { @@ -27,23 +31,20 @@ abstract class AsyncPhase extends Transform with TypingTransformers { // case rd => rd // }).Async_async.exists // } - - + final class FutureSystemAttachment(val system: FutureSystem) extends PlainAttachment object macroExpansion extends AsyncEarlyExpansion { - val u: global.type = global - val asyncBase = user.ScalaConcurrentAsync - import treeInfo.Applied - - def fastTrackEntry: (Symbol, PartialFunction[Applied, scala.reflect.macros.contexts.Context { val universe: u.type } => Tree]) = - (currentRun.runDefinitions.Async_async, { - // def async[T](body: T)(implicit execContext: ExecutionContext): Future[T] = macro ??? - case app@Applied(_, resultTp :: Nil, List(asyncBody :: Nil, execContext :: Nil)) => - c => c.global.async.macroExpansion(c.global.analyzer.suppressMacroExpansion(app.tree), execContext, resultTp.tpe, c.internal.enclosingOwner) - }) - + 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(_, resultTp :: Nil, List(asyncBody :: Nil, execContext :: Nil)) => + c => c.global.async.macroExpansion.apply(c.callsiteTyper, asyncBody, execContext, resultTp.tpe, c.internal.enclosingOwner) + }) + def newTransformer(unit: CompilationUnit): Transformer = new AsyncTransformer(unit) // TODO: support more than the original late expansion tests @@ -51,31 +52,11 @@ abstract class AsyncPhase extends Transform with TypingTransformers { // replace the ExecutionContext implicit arg with an AsyncContext implicit that also specifies the type of the Future/Awaitable/Node/...? class AsyncTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - abstract class AsyncTransformBase(asyncBase: user.AsyncBase) extends AsyncTransform(asyncBase) { - val u: global.type = global - import u._ - - val asyncNames: AsyncNames[u.type] = asyncNames_ - - def typecheck(tree: Tree): Tree = localTyper.typed(tree) - def abort(pos: Position, msg: String): Nothing = {localTyper.context.reporter.error(pos, msg); ???} - def error(pos: Position, msg: String): Unit = localTyper.context.reporter.error(pos, msg) - - val typingTransformers = - new TypingTransformers { - def callsiteTyper = localTyper - } - } - - object asyncTransformerConcurrent extends AsyncTransformBase(user.ScalaConcurrentAsync) { - val Async_async = currentRun.runDefinitions.Async_async - val Async_await = currentRun.runDefinitions.Async_await - } - lazy val uncurrier = new uncurry.UnCurryTransformer(unit) + lazy val eraser = new erasure.ErasureTransformer(unit) override def transform(tree: Tree): Tree = - tree match { + super.transform(tree) match { // { // class stateMachine$async extends scala.runtime.AbstractFunction1 with Function0$mcV$sp { // def apply(tr$async: scala.util.Try): Unit = { // symbol of this def is `applySym`, symbol of its param named "tr$async" is `trParamSym` @@ -84,30 +65,33 @@ abstract class AsyncPhase extends Transform with TypingTransformers { // val stateMachine = ... // ... // } - case Block((cd@ClassDef(mods, tpnme.stateMachine, _, impl@Template(parents, self, stats))) :: (vd@ValDef(_, nme.stateMachine, tpt, _)) :: rest, expr) if tpt.tpe.typeSymbol == cd.symbol => - import asyncTransformerConcurrent._ - - stats.collectFirst { - case dd@DefDef(mods, name@nme.apply, tparams, vparamss@List(tr :: Nil), tpt, Block( Apply(asyncMacro, List(asyncBody, execContext)) :: Nil, Literal(Constant(())))) => - asyncTransform(asyncBody, dd.symbol, tr.symbol, execContext) match { - case Some((newRhs, liftables)) => - Right(treeCopy.DefDef(dd, mods, name, tparams, vparamss, tpt, newRhs).setType(null) /* need to retype */ :: liftables) - case None => - val thunkFun = localTyper.typedPos(asyncBody.pos)(Function(Nil, asyncBody)).asInstanceOf[Function] - thunkFun.body.changeOwner(dd.symbol, thunkFun.symbol) - thunkFun.setType(definitions.functionType(Nil, exitingTyper { futureSystemOps.tryTypeToResult(tr.symbol.info) })) // ugh (uncurry normally runs before erasure and wants a full function type) - Left(futureSystemOps.future(uncurrier.transformFunction(thunkFun), execContext)) + case tree if tree.hasAttachment[FutureSystemAttachment] => + val saved = currentTransformState + val futureSystem = tree.getAndRemoveAttachment[FutureSystemAttachment].get.system + val newState = new AsyncTransformState[global.type](global, futureSystem, unit, this) + currentTransformState = newState + try tree match { + case blk@Block((temp@ValDef(_, nme.execContextTemp, _, execContext)) :: (cd@ClassDef(mods, tpnme.stateMachine, _, impl@Template(parents, self, stats))) :: (vd@ValDef(_, nme.stateMachine, tpt, _)) :: rest, expr) if tpt.tpe.typeSymbol == cd.symbol => + val ((dd: DefDef) :: Nil, others) = stats.partition { + case dd@DefDef(mods, nme.apply, _, List(tr :: Nil), _, _) => !dd.symbol.isBridge + case _ => false + } + val asyncBody = (dd.rhs: @unchecked) match { + case Block(stats, Literal(Constant(()))) => Block(stats.init, stats.last) } - } match { - case Some(Left(simple)) => localTyper.typed(simple) - case Some(Right(newStats@(newApply :: liftables))) => - val newTempl = treeCopy.Template(impl, parents, self, stats.filterNot(_.symbol == newApply.symbol) ::: newStats) - treeCopy.Block(tree, localTyper.typedClassDef(treeCopy.ClassDef(cd, mods, tpnme.stateMachine, Nil, newTempl)) :: vd :: rest, expr) + val (newRhs, liftables) = asyncTransform(asyncBody, dd.symbol, dd.vparamss.head.head.symbol) + + val newApply = deriveDefDef(dd)(_ => newRhs).setType(null) /* need to retype */ + val newStats = new ListBuffer[Tree] + newStats ++= others + newStats += newApply + newStats ++= liftables + val newTempl = treeCopy.Template(impl, parents, self, newStats.toList) + treeCopy.Block(tree, temp :: localTyper.typedClassDef(treeCopy.ClassDef(cd, mods, tpnme.stateMachine, Nil, newTempl)) :: vd :: rest, expr) + } finally { + currentTransformState = saved } -// case ap@Apply(fun, rhs :: execContext :: Nil) if fun.symbol == asyncTransformerConcurrent.Async_async => transformAsyncStd(rhs, execContext) - case tree => - super.transform(tree) + case tree => tree } - } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 549b3a53fa5f..ce33153381a0 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -12,17 +12,23 @@ package scala.tools.nsc.transform.async -import user.{AsyncBase, FutureSystem} +import user.{FutureSystem, ScalaConcurrentFutureSystem} import scala.reflect.internal.Flags +import scala.tools.nsc.Global +import scala.tools.nsc.transform.TypingTransformers // TODO: check there's no await outside of an async block -abstract class AsyncEarlyExpansion extends AsyncContext { - import u._ +abstract class AsyncEarlyExpansion extends TypingTransformers { + import global._ + import global.async.FutureSystemAttachment // NOTE: this part runs during typer - lazy val futureSystem: FutureSystem = asyncBase.futureSystem - lazy val futureSystemOps: futureSystem.Ops[u.type] = futureSystem.mkOps(u, false) + lazy val futureSystem: FutureSystem = ScalaConcurrentFutureSystem + lazy val futureSystemOps: futureSystem.Ops[global.type] = futureSystem.mkOps(global) + + private lazy val Promise_class = rootMirror.requiredClass[scala.concurrent.Promise[_]] + private def promType(tp: Type): Type = appliedType(Promise_class, tp) /** 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. @@ -37,30 +43,35 @@ abstract class AsyncEarlyExpansion extends AsyncContext { * * Synthesizes: { - class stateMachine$async extends scala.runtime.AbstractFunction1[scala.util.Try[`resultType`],Unit] with () => Unit { + 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 = `execContext`; + private[this] val execContext$async: scala.concurrent.ExecutionContext = execContext0$async; def execContext$async: scala.concurrent.ExecutionContext = stateMachine$async.this.execContext$async; - def apply(): Unit = stateMachine$async.this.apply(null); def apply(tr$async: scala.util.Try[`resultType`]): Unit = { - scala.async.async[`resultType`](`asyncBody`)(`execContext`); + `asyncBody` () } }; val stateMachine$async: stateMachine$async = new stateMachine$async(); - Future.apply[stateMachine$async](stateMachine$async)(stateMachine$async.execContext$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(asyncBody: Tree, execContext: Tree, resultType: Type, originalOwner: Symbol) = { + def apply(callsiteTyper: analyzer.Typer, asyncBody: Tree, execContext: Tree, resultType: Type, originalOwner: Symbol) = { val tryResult = futureSystemOps.tryType(resultType) + val execContextTempVal = + ValDef(NoMods, nme.execContextTemp, TypeTree(execContext.tpe), execContext) + val stateMachine: ClassDef = { val parents = { val customParents = futureSystemOps.stateMachineClassParents @@ -72,82 +83,102 @@ abstract class AsyncEarlyExpansion extends AsyncContext { if (useClass) definitions.abstractFunctionType(tryResult :: Nil, definitions.UnitTpe) else definitions.functionType(tryResult :: Nil, definitions.UnitTpe) - // We extend () => Unit so we can pass this class as the by-name argument to `Future.apply`. - // See SI-1247 for the the optimization that avoids creation. - val funParents = List(fun1Tpe, definitions.functionType(Nil, definitions.UnitTpe)) + val funParents = List(fun1Tpe) (customParents ::: funParents).map(TypeTree(_)) } val stateVar = - ValDef(Modifiers(Flags.MUTABLE | Flags.PRIVATE | Flags.LOCAL), nme.state, TypeTree(definitions.IntTpe), Literal(Constant(StateAssigner.Initial))) + 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(futureSystemOps.promType(resultType)), futureSystemOps.createProm(resultType)) + ValDef(NoMods, nme.result, TypeTree(promType(resultType)), createProm(resultType)) val execContextVal = - ValDef(NoMods, nme.execContext, TypeTree(execContext.tpe), execContext) - - val apply0Def = - DefDef(NoMods, nme.apply, Nil, List(Nil), TypeTree(definitions.UnitTpe), Apply(Ident(nme.apply), Literal(Constant(null)) :: Nil)) + 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), asyncBody).updateAttachment(ChangeOwnerAttachment(originalOwner)) + DefDef(NoMods, nme.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), Block(asyncBody.updateAttachment(SuppressPureExpressionWarning), Literal(Constant(())))).updateAttachment(ChangeOwnerAttachment(originalOwner)) } atPos(asyncBody.pos)(ClassDef(NoMods, tpnme.stateMachine, Nil, gen.mkTemplate(parents, noSelfType, NoMods, List(Nil), - List(stateVar, resultVal, execContextVal, apply0Def, applyFSM)))) + List(stateVar, resultVal, execContextVal, applyFSM)))) } val newStateMachine = ValDef(NoMods, nme.stateMachine, TypeTree(), Apply(Select(New(Ident(tpnme.stateMachine)), nme.CONSTRUCTOR), Nil)) - // Note the invocation `.apply()` in `scala.concurrent.Future.apply[$resultType](stateMachine$async.apply())(stateMachine$async.execContext$async);` - // so that, after uncurry, we get: scala.concurrent.Future.apply[$resultType](stateMachine$async, stateMachine$async.execContext$async()); - val stateMachineToFuture = futureSystemOps.future(Apply(Select(Ident(nme.stateMachine), nme.apply), Nil), Select(Ident(nme.stateMachine), nme.execContext)) - val promToFuture = futureSystemOps.promiseToFuture(Select(Ident(nme.stateMachine), nme.result)) + 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)) - Block(List(stateMachine, newStateMachine, stateMachineToFuture), promToFuture) + 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).updateAttachment(new FutureSystemAttachment(futureSystem)) } } +class AsyncTransformState[U <: Global with Singleton](val symbolTable: U, val futureSystem: FutureSystem, + val unit: U#CompilationUnit, + val typingTransformer: TypingTransformers#TypingTransformer) { + 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]() +} + // This was originally a macro -- TODO: complete integration with compiler universe (use global instead of scala.reflect.internal stuff) -abstract class AsyncTransform(val asyncBase: AsyncBase) extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables { - import u._ - import typingTransformers.{TypingTransformApi, typingTransform} +trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables with TypingTransformers { + var currentTransformState: AsyncTransformState[global.type] = null + import global._ + def typecheck(tree: Tree): Tree = currentTransformState.localTyper.typed(tree) + def abort(pos: Position, msg: String): Nothing = {currentTransformState.localTyper.context.reporter.error(pos, msg); ???} + def error(pos: Position, msg: String): Unit = currentTransformState.localTyper.context.reporter.error(pos, msg) + + import global._ // synthesize the state machine logic -- explode the apply method's rhs and lift local vals to field defs in the state machine /* - class stateMachine$async extends scala.runtime.AbstractFunction1 with Function0$mcV$sp { + val execContext0$async = `execContext`; + class stateMachine$async extends scala.runtime.AbstractFunction1 { def (): stateMachine$async = { stateMachine$async.super.(); stateMachine$async.super./*Function0*/$init$(); () }; 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 = Promise.apply(); def result$async(): scala.concurrent.Promise = stateMachine$async.this.result$async; - private[this] val execContext$async: scala.concurrent.ExecutionContext = `execContext`; + private[this] val execContext$async: scala.concurrent.ExecutionContext = execContext0$async; def execContext$async(): scala.concurrent.ExecutionContext = stateMachine$async.this.execContext$async; - def apply(): Unit = stateMachine$async.this.apply$mcV$sp(); def apply(tr$async: scala.util.Try): Unit = { // symbol of this def is `applySym`, symbol of its param named "tr$async" is `trParamSym` - scala.async.async(`asyncBody`, `execContext`); + `asyncBody` () }; - def apply$mcV$sp(): Unit = stateMachine$async.this.apply(null); def apply(v1: Object): Object = { stateMachine$async.this.apply(v1.$asInstanceOf[scala.util.Try]()); scala.runtime.BoxedUnit.UNIT }; - def apply(): Object = { - stateMachine$async.this.apply(); - scala.runtime.BoxedUnit.UNIT - } }; */ - def asyncTransform(asyncBody: Tree, applySym: Symbol, trParamSym: Symbol, execContext: Tree): Option[(Tree, List[Tree])] = { + def asyncTransform(asyncBody: Tree, applySym: Symbol, trParamSym: Symbol): (Tree, List[Tree]) = { + val futureSystem = currentTransformState.futureSystem + val futureSystemOps = futureSystem.mkOps(global) + val asyncPos = asyncBody.pos val stateMachineClass = applySym.owner - val resultType = exitingTyper { futureSystemOps.tryTypeToResult(trParamSym.info) } markContainsAwait(asyncBody) // ANF transform also relies on whether something contains await reportUnsupportedAwaits(asyncBody) @@ -165,77 +196,72 @@ abstract class AsyncTransform(val asyncBase: AsyncBase) extends AnfTransform wit val asyncBlock = buildAsyncBlock(anfTree, SymLookup(stateMachineClass, trParamSym)) - // generate lean code for the simple case of `async { 1 + 1 }` - if (asyncBlock.asyncStates.lengthCompare(1) == 0) None - else { - val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) - - // live variables analysis - // the result map indicates in which states a given field should be nulled out - val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields) - - for ((state, flds) <- assignsOf) { - val assigns = flds.map { fld => - val fieldSym = fld.symbol - val assign = Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info, asyncPos)) - val nulled = nullOut(fieldSym) - if (isLiteralUnit(nulled)) assign - else Block(nulled :: Nil, assign) - } - val asyncState = asyncBlock.asyncStates.find(_.state == state).get - asyncState.stats = assigns ++ asyncState.stats - } + val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) - val liftedSyms = liftedFields.map(_.symbol).toSet - liftedSyms.foreach { sym => - if (sym != null) { - sym.owner = stateMachineClass - if (sym.isModule) - sym.asModule.moduleClass.owner = stateMachineClass - } - } + // live variables analysis + // the result map indicates in which states a given field should be nulled out + val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields) + + for ((state, flds) <- assignsOf) { + val assigns = flds.map { fld => + val fieldSym = fld.symbol + Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info, asyncPos)) - // Replace the ValDefs in the async block with Assigns to the corresponding lifted - // fields. Similarly, replace references to them with references to the field. - val useFields: (Tree, TypingTransformApi) => Tree = (tree, api) => { - def fieldSel = - atPos(tree.pos)(Select(This(stateMachineClass).setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) - tree match { - case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => - if (tree.symbol.asTerm.isLazy) literalUnit - else assignUnitType(treeCopy.Assign(tree, fieldSel, api.recur(rhs.changeOwner((tree.symbol, api.currentOwner))))) - case _: DefTree if liftedSyms(tree.symbol) => EmptyTree - case Ident(name) if liftedSyms(tree.symbol) => fieldSel.setType(tree.tpe) - case _ => api.default(tree) - } } + val asyncState = asyncBlock.asyncStates.find(_.state == state).get + asyncState.stats = assigns ++ asyncState.stats + } - val liftablesUseFields = liftedFields.map { - case vd: ValDef if !vd.symbol.asTerm.isLazy => vd - case x => typingTransform(x, stateMachineClass)(useFields) + val liftedSyms = liftedFields.map(_.symbol).toSet + liftedSyms.foreach { sym => + if (sym != null) { + sym.owner = stateMachineClass + if (sym.isModule) + sym.asModule.moduleClass.owner = stateMachineClass } + } - liftablesUseFields.foreach { t => - if (t.symbol != null) { - stateMachineClass.info.decls.enter(t.symbol) - // TODO AM: refine the resetting of the lazy flag -- this is so that local lazy vals that are lifted to the class - // actually get their LazyRef allocated to the var that holds the lazy val's reference - t.symbol.resetFlag(Flags.LAZY) - } + // Replace the ValDefs in the async block with Assigns to the corresponding lifted + // fields. Similarly, replace references to them with references to the field. + object UseFields extends TypingTransformer(currentTransformState.unit) { + private def fieldSel(tree: Tree) = + atPos(tree.pos)(Select(This(stateMachineClass).setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) + override def transform(tree: Tree): Tree = tree match { + case _ if currentOwner == stateMachineClass => + super.transform(tree) + case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => + assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner((tree.symbol, currentOwner))))) + case _: DefTree if liftedSyms(tree.symbol) => EmptyTree + case Ident(name) if liftedSyms(tree.symbol) => fieldSel(tree).setType(tree.tpe) + case _ => super.transform(tree) } + } + + val liftablesUseFields = liftedFields.map { + case vd: ValDef => vd + case x => UseFields.transformAtOwner(stateMachineClass, x) + } - val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler(WeakTypeTag(transformType(resultType)))) - val applyRhs = typingTransform(applyBody, stateMachineClass)(useFields) - if (AsyncUtils.verbose) { - val location = try asyncBody.pos.source.path catch { - case _: UnsupportedOperationException => asyncBody.pos.toString - } - logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) + liftablesUseFields.foreach { t => + if (t.symbol != null && t.symbol.owner == stateMachineClass) { + stateMachineClass.info.decls.enter(t.symbol) + // TODO AM: refine the resetting of the lazy flag -- this is so that local lazy vals that are lifted to the class + // actually get their LazyRef allocated to the var that holds the lazy val's reference + t.symbol.resetFlag(Flags.LAZY) } - futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) + } - Some((cleanupContainsAwaitAttachments(applyRhs), liftablesUseFields)) + val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) + val applyRhs = UseFields.transformAtOwner(applySym, applyBody) + if (AsyncUtils.verbose) { + val location = try asyncBody.pos.source.path catch { + case _: UnsupportedOperationException => asyncBody.pos.toString + } + logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) } + futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) + + (cleanupContainsAwaitAttachments(applyRhs), liftablesUseFields) } def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index e0c5549d220d..3fa88e587b51 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -20,30 +20,21 @@ import scala.collection.mutable.ListBuffer import scala.language.existentials trait ExprBuilder extends TransformUtils { - import u._ + import global._ - lazy val futureSystem: FutureSystem = asyncBase.futureSystem - lazy val futureSystemOps: futureSystem.Ops[u.type] = futureSystem.mkOps(u, isPastErasure) + def tryAny = transformType(currentTransformState.ops.tryType(definitions.AnyTpe)) - def nullOut(fieldSym: Symbol): Tree = - asyncBase.nullOut(u)(Expr[String](Literal(Constant(fieldSym.name.toString))), Expr[Any](Ident(fieldSym))).tree - - def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = u.Expr[T](rootMirror, FixedMirrorTreeCreator(rootMirror, tree)) - def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = u.WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, tpe)) - - lazy val tryAny = transformType(futureSystemOps.tryType(definitions.AnyTpe)) - - private val stateAssigner = new StateAssigner - private val labelDefStates = collection.mutable.Map[Symbol, Int]() + private def stateAssigner = currentTransformState.stateAssigner + private def labelDefStates = currentTransformState.labelDefStates trait AsyncState { def state: Int def nextStates: Array[Int] - def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef + def mkHandlerCaseForState[T]: CaseDef - def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = None + def mkOnCompleteHandler[T]: Option[CaseDef] = None var stats: List[Tree] @@ -68,7 +59,7 @@ trait ExprBuilder extends TransformUtils { val nextStates: Array[Int] = Array(nextState) - def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { + def mkHandlerCaseForState[T]: CaseDef = { mkHandlerCase(state, treeThenStats(mkStateTree(nextState, symLookup))) } @@ -80,7 +71,7 @@ trait ExprBuilder extends TransformUtils { * a branch of an `if` or a `match`. */ final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: Array[Int]) extends AsyncState { - override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = + override def mkHandlerCaseForState[T]: CaseDef = mkHandlerCase(state, stats) override val toString: String = @@ -97,14 +88,16 @@ trait ExprBuilder extends TransformUtils { val nextStates: Array[Int] = Array(nextState) - override def mkHandlerCaseForState[T: WeakTypeTag]: CaseDef = { + override def mkHandlerCaseForState[T]: CaseDef = { + val futureSystem = currentTransformState.futureSystem + val futureSystemOps = futureSystem.mkOps(global) val fun = This(tpnme.EMPTY) - val callOnComplete = futureSystemOps.onComplete[Any, Unit](Expr[futureSystem.Fut[Any]](awaitable.expr), - Expr[futureSystem.Tryy[Any] => Unit](fun), Expr[futureSystem.ExecContext](Ident(nme.execContext))).tree + val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, + fun, Ident(nme.execContext)) val tryGetOrCallOnComplete: List[Tree] = if (futureSystemOps.continueCompletedFutureOnSameThread) { val tempName = nme.completed - val initTemp = ValDef(NoMods, tempName, TypeTree(tryAny), futureSystemOps.getCompleted[Any](Expr[futureSystem.Fut[Any]](awaitable.expr)).tree) + val initTemp = ValDef(NoMods, tempName, TypeTree(tryAny), futureSystemOps.getCompleted[Any](awaitable.expr)) val null_ne = Select(Literal(Constant(null)), TermName("ne")) val ifTree = If(Apply(null_ne, Ident(tempName) :: Nil), @@ -125,19 +118,19 @@ trait ExprBuilder extends TransformUtils { * * } */ - def ifIsFailureTree[T: WeakTypeTag](tryReference: => Tree) = { - val tryyGet = - // no need to cast past erasure, and in fact we should leave boxing or casting to the erasure typer - if (isPastErasure) futureSystemOps.tryyGet[Any](Expr[futureSystem.Tryy[Any]](tryReference)).tree - else mkAsInstanceOf(futureSystemOps.tryyGet[Any](Expr[futureSystem.Tryy[Any]](tryReference)).tree, awaitable.resultType) + def ifIsFailureTree[T](tryReference: => Tree) = { + val futureSystem = currentTransformState.futureSystem + val futureSystemOps = futureSystem.mkOps(global) + assert(isPastErasure) + val tryyGet = futureSystemOps.tryyGet[Any](tryReference) val getAndUpdateState = Block(List(Assign(Ident(awaitable.resultName), tryyGet)), mkStateTree(nextState, symLookup)) - if (emitTryCatch) { - If(futureSystemOps.tryyIsFailure(Expr[futureSystem.Tryy[T]](tryReference)).tree, + if (futureSystem.emitTryCatch) { + If(futureSystemOps.tryyIsFailure(tryReference), Block(toList(futureSystemOps.completeProm[T]( - Expr[futureSystem.Prom[T]](symLookup.selectResult), - Expr[futureSystem.Tryy[T]](mkAsInstanceOf(tryReference, transformType(futureSystemOps.tryType(implicitly[WeakTypeTag[T]].tpe))))).tree), // TODO: this is pretty bonkers... implicitly[WeakTypeTag[T]].tpe == resultType + symLookup.selectResult, + tryReference)), Return(literalUnit)), getAndUpdateState ) @@ -146,7 +139,7 @@ trait ExprBuilder extends TransformUtils { } } - override def mkOnCompleteHandler[T: WeakTypeTag]: Option[CaseDef] = { + override def mkOnCompleteHandler[T]: Option[CaseDef] = { Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree[T](Ident(symLookup.applyTrParam))))) } @@ -261,7 +254,8 @@ trait ExprBuilder extends TransformUtils { new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, symLookup) } - import stateAssigner.nextState + + def nextState() = stateAssigner.nextState() def directlyAdjacentLabelDefs(t: Tree): List[Tree] = { def isPatternCaseLabelDef(t: Tree) = t match { case LabelDef(name, _, _) => name.toString.startsWith("case") @@ -370,7 +364,7 @@ trait ExprBuilder extends TransformUtils { trait AsyncBlock { def asyncStates: List[AsyncState] - def onCompleteHandler[T: WeakTypeTag]: Tree + def onCompleteHandler[T]: Tree def toDot: String } @@ -381,7 +375,12 @@ trait ExprBuilder extends TransformUtils { stateMachineClass.info.member(name) } def memberRef(name: TermName): Tree = - gen.mkAttributedRef(stateMachineMember(name)) + gen.mkAttributedRef(stateMachineClass.typeConstructor, stateMachineMember(name)) + def memberRef(sym: Symbol): Tree = + gen.mkAttributedRef(stateMachineClass.typeConstructor, sym) + + lazy val stateGetter: Symbol = stateMachineMember(nme.state) + lazy val stateSetter: Symbol = stateGetter.setterIn(stateGetter.owner) def selectResult = applyNilAfterUncurry(memberRef(nme.result)) } @@ -505,12 +504,15 @@ trait ExprBuilder extends TransformUtils { } - def mkCombinedHandlerCases[T: WeakTypeTag]: List[CaseDef] = { + def mkCombinedHandlerCases[T]: List[CaseDef] = { + val futureSystem = currentTransformState.futureSystem + val futureSystemOps = futureSystem.mkOps(global) + val caseForLastState: CaseDef = { val lastState = asyncStates.last - val lastStateBody = Expr[T](lastState.body) + val lastStateBody = lastState.body val rhs = futureSystemOps.completeWithSuccess( - Expr[futureSystem.Prom[T]](symLookup.selectResult), lastStateBody).tree + symLookup.selectResult, lastStateBody) mkHandlerCase(lastState.state, Block(rhs, Return(literalUnit))) } asyncStates match { @@ -541,8 +543,11 @@ trait ExprBuilder extends TransformUtils { * case NonFatal(t) => result.failure(t) * } */ - private def resumeFunTree[T: WeakTypeTag]: Tree = { - val stateMemberRef = symLookup.memberRef(nme.state) + private def resumeFunTree[T]: Tree = { + val futureSystem = currentTransformState.futureSystem + val futureSystemOps = futureSystem.mkOps(global) + + val stateMemberRef = gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)) val body = Match(stateMemberRef, mkCombinedHandlerCases[T] ++ @@ -551,16 +556,16 @@ trait ExprBuilder extends TransformUtils { val body1 = compactStates(body) - maybeTry( + maybeTry(currentTransformState.futureSystem.emitTryCatch)( body1, List( CaseDef( Bind(nme.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), EmptyTree, { val branchTrue = { - val t = Expr[Throwable](Ident(nme.t)) + val t = Ident(nme.t) val complete = futureSystemOps.completeProm[T]( - Expr[futureSystem.Prom[T]](symLookup.selectResult), futureSystemOps.tryyFailure[T](t)).tree + symLookup.selectResult, futureSystemOps.tryyFailure[T](t)) Block(toList(complete), Return(literalUnit)) } If(Apply(Ident(NonFatalClass), List(Ident(nme.t))), branchTrue, Throw(Ident(nme.t))) @@ -568,12 +573,11 @@ trait ExprBuilder extends TransformUtils { })), EmptyTree) } - private lazy val stateMemberSymbol = symLookup.stateMachineMember(nme.state) private val compactStateTransform = new Transformer { override def transform(tree: Tree): Tree = tree match { - case as @ Assign(lhs, Literal(Constant(i: Integer))) if lhs.symbol == stateMemberSymbol => + case as @ Apply(qual: Select, Literal(Constant(i: Integer)) :: Nil) if qual.symbol == symLookup.stateSetter => val replacement = switchIds(i) - treeCopy.Assign(tree, lhs, Literal(Constant(replacement))) + treeCopy.Apply(tree, qual, Literal(Constant(replacement)):: Nil) case _: Match | _: CaseDef | _: Block | _: If => super.transform(tree) case _ => tree @@ -599,7 +603,7 @@ trait ExprBuilder extends TransformUtils { /** * Builds a `match` expression used as an onComplete handler, wrapped in a while(true) loop. */ - def onCompleteHandler[T: WeakTypeTag]: Tree = { + def onCompleteHandler[T]: Tree = { forever { adaptToUnit(toList(resumeFunTree)) } @@ -615,7 +619,7 @@ trait ExprBuilder extends TransformUtils { case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef) private def mkStateTree(nextState: Int, symLookup: SymLookup): Tree = - Assign(symLookup.memberRef(nme.state), Literal(Constant(nextState))) + Apply(symLookup.memberRef(symLookup.stateSetter), Literal(Constant(nextState)) :: Nil) private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef = mkHandlerCase(num, adaptToUnit(rhs)) diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index d08a21f5384f..3a01ff64402b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -16,7 +16,7 @@ import scala.collection.mutable import scala.reflect.internal.Flags._ trait Lifter extends ExprBuilder { - import u._ + import global._ /** * Identify which DefTrees are used (including transitively) which are declared @@ -103,7 +103,7 @@ trait Lifter extends ExprBuilder { // Only mark transitive references of defs, modules and classes. The RHS of lifted vals/vars // stays in its original location, so things that it refers to need not be lifted. - if (!(sym.isTerm && !sym.asTerm.isLazy && (sym.asTerm.isVal || sym.asTerm.isVar))) + if (!(sym.isTerm && (sym.asTerm.isVal || sym.asTerm.isVar))) defSymToReferenced(sym).foreach(sym2 => markForLift(sym2)) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index f4305c96c393..0d38387e1111 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -20,7 +20,7 @@ import scala.collection.mutable import scala.reflect.internal.Flags._ trait LiveVariables extends ExprBuilder { - import u._ + import global._ /** * Returns for a given state a list of fields (as trees) that should be nulled out diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 11303af6ffed..df64fa7da8c3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -14,36 +14,31 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.reflect.internal.{Flags, SymbolTable} -import user.AsyncBase -import scala.tools.nsc.{Global, NoPhase} +import scala.tools.nsc.NoPhase import scala.language.existentials - -private[async] trait AsyncContext { - val asyncBase: AsyncBase - val u: Global -} +import scala.reflect.internal.util.ListOfNil +import scala.tools.nsc.transform.TypingTransformers // Logic sensitive to where we are in the pipeline // (intend to move the transformation as late as possible, to avoid lugging all these trees around) -trait PhasedTransform extends AsyncContext { - import u._ +trait PhasedTransform extends TypingTransformers { + import global._ // macro context interface -- the rest is meant to be independent of our being a macro (planning to move async into the compiler) def abort(pos: Position, msg: String): Nothing def error(pos: Position, msg: String): Unit def typecheck(tree: Tree): Tree + def isPastErasure: Boolean = { + val erasurePhase = global.currentRun.erasurePhase + erasurePhase != NoPhase && global.isPast(erasurePhase) + } + // We're not that granular, but keeping separate flag for semantics - private lazy val isPastUncurry = isPastErasure - private lazy val emptyParamss: List[Nil.type] = if (isPastUncurry) List(Nil) else Nil + private def isPastUncurry = isPastErasure + private def emptyParamss: List[List[ValDef]] = if (isPastUncurry) ListOfNil else Nil protected def applyNilAfterUncurry(t: Tree) = if (isPastUncurry) Apply(t, Nil) else t - lazy val isPastErasure = { - val erasurePhase = u.currentRun.erasurePhase - erasurePhase != NoPhase && u.isPast(erasurePhase) - } - def literalNull = Literal(Constant(null)) def typeEqualsNothing(tp: Type) = tp =:= definitions.NothingTpe @@ -133,28 +128,6 @@ trait PhasedTransform extends AsyncContext { } } - final def uncheckedBounds(tp: Type): Type = if (isPastErasure) tp else u.uncheckedBounds(tp) - - def uncheckedBoundsIfNeeded(t: Type): Type = { - var quantified: List[Symbol] = Nil - var badSkolemRefs: List[Symbol] = Nil - t.foreach { - case et: ExistentialType => - quantified :::= et.quantified - case TypeRef(pre, sym, args) => - val illScopedSkolems = args.map(_.typeSymbol).filter(arg => arg.isExistentialSkolem && !quantified.contains(arg)) - badSkolemRefs :::= illScopedSkolems - case _ => - } - if (badSkolemRefs.isEmpty) t - else t.map { - case tp @ TypeRef(pre, sym, args) if args.exists(a => badSkolemRefs.contains(a.typeSymbol)) => - uncheckedBounds(tp) - case t => t - } - } - - final def mkMutableField(tpt: Type, name: TermName, init: Tree): List[Tree] = { if (isPastTyper) { import scala.reflect.internal.Flags._ @@ -192,28 +165,22 @@ trait PhasedTransform extends AsyncContext { * Utilities used in both `ExprBuilder` and `AnfTransform`. */ private[async] trait TransformUtils extends PhasedTransform { - import typingTransformers.{TypingTransformApi, typingTransform} - import u._ + import global._ - val asyncNames: AsyncNames[u.type] + def currentTransformState: AsyncTransformState[global.type] + val asyncNames: AsyncNames[global.type] object name extends asyncNames.AsyncName { def fresh(name: TermName): TermName = freshenIfNeeded(name) def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName } - def emitTryCatch: Boolean = asyncBase.futureSystem.emitTryCatch - - def maybeTry(block: Tree, catches: List[CaseDef], finalizer: Tree) = + def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree) = if (emitTryCatch) Try(block, catches, finalizer) else block lazy val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException") - val Async_async: Symbol - val Async_await: Symbol - - def isAsync(fun: Tree) = fun.symbol == Async_async - def isAwait(fun: Tree) = fun.symbol == Async_await - + def isAsync(fun: Tree) = fun.symbol == currentTransformState.ops.Async_async + def isAwait(fun: Tree) = fun.symbol == currentTransformState.ops.Async_await private lazy val Boolean_ShortCircuits: Set[Symbol] = { import definitions.BooleanClass @@ -356,13 +323,6 @@ private[async] trait TransformUtils extends PhasedTransform { } } - def transformAt(tree: Tree)(f: PartialFunction[Tree, (TypingTransformApi => Tree)]) = { - typingTransform(tree)((tree, api) => { - if (f.isDefinedAt(tree)) f(tree)(api) - else api.default(tree) - }) - } - def toMultiMap[A, B](abs: Iterable[(A, B)]): mutable.LinkedHashMap[A, List[B]] = { // LinkedHashMap for stable order of results. val result = new mutable.LinkedHashMap[A, ListBuffer[B]]() @@ -373,23 +333,6 @@ private[async] trait TransformUtils extends PhasedTransform { result.map { case (a, b) => (a, b.toList) } } - // Attributed version of `TreeGen#mkCastPreservingAnnotations` - def mkAttributedCastPreservingAnnotations(tree: Tree, tp: Type): Tree = { - atPos(tree.pos) { - val casted = typecheck(gen.mkCast(tree, uncheckedBounds(tp.withoutAnnotations).dealias)) - Typed(casted, TypeTree(tp)).setType(tp) - } - } - - def withAnnotation(tp: Type, ann: Annotation): Type = withAnnotations(tp, List(ann)) - - def withAnnotations(tp: Type, anns: List[Annotation]): Type = tp match { - case AnnotatedType(existingAnns, underlying) => annotatedType(anns ::: existingAnns, underlying) - case ExistentialType(quants, underlying) => existentialAbstraction(quants, withAnnotations(underlying, anns)) - case _ => annotatedType(anns, tp) - } - - def thisType(sym: Symbol): Type = { if (sym.isClass) sym.asClass.thisPrefix else NoPrefix @@ -422,6 +365,7 @@ private[async] trait TransformUtils extends PhasedTransform { private object markContainsAwaitTraverser extends Traverser { def shouldAttach(t: Tree) = !treeCannotContainAwait(t) private def treeCannotContainAwait(t: Tree) = t match { + case _: CannotHaveAttrs => true case _: Ident | _: TypeTree | _: Literal => true case _ => isAsync(t) } @@ -452,9 +396,11 @@ private[async] trait TransformUtils extends PhasedTransform { } final def cleanupContainsAwaitAttachments(t: Tree): t.type = { - t.foreach {t => - t.removeAttachment[ContainsAwait.type] - t.removeAttachment[NoAwait.type] + t.foreach { + case _: CannotHaveAttrs => + case t => + t.removeAttachment[ContainsAwait.type] + t.removeAttachment[NoAwait.type] } t } @@ -464,58 +410,45 @@ private[async] trait TransformUtils extends PhasedTransform { // - Propagate this change to trees known to directly enclose them: // ``If` / `Block`) adjust types of enclosing final def adjustTypeOfTranslatedPatternMatches(t: Tree, owner: Symbol): Tree = { - import definitions.UnitTpe - - typingTransform(t, owner) { - (tree, api) => - tree match { - case LabelDef(name, params, rhs) => - val rhs1 = api.recur(rhs) - if (rhs1.tpe =:= UnitTpe) { - tree.symbol.info = internal.methodType(tree.symbol.info.paramLists.head, UnitTpe) - treeCopy.LabelDef(tree, name, params, rhs1) - } else { - treeCopy.LabelDef(tree, name, params, rhs1) - } - case Block(stats, expr) => - val stats1 = stats map api.recur - val expr1 = api.recur(expr) - if (expr1.tpe =:= UnitTpe) - treeCopy.Block(tree, stats1, expr1).setType(UnitTpe) - else - treeCopy.Block(tree, stats1, expr1) - case If(cond, thenp, elsep) => - val cond1 = api.recur(cond) - val thenp1 = api.recur(thenp) - val elsep1 = api.recur(elsep) - if (thenp1.tpe =:= definitions.UnitTpe && elsep.tpe =:= UnitTpe) - treeCopy.If(tree, cond1, thenp1, elsep1).setType(UnitTpe) - else - treeCopy.If(tree, cond1, thenp1, elsep1) - case Apply(fun, args) if isLabel(fun.symbol) => - treeCopy.Apply(tree, api.recur(fun), args map api.recur).setType(UnitTpe) - case vd @ ValDef(mods, name, tpt, rhs) if isCaseTempVal(vd.symbol) => - def addUncheckedBounds(t: Tree) = { - typingTransform(t, owner) { - (tree, api) => - if (tree.tpe == null) tree else api.default(tree).setType(uncheckedBoundsIfNeeded(tree.tpe)) - } - - } - val uncheckedRhs = addUncheckedBounds(api.recur(rhs)) - val uncheckedTpt = addUncheckedBounds(tpt) - vd.symbol.info = uncheckedBoundsIfNeeded(vd.symbol.info) - treeCopy.ValDef(vd, mods, name, uncheckedTpt, uncheckedRhs) - case t => api.default(t) - } - } + val trans = new PatmatAdjuster + trans.transformAtOwner(owner, t) } - private def isCaseTempVal(s: Symbol) = { - s.isTerm && s.asTerm.isVal && s.isSynthetic && s.name.toString.startsWith("x") + private class PatmatAdjuster extends TypingTransformer(currentTransformState.unit) { + import definitions.UnitTpe + + override def transform(tree: Tree): Tree = { + tree match { + case LabelDef(name, params, rhs) => + val rhs1 = transform(rhs) + if (rhs1.tpe =:= UnitTpe) { + tree.symbol.info = internal.methodType(tree.symbol.info.paramLists.head, UnitTpe) + treeCopy.LabelDef(tree, name, params, rhs1) + } else { + treeCopy.LabelDef(tree, name, params, rhs1) + } + case Block(stats, expr) => + val stats1 = transformTrees(stats) + val expr1 = transform(expr) + if (expr1.tpe =:= UnitTpe) + treeCopy.Block(tree, stats1, expr1).setType(UnitTpe) + else + treeCopy.Block(tree, stats1, expr1) + case If(cond, thenp, elsep) => + val cond1 = transform(cond) + val thenp1 = transform(thenp) + val elsep1 = transform(elsep) + if (thenp1.tpe =:= definitions.UnitTpe && elsep.tpe =:= UnitTpe) + treeCopy.If(tree, cond1, thenp1, elsep1).setType(UnitTpe) + else + treeCopy.If(tree, cond1, thenp1, elsep1) + case Apply(fun, args) if isLabel(fun.symbol) => + treeCopy.Apply(tree, transform(fun), transformTrees(args)).setType(UnitTpe) + case t => super.transform(t) + } + } } - def deriveLabelDef(ld: LabelDef, applyToRhs: Tree => Tree): LabelDef = { val rhs2 = applyToRhs(ld.rhs) val ld2 = treeCopy.LabelDef(ld, ld.name, ld.params, rhs2) @@ -536,90 +469,6 @@ private[async] trait TransformUtils extends PhasedTransform { case _ => None } } - - // TODO get rid of all of this and use compiler's facilities directly - val typingTransformers: TypingTransformers { val global: u.type } - - abstract class TypingTransformers extends scala.tools.nsc.transform.TypingTransformers { - val global: u.type = u - def callsiteTyper: analyzer.Typer - - trait TransformApi { - /** Calls the current transformer on the given tree. - * Current transformer = argument to the `transform` call. - */ - def recur(tree: Tree): Tree - - /** Calls the default transformer on the given tree. - * Default transformer = recur into tree's children and assemble the results. - */ - def default(tree: Tree): Tree - } - - /** Functions that are available during [[typingTransform]]. - * @see [[typingTransform]] - */ - trait TypingTransformApi extends TransformApi { - /** Temporarily pushes the given symbol onto the owner stack, creating a new local typer, - * invoke the given operation and then rollback the changes to the owner stack. - */ - def atOwner[T](owner: Symbol)(op: => T): T - - /** Temporarily pushes the given tree onto the recursion stack, and then calls `atOwner(symbol)(trans)`. - */ - def atOwner[T](tree: Tree, owner: Symbol)(op: => T): T - - /** Returns the symbol currently on the top of the owner stack. - * If we're not inside any `atOwner` call, then macro application's context owner will be used. - */ - def currentOwner: Symbol - - /** Typechecks the given tree using the local typer currently on the top of the owner stack. - * If we're not inside any `atOwner` call, then macro application's callsite typer will be used. - */ - def typecheck(tree: Tree): Tree - } - - class HofTransformer(hof: (Tree, TransformApi) => Tree) extends Transformer { - val api = new TransformApi { - def recur(tree: Tree): Tree = hof(tree, this) - def default(tree: Tree): Tree = superTransform(tree) - } - def superTransform(tree: Tree) = super.transform(tree) - override def transform(tree: Tree): Tree = hof(tree, api) - } - - // def transform(tree: Tree)(transformer: (Tree, TransformApi) => Tree): Tree = new HofTransformer(transformer).transform(tree) - - class HofTypingTransformer(hof: (Tree, TypingTransformApi) => Tree) extends TypingTransformer(callsiteTyper.context.unit) { self => - currentOwner = callsiteTyper.context.owner - curTree = EmptyTree - - localTyper = { - val ctx: analyzer.Context = callsiteTyper.context.make(unit = callsiteTyper.context.unit) - if (phase.erasedTypes) erasure.newTyper(ctx.asInstanceOf[erasure.Context]).asInstanceOf[analyzer.Typer] else analyzer.newTyper(ctx) - } - - val api = new TypingTransformApi { - def recur(tree: Tree): Tree = hof(tree, this) - def default(tree: Tree): Tree = superTransform(tree) - def atOwner[T](owner: Symbol)(op: => T): T = self.atOwner(owner)(op) - def atOwner[T](tree: Tree, owner: Symbol)(op: => T): T = self.atOwner(tree, owner)(op) - def currentOwner: Symbol = self.currentOwner - def typecheck(tree: Tree): Tree = localTyper.typed(tree) - } - def superTransform(tree: Tree) = super.transform(tree) - override def transform(tree: Tree): Tree = hof(tree, api) - } - - def typingTransform(tree: Tree)(transformer: (Tree, TypingTransformApi) => Tree): Tree = new HofTypingTransformer(transformer).transform(tree) - - def typingTransform(tree: Tree, owner: Symbol)(transformer: (Tree, TypingTransformApi) => Tree): Tree = { - val trans = new HofTypingTransformer(transformer) - trans.atOwner(owner)(trans.transform(tree)) - } - } - } case object ContainsAwait diff --git a/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala b/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala deleted file mode 100644 index 4c08a5007cba..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/user/AsyncBase.scala +++ /dev/null @@ -1,111 +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 -package user - -import scala.reflect.api.Universe -import scala.reflect.internal.annotations.compileTimeOnly -import scala.reflect.macros.Aliases -import scala.reflect.macros.whitebox.Context - -/** - * A base class for the `async` macro. Subclasses must provide: - * - * - Concrete types for a given future system - * - Tree manipulations to create and complete the equivalent of Future and Promise - * in that system. - * - The `async` macro declaration itself, and a forwarder for the macro implementation. - * (The latter is temporarily needed to workaround bug SI-6650 in the macro system) - * - * The default implementation, [[scala.async]], binds the macro to `scala.concurrent._`. - */ -abstract class AsyncBase { - type FS <: FutureSystem - val futureSystem: FS - -// /** -// * A call to `await` must be nested in an enclosing `async` block. -// * -// * A call to `await` does not block the current thread, rather it is a delimiter -// * used by the enclosing `async` macro. Code following the `await` -// * call is executed asynchronously, when the argument of `await` has been completed. -// * -// * @param awaitable the future from which a value is awaited. -// * @tparam T the type of that value. -// * @return the value. -// */ -// @compileTimeOnly("`await` must be enclosed in an `async` block") -// def await[T](awaitable: futureSystem.Fut[T]): T = ??? -// -// def asyncImpl[T: c.WeakTypeTag](c: Context) -// (body: c.Expr[T]) -// (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[futureSystem.Fut[T]] = { -// -// // I think there's a bug in subtyping -- shouldn't be necessary to specify the Aliases parent explicitly -// // (but somehow we don't rebind the abstract types otherwise) -// val ctx = c.asInstanceOf[Aliases with scala.reflect.macros.whitebox.Context {val universe: asyncMacro.u.type}] -// val asyncMacroSymbol = ctx.macroApplication.symbol -// -// object asyncMacro extends AsyncTransform(AsyncBase.this, c.universe.asInstanceOf[scala.reflect.internal.SymbolTable]) { -// // The actual transformation happens in terms of the internal compiler data structures. -// // We hide the macro context from the classes in the transform package, -// // because they're basically a compiler plugin packaged as a macro. -// import u._ -// -// // TODO AM: rework -// val asyncNames: AsyncNames[u.type] = rootMirror.RootClass.attachments.get[AsyncNames[u.type]].getOrElse { -// val names = new AsyncNames[u.type](u) -// rootMirror.RootClass.attachments.update(names) -// names -// } -// -// val Async_async = asyncMethod(u)(asyncMacroSymbol) -// val Async_await = awaitMethod(u)(asyncMacroSymbol) -// -// // a few forwarders to context, since they are not easily available through SymbolTable -// def typecheck(tree: Tree): Tree = ctx.typecheck(tree) -// def abort(pos: Position, msg: String): Nothing = ctx.abort(pos, msg) -// def error(pos: Position, msg: String): Unit = ctx.error(pos, msg) -// val typingTransformers = new TypingTransformers { -// val callsiteTyper: global.analyzer.Typer = ctx.asInstanceOf[scala.reflect.macros.contexts.Context].callsiteTyper.asInstanceOf[global.analyzer.Typer] -// } -// } -// -// val enclosingOwner = ctx.internal.enclosingOwner -// val code = -// try asyncMacro.asyncTransform(body.tree.asInstanceOf[ctx.Tree], execContext.tree.asInstanceOf[ctx.Tree], enclosingOwner, asyncMacroSymbol.pos.makeTransparent)(ctx.weakTypeTag[T].tpe) -// catch { case te: ctx.universe.TypeError => ctx.info(enclosingOwner.pos, te.getStackTrace.mkString("\n"), true); ??? } -// -// AsyncUtils.vprintln(s"async state machine transform expands to:\n $code") -// -// // Mark range positions for synthetic code as transparent to allow some wiggle room for overlapping ranges -// for (t <- code) t.setPos(t.pos.makeTransparent) -// c.Expr[futureSystem.Fut[T]](code.asInstanceOf[c.Tree]) -// } -// -// -// protected[async] def asyncMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { -// import u._ -// if (asyncMacroSymbol == null) NoSymbol -// else asyncMacroSymbol.owner.typeSignature.member(TermName("async")) -// } -// -// protected[async] def awaitMethod(u: Universe)(asyncMacroSymbol: u.Symbol): u.Symbol = { -// import u._ -// if (asyncMacroSymbol == null) NoSymbol -// else asyncMacroSymbol.owner.typeSignature.member(TermName("await")) -// } - - protected[async] def nullOut(u: Universe)(name: u.Expr[String], v: u.Expr[Any]): u.Expr[Unit] = - u.reify { () } -} diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index 5f4bd9672031..26938f504c44 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -13,19 +13,19 @@ package scala.tools.nsc.transform.async.user import scala.language.higherKinds -import scala.reflect.internal.SymbolTable +import scala.reflect.internal.{NoPhase, SymbolTable} +import scala.tools.nsc.Global /** * An abstraction over a future system. * - * Used by the macro implementations in [[scala.async.AsyncBase]] to + * Used by the macro implementations in [[scala.tools.nsc.transform.async.AsyncTransform]] 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. */ -// TODO use Tree instead of Expr trait FutureSystem { /** A container to receive the final value of the computation */ type Prom[A] @@ -36,31 +36,30 @@ trait FutureSystem { /** Any data type isomorphic to scala.util.Try. */ type Tryy[T] - // We could do with just Universe <: reflect.api.Universe if it wasn't Expr and WeakTypeTag - // (the api effectively doesn't let you call these factories since there's no way to get at the Tree- and TypeCreators) - abstract class Ops[Universe <: SymbolTable](val u: Universe, val isPastErasure: Boolean) { + abstract class Ops[Universe <: SymbolTable](val u: Universe) { import u._ - def Expr[T: WeakTypeTag](tree: Tree): Expr[T] = u.Expr[T](rootMirror, FixedMirrorTreeCreator(rootMirror, tree)) - def WeakTypeTag[T](tpe: Type): WeakTypeTag[T] = u.WeakTypeTag[T](rootMirror, FixedMirrorTypeCreator(rootMirror, tpe)) + final def isPastErasure = { + val global = u.asInstanceOf[Global] + val erasurePhase = global.currentRun.erasurePhase + erasurePhase != NoPhase && global.isPast(erasurePhase) + } + def Async_async: Symbol + def Async_await: Symbol - def literalUnitExpr = Expr[Unit](if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) else Literal(Constant(()))) + def literalUnitExpr = if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) else Literal(Constant(())) def phasedAppliedType(tycon: Type, tp: Type) = if (isPastErasure) tycon else appliedType(tycon, tp) - def promType(tp: Type): Type def tryType(tp: Type): Type def tryTypeToResult(tp: Type): Type def stateMachineClassParents: List[Type] = Nil - /** Create an empty promise -- tree should be suitable for use during typer phase. */ - def createProm(resultType: Type): Tree - - /** Extract a future from the given promise -- tree should be suitable for use during typer phase. */ - def promiseToFuture(prom: Tree): Tree - /** 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], @@ -73,27 +72,28 @@ trait FutureSystem { * * Only called when isPastErasure */ - def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = + 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: WeakTypeTag](prom: Expr[Prom[A]], value: Expr[A]): Expr[Unit] = completeProm(prom, tryySuccess(value)) + 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: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] - def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[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, isPastErasure: Boolean = false): Ops[u.type] + def mkOps(u: SymbolTable): Ops[u.type] @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7") def freshenAllNames: Boolean = false @@ -111,92 +111,81 @@ object ScalaConcurrentFutureSystem extends FutureSystem { type ExecContext = ExecutionContext type Tryy[A] = scala.util.Try[A] - def mkOps(u: SymbolTable, isPastErasure: Boolean = false): Ops[u.type] = new ScalaConcurrentOps[u.type](u, isPastErasure) - class ScalaConcurrentOps[Universe <: SymbolTable](u0: Universe, isPastErasure: Boolean) extends Ops[Universe](u0, isPastErasure) { + def mkOps(u: SymbolTable): Ops[u.type] = new ScalaConcurrentOps[u.type](u) + class ScalaConcurrentOps[Universe <: SymbolTable](u0: Universe) extends Ops[Universe](u0) { import u._ - def promType(tp: Type): Type = appliedType(Promise, tp) - def tryType(tp: Type): Type = appliedType(TryClass, tp) - def tryTypeToResult(tp: Type): Type = tp.baseType(TryClass).typeArgs.headOption.getOrElse(NoType) - - lazy val future = newTermName("future") - lazy val Future = rootMirror.getRequiredModule("scala.concurrent.Future") - lazy val Promise = rootMirror.requiredClass[scala.concurrent.Promise[_]] - lazy val TryClass = rootMirror.requiredClass[scala.util.Try[_]] - - // only called to generate a typer-time tree (!isPastErasure) - def createProm(resultType: Type): Tree = - Apply(TypeApply(gen.mkAttributedStableRef(Promise.companionModule), TypeTree(resultType) :: Nil), Nil) - - // only called to generate a typer-time tree (!isPastErasure) - def promiseToFuture(prom: Tree) = Select(prom, future) + 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 tryTypeToResult(tp: Type): Type = tp.baseType(Try_class).typeArgs.headOption.getOrElse(NoType) def future(a: Tree, execContext: Tree): Tree = - if (isPastErasure) Apply(Select(gen.mkAttributedStableRef(Future), nme.apply), List(a, execContext)) - else Apply(Apply(Select(gen.mkAttributedStableRef(Future), nme.apply), List(a)), List(execContext)) + 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 expr = reify { future.splice.onComplete(fun.splice)(execContext.splice) } + val sel = Select(future, Future_onComplete) if (isPastErasure) - expr.tree match { - case ap@Apply(Apply(fut, fun), execCtx) => Expr[Unit](treeCopy.Apply(ap, fut, fun ++ execCtx)) - } - else expr + 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 (isPastErasure) Apply(sel, Nil) else sel + } - override def getCompleted[A: WeakTypeTag](future: Expr[Fut[A]]): Expr[Tryy[A]] = { - val valueGet = - if (isPastErasure) { - val futVal = reify { future.splice.value } - val futValGet = reify { - Expr[Option[util.Try[A]]](Apply(futVal.tree, Nil)).splice.get - } - Expr[Tryy[A]](Apply(futValGet.tree, Nil)) - } - else reify { future.splice.value.get } - - val isCompleted = reify { future.splice.isCompleted } - reify { - if ({ if (isPastErasure) Expr[Boolean](Apply(isCompleted.tree, Nil)) else isCompleted }.splice) - valueGet.splice else null - } + 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] = reify { - prom.splice.complete(value.splice) - literalUnitExpr.splice + def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = { + Block(gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) :: Nil, literalUnitExpr) } def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = { - val expr = reify { tryy.splice.isFailure } - if (isPastErasure) Expr[Boolean](Apply(expr.tree, Nil)) - else expr + mkAttributedSelectApplyIfNeeded(tryy, Try_isFailure) } def tryyGet[A](tryy: Expr[Tryy[A]]): Expr[A] = { - val expr = reify { tryy.splice.get } - if (isPastErasure) Expr[A](Apply(expr.tree, Nil)) - else expr + mkAttributedSelectApplyIfNeeded(tryy, Try_get) } - def tryySuccess[A: WeakTypeTag](a: Expr[A]): Expr[Tryy[A]] = { - val expr = reify { scala.util.Success[A](a.splice) } - if (isPastErasure) - Expr[Tryy[A]](expr.tree match { // drop type apply - case ap@Apply(TypeApply(succ, _), args) => treeCopy.Apply(ap, succ, args) - }) - else expr + def tryySuccess[A](a: Expr[A]): Expr[Tryy[A]] = { + assert(isPastErasure) + New(Success_class, a) } - def tryyFailure[A: WeakTypeTag](a: Expr[Throwable]): Expr[Tryy[A]] = { - val expr = reify { scala.util.Failure[A](a.splice) } - if (isPastErasure) - Expr[Tryy[A]](expr.tree match { // drop type apply - case ap@Apply(TypeApply(fail, _), args) => treeCopy.Apply(ap, fail, args) - }) - else expr + + def tryyFailure[A](a: Expr[Throwable]): Expr[Tryy[A]] = { + assert(isPastErasure) + New(Failure_class, a) } } } diff --git a/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala b/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala deleted file mode 100644 index b6f9ce169d29..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/user/ScalaConcurrentAsync.scala +++ /dev/null @@ -1,27 +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.concurrent.Future -import scala.reflect.macros.whitebox.Context - -object ScalaConcurrentAsync extends AsyncBase { - type FS = ScalaConcurrentFutureSystem.type - val futureSystem: FS = ScalaConcurrentFutureSystem - -// override def asyncImpl[T: c.WeakTypeTag](c: Context) -// (body: c.Expr[T]) -// (execContext: c.Expr[futureSystem.ExecContext]): c.Expr[Future[T]] = { -// super.asyncImpl[T](c)(body)(execContext) -// } -} diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 02a7869e4a71..83888bab9020 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -69,7 +69,7 @@ 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.macroExpansion.fastTrackEntry) + ) ++ makeBlackBoxIfExists(global.async.fastTrackEntry) } } diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index a44626c1e7a7..0ae72e6abf53 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -122,4 +122,6 @@ trait StdAttachments { // When typing a Def with this attachment, change the owner of its RHS from origalOwner to the symbol of the Def case class ChangeOwnerAttachment(originalOwner: Symbol) + + case object SuppressPureExpressionWarning extends PlainAttachment } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 14ac74e66a12..8e6e745e6bb9 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -820,6 +820,7 @@ trait StdNames { val unapply: NameType = "unapply" val unapplySeq: NameType = "unapplySeq" val unbox: NameType = "unbox" + val unit: NameType = "unit" val universe: NameType = "universe" val UnliftListElementwise: NameType = "UnliftListElementwise" val UnliftListOfListsElementwise: NameType = "UnliftListOfListsElementwise" @@ -834,13 +835,15 @@ trait StdNames { val zero: NameType = "zero" // async - val result : NameType = "result$async" - val completed : NameType = "completed$async" - val stateMachine : NameType = "stateMachine$async" - val state : NameType = "state$async" - val execContext : NameType = "execContext$async" - val tr : NameType = "tr$async" - val t : NameType = "throwable$async" + val result : NameType = "result$async" + val completed : NameType = "completed$async" + val stateMachine : NameType = "stateMachine$async" + val state : NameType = "state$async" + val execContextTemp : NameType = "execContext0$async" + val execContext : NameType = "execContext$async" + val tr : NameType = "tr$async" + val t : NameType = "throwable$async" + val future : NameType = "future" // quasiquote interpolators: val q: NameType = "q" diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index 0fa233dd6eb8..bd289932ac6c 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -232,6 +232,7 @@ abstract class TreeInfo { ( !tree.isErrorTyped && (isExprSafeToInline(tree) || isWarnableRefTree) && isWarnableSymbol + && !tree.hasAttachment[SuppressPureExpressionWarning.type] ) } diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 675296af6e5c..fb87309621b1 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -64,6 +64,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.TypeParamVarargsAttachment this.KnownDirectSubclassesCalled this.ChangeOwnerAttachment + this.SuppressPureExpressionWarning this.noPrint this.typeDebug // inaccessible: this.posAssigner diff --git a/test/async/.gitignore b/test/async/.gitignore new file mode 100644 index 000000000000..161be5b55fad --- /dev/null +++ b/test/async/.gitignore @@ -0,0 +1,2 @@ +*.log +*.obj/ diff --git a/test/async/run/auto_AfterRefchecksIssue.scala b/test/async/run/auto_AfterRefchecksIssue.scala deleted file mode 100644 index bd8707a86c26..000000000000 --- a/test/async/run/auto_AfterRefchecksIssue.scala +++ /dev/null @@ -1,18 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -trait Factory[T] { - def create: T -} - -sealed trait TimePoint - -class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) { - @autoawait - @async private[TimeLine] val tp: TP = tpInitial.create - @autoawait - @async def timePoint: TP = tp -} - -object Test extends App { test - def test: Unit = () -} diff --git a/test/async/run/auto_Combo.check b/test/async/run/auto_Combo.check deleted file mode 100644 index 54c32ce3574e..000000000000 --- a/test/async/run/auto_Combo.check +++ /dev/null @@ -1 +0,0 @@ -Test.test diff --git a/test/async/run/auto_Combo.scala b/test/async/run/auto_Combo.scala deleted file mode 100644 index eb94c796074a..000000000000 --- a/test/async/run/auto_Combo.scala +++ /dev/null @@ -1,26 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { assert(test == "case 3: blerg3") - @async - def test: Any = { - object Extractor1 { - @autoawait def unapply(a: String) = Some((a + 1, a + 2)) - } - object Extractor2 { - @autoawait def unapply(a: String) = Some(a + 3) - } - @autoawait def id(a: String) = a - - println("Test.test") - val r1 = Predef.identity("blerg") match { - case x if " ".isEmpty => "case 2: " + x - case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y - x match { - case Extractor1(Extractor2(x), y: String) => - case _ => - } - case Extractor2(x) => "case 3: " + x - } - r1 - } -} diff --git a/test/async/run/auto_Extractor.scala b/test/async/run/auto_Extractor.scala deleted file mode 100644 index b53140b78f98..000000000000 --- a/test/async/run/auto_Extractor.scala +++ /dev/null @@ -1,13 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { assert(test) - @async - def test: Boolean = { - object Extractor { - @autoawait def unapply(a: String) = Some((a, a)) - } - "" match { - case Extractor(a, b) if "".isEmpty => a == b - } - } -} diff --git a/test/async/run/auto_GenericTypeBoundaryIssue.check b/test/async/run/auto_GenericTypeBoundaryIssue.check deleted file mode 100644 index c89f5ee868a1..000000000000 --- a/test/async/run/auto_GenericTypeBoundaryIssue.check +++ /dev/null @@ -1 +0,0 @@ -process Bound diff --git a/test/async/run/auto_GenericTypeBoundaryIssue.scala b/test/async/run/auto_GenericTypeBoundaryIssue.scala deleted file mode 100644 index a9823f04beb5..000000000000 --- a/test/async/run/auto_GenericTypeBoundaryIssue.scala +++ /dev/null @@ -1,32 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -trait InstrumentOfValue - -trait Security[T <: InstrumentOfValue] extends InstrumentOfValue - -class Bound extends Security[Bound] - -class Futures extends Security[Futures] - -object TestGenericTypeBoundIssue { - @autoawait - @async def processBound(bound: Bound): Unit = { - println("process Bound") - } - @autoawait - @async def processFutures(futures: Futures): Unit = { - println("process Futures") - } - @autoawait - @async def doStuff(sec: Security[_]): Unit = { - sec match { - case bound: Bound => processBound(bound) - case futures: Futures => processFutures(futures) - case _ => throw new Exception("Unknown Security type: " + sec) - } - } -} - -object Test extends App { test - @async def test: Unit = TestGenericTypeBoundIssue.doStuff(new Bound) -} diff --git a/test/async/run/auto_Guard.scala b/test/async/run/auto_Guard.scala deleted file mode 100644 index cda03bad2174..000000000000 --- a/test/async/run/auto_Guard.scala +++ /dev/null @@ -1,13 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { assert(test == "okay") - @async - def test: Any = { - @autoawait def id[A](a: A) = a - - "" match { - case _ if id(false) => ???; - case _ => "okay" - } - } -} diff --git a/test/async/run/auto_MatchEndIssue.scala b/test/async/run/auto_MatchEndIssue.scala deleted file mode 100644 index 061eebbe7d8b..000000000000 --- a/test/async/run/auto_MatchEndIssue.scala +++ /dev/null @@ -1,22 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -sealed trait Subject - -final class Principal(val name: String) extends Subject - -object Principal { - def unapply(p: Principal): Option[String] = Some(p.name) -} - -object Test extends App { test - @autoawait - @async - def containsPrincipal(search: String, value: Subject): Boolean = value match { - case Principal(name) if name == search => true - case Principal(name) => containsPrincipal(search, value) - case other => false - } - - @async - def test = containsPrincipal("test", new Principal("test")) -} diff --git a/test/async/run/auto_NegativeArraySizeException.scala b/test/async/run/auto_NegativeArraySizeException.scala deleted file mode 100644 index 2332b23c7b10..000000000000 --- a/test/async/run/auto_NegativeArraySizeException.scala +++ /dev/null @@ -1,12 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { test - def foo(foo: Any, bar: Any) = () - @autoawait def getValue = 4.2 - @async def func(f: Any) = { - foo(f match { case _ if "".isEmpty => 2 }, getValue); - } - - @async - def test() = func(4) -} diff --git a/test/async/run/auto_NegativeArraySizeExceptionFine1.scala b/test/async/run/auto_NegativeArraySizeExceptionFine1.scala deleted file mode 100644 index a2814327840b..000000000000 --- a/test/async/run/auto_NegativeArraySizeExceptionFine1.scala +++ /dev/null @@ -1,20 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -case class FixedFoo(foo: Int) - -class Foobar(val foo: Int, val bar: Double) { - @autoawait - @async def getValue = 4.2 - @autoawait - @async def func(f: Any) = { - new Foobar(foo = f match { - case FixedFoo(x) => x - case _ => 2 - }, - bar = getValue) - } -} - -object Test extends App { test - @async def test() = new Foobar(0, 0).func(4) -} diff --git a/test/async/run/auto_NestedMatchExtractor.scala b/test/async/run/auto_NestedMatchExtractor.scala deleted file mode 100644 index 2dfebad175d6..000000000000 --- a/test/async/run/auto_NestedMatchExtractor.scala +++ /dev/null @@ -1,16 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { assert(test) - @async - def test: Boolean = { - object Extractor { - @autoawait def unapply(a: String) = Some((a, a)) - } - "" match { - case _ if "".isEmpty => - "" match { - case Extractor(a, b) => a == b - } - } - } -} diff --git a/test/async/run/auto_ReturnTupleIssue.scala b/test/async/run/auto_ReturnTupleIssue.scala deleted file mode 100644 index 547da1866940..000000000000 --- a/test/async/run/auto_ReturnTupleIssue.scala +++ /dev/null @@ -1,19 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -class TestReturnExprIssue(str: String) { - @autoawait - @async def getTestValue = Some(42) - @autoawait - @async def doStuff: Int = { - val opt: Option[Int] = getTestValue // here we have an async method invoke - opt match { - case Some(li) => li // use the result somehow - case None => - } - 42 // type mismatch; found : AnyVal required: Int - } -} - -object Test extends App { test - @async def test: Unit = new TestReturnExprIssue("").doStuff -} diff --git a/test/async/run/auto_patternAlternative.scala b/test/async/run/auto_patternAlternative.scala deleted file mode 100644 index 1d72f31295f9..000000000000 --- a/test/async/run/auto_patternAlternative.scala +++ /dev/null @@ -1,15 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { test - @async - def test: Any = { - @autoawait def one = 1 - - @async def test = { - Option(true) match { - case null | None => false - case Some(v) => one; v - } - } - } -} diff --git a/test/async/run/auto_patternAlternativeBothAnnotations.scala b/test/async/run/auto_patternAlternativeBothAnnotations.scala deleted file mode 100644 index 0f8e897fba53..000000000000 --- a/test/async/run/auto_patternAlternativeBothAnnotations.scala +++ /dev/null @@ -1,15 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { test - @async - def test: Any = { - @autoawait def func1() = "hello" - - @async def func(a: Option[Boolean]) = a match { - case null | None => func1 + " world" - case _ => "okay" - } - - def test: Any = func(None) - } -} diff --git a/test/async/run/auto_polymorphicMethod.check b/test/async/run/auto_polymorphicMethod.check deleted file mode 100644 index a5d2b3b91803..000000000000 --- a/test/async/run/auto_polymorphicMethod.check +++ /dev/null @@ -1,3 +0,0 @@ -auto_polymorphicMethod.scala:13: warning: unreachable code - case _ if false => ???; - ^ diff --git a/test/async/run/auto_polymorphicMethod.scala b/test/async/run/auto_polymorphicMethod.scala deleted file mode 100644 index e2bd16ec4291..000000000000 --- a/test/async/run/auto_polymorphicMethod.scala +++ /dev/null @@ -1,19 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { assert(test.toString == "(C,C)") - - class C { - override def toString = "C" - } - - @autoawait def foo[A <: C](a: A): A = a - @async - def test1[CC <: C](c: CC): (CC, CC) = { - val x: (CC, CC) = 0 match { - case _ if false => ???; - case _ => (foo(c), foo(c)) - } - x - } - def test(): (C, C) = test1(new C) -} diff --git a/test/async/run/auto_shadowing.check b/test/async/run/auto_shadowing.check deleted file mode 100644 index a78a6c78443c..000000000000 --- a/test/async/run/auto_shadowing.check +++ /dev/null @@ -1,3 +0,0 @@ -auto_shadowing.scala:16: warning: a pure expression does nothing in statement position - case _ => foo; () - ^ diff --git a/test/async/run/auto_shadowing.scala b/test/async/run/auto_shadowing.scala deleted file mode 100644 index e1f3e980957b..000000000000 --- a/test/async/run/auto_shadowing.scala +++ /dev/null @@ -1,22 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { test - - trait Foo - - trait Bar extends Foo - - @autoawait def boundary = "" - @async - def test: Unit = { - (new Bar {}: Any) match { - case foo: Bar => - boundary - 0 match { - case _ => foo; () - } - () - } - () - } -} diff --git a/test/async/run/auto_shadowing0.scala b/test/async/run/auto_shadowing0.scala deleted file mode 100644 index efe346891bf5..000000000000 --- a/test/async/run/auto_shadowing0.scala +++ /dev/null @@ -1,23 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { test - - trait Foo - - trait Bar - - def test: Any = test(new C) - @autoawait def asyncBoundary: String = "" - @async - def test(foo: Foo): Foo = foo match { - case foo: Bar => - val foo2: Foo with Bar = new Foo with Bar {} - asyncBoundary - null match { - case _ => foo2 - } - case other => foo - } - - class C extends Foo with Bar -} diff --git a/test/async/run/auto_shadowing2.scala b/test/async/run/auto_shadowing2.scala deleted file mode 100644 index 39afb3a4fdc1..000000000000 --- a/test/async/run/auto_shadowing2.scala +++ /dev/null @@ -1,27 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -object Test extends App { test - - trait Base; - - trait Foo[T <: Base] { - @autoawait def func: Option[Foo[T]] = None - } - - class Sub extends Base - - trait Bar extends Foo[Sub] - - def test: Any = test(new Bar {}) - @async - def test[T <: Base](foo: Foo[T]): Foo[T] = foo match { - case foo: Bar => - val res = foo.func - res match { - case _ => - } - foo - case other => foo - } - test(new Bar {}) -} diff --git a/test/async/run/auto_shadowingRefinedTypes.scala b/test/async/run/auto_shadowingRefinedTypes.scala deleted file mode 100644 index 11fadca7066d..000000000000 --- a/test/async/run/auto_shadowingRefinedTypes.scala +++ /dev/null @@ -1,27 +0,0 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} - -trait Base - -class Sub extends Base - -trait Foo[T <: Base] { - @autoawait def func: Option[Foo[T]] = None -} - -trait Bar extends Foo[Sub] - -object Test extends App { assert(test) - @async def func[T <: Base](foo: Foo[T]): Foo[T] = foo match { // the whole pattern match will be wrapped with async{ } - case foo: Bar => - val res = foo.func // will be rewritten into: await(foo.func) - res match { - case Some(v) => v // this will report type mismtach - case other => foo - } - case other => foo - } - def test: Boolean = { - val b = new Bar {}; - func(b) == b - } -} diff --git a/test/async/run/concurrent_AfterRefchecksIssue.scala b/test/async/run/concurrent_AfterRefchecksIssue.scala new file mode 100644 index 000000000000..e6116f2047d2 --- /dev/null +++ b/test/async/run/concurrent_AfterRefchecksIssue.scala @@ -0,0 +1,16 @@ +import scala.concurrent._, ExecutionContext.Implicits.global, scala.async._ + +trait Factory[T] { + def create: T +} + +sealed trait TimePoint + +class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) { + private[TimeLine] val tp: Future[TP] = async { tpInitial.create } + def timePoint: Future[TP] = async { await(tp) } +} + +object Test extends App { test + def test: Unit = () +} diff --git a/test/async/run/auto_ArrayIndexOutOfBoundIssue.scala b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala similarity index 52% rename from test/async/run/auto_ArrayIndexOutOfBoundIssue.scala rename to test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala index ccffb315f949..c5a8bac5f893 100644 --- a/test/async/run/auto_ArrayIndexOutOfBoundIssue.scala +++ b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala @@ -1,4 +1,7 @@ -import scala.tools.nsc.transform.async.user.{async, autoawait} +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration sealed trait Result @@ -11,10 +14,10 @@ case object C extends Result object Test extends App { test protected def doStuff(res: Result) = { class C { - @autoawait def needCheck = false + def needCheck = async { false } - @async def m = { - if (needCheck) "NO" + def m = async { + if (await(needCheck)) "NO" else { res match { case A => 1 @@ -23,9 +26,9 @@ object Test extends App { test } } } + new C().m } - @async - def test() = doStuff(B) + def test() = Await.result(doStuff(B), Duration.Inf) } diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.check b/test/async/run/concurrent_GenericTypeBoundaryIssue.check new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.scala b/test/async/run/concurrent_GenericTypeBoundaryIssue.scala new file mode 100644 index 000000000000..08151d924110 --- /dev/null +++ b/test/async/run/concurrent_GenericTypeBoundaryIssue.scala @@ -0,0 +1,33 @@ +import Test.test + +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +trait InstrumentOfValue + +trait Security[T <: InstrumentOfValue] extends InstrumentOfValue + +class Bound extends Security[Bound] + +class Futures extends Security[Futures] + +object Test extends App { test + val out = Console.out + def processBound(bound: Bound): Future[Unit] = async { + out.println("process Bound") + } + def processFutures(futures: Futures): Future[Unit] = async { + out.println("process Futures") + } + def doStuff(sec: Security[_]): Future[Unit] = async { + sec match { + case bound: Bound => processBound(bound) + case futures: Futures => processFutures(futures) + case _ => throw new Exception("Unknown Security type: " + sec) + } + } + + def test: Unit = Await.result(doStuff(new Bound), Duration.Inf) +} diff --git a/test/async/run/concurrent_MatchEndIssue.scala b/test/async/run/concurrent_MatchEndIssue.scala new file mode 100644 index 000000000000..9d4c02ebd27e --- /dev/null +++ b/test/async/run/concurrent_MatchEndIssue.scala @@ -0,0 +1,24 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +sealed trait Subject + +final class Principal(val name: String) extends Subject + +object Principal { + def unapply(p: Principal): Option[String] = Some(p.name) +} + +object Test extends App { test + def containsPrincipal(search: String, value: Subject): Future[Boolean] = async { + value match { + case Principal(name) if name == search => true + case Principal(name) => await(containsPrincipal(search, value)) + case other => false + } + } + + def test = Await.result(containsPrincipal("test", new Principal("test")), Duration.Inf) +} diff --git a/test/async/run/concurrent_NegativeArraySizeException.scala b/test/async/run/concurrent_NegativeArraySizeException.scala new file mode 100644 index 000000000000..40879f697152 --- /dev/null +++ b/test/async/run/concurrent_NegativeArraySizeException.scala @@ -0,0 +1,14 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { test + def foo(foo: Any, bar: Any) = () + def getValue = async {4.2} + def func(f: Any) = async { + foo(f match { case _ if "".isEmpty => 2 }, await(getValue)); + } + + def test() = Await.result(func(4), Duration.Inf) +} diff --git a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala new file mode 100644 index 000000000000..79d3ab12f1b7 --- /dev/null +++ b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala @@ -0,0 +1,21 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +case class FixedFoo(foo: Int) + +class Foobar(val foo: Int, val bar: Double) { + def getValue = async { 4.2 } + def func(f: Any) = async { + new Foobar(foo = f match { + case FixedFoo(x) => x + case _ => 2 + }, + bar = await(getValue)) + } +} + +object Test extends App { test + def test() = Await.result(new Foobar(0, 0).func(4), Duration.Inf) +} diff --git a/test/async/run/concurrent_ReturnTupleIssue.scala b/test/async/run/concurrent_ReturnTupleIssue.scala new file mode 100644 index 000000000000..d61f2cded98d --- /dev/null +++ b/test/async/run/concurrent_ReturnTupleIssue.scala @@ -0,0 +1,20 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +class TestReturnExprIssue(str: String) { + def getTestValue = async(Some(42)) + def doStuff: Future[Int] = async { + val opt: Option[Int] = await(getTestValue) // here we have an async method invoke + opt match { + case Some(li) => li // use the result somehow + case None => + } + 42 // type mismatch; found : AnyVal required: Int + } +} + +object Test extends App { test + def test: Unit = Await.result(new TestReturnExprIssue("").doStuff, Duration.Inf) +} diff --git a/test/async/run/concurrent_fetch.scala b/test/async/run/concurrent_fetch.scala index f62d54d5781d..7b4ebd604400 100644 --- a/test/async/run/concurrent_fetch.scala +++ b/test/async/run/concurrent_fetch.scala @@ -3,17 +3,16 @@ import scala.concurrent.ExecutionContext.Implicits.global import scala.async.{async, await} object Test extends App { - def fetchURL(url: String): Future[String] = Future {println("fetching"); s"fetched $url"} + val out = Console.out + def fetchURL(url: String): Future[String] = Future {out.println("fetching"); s"fetched $url"} val sumLengths: Future[Int] = { - println(s"pre fsm") async { - println(s"huh") val body1 = fetchURL("http://scala-lang.org") val body2 = fetchURL("http://docs.scala-lang.org") await(body1).length + await(body2).length } } - Await.result(async {println(await(sumLengths)) }, duration.Duration.Inf) + Await.result(async {out.println(await(sumLengths)) }, duration.Duration.Inf) } diff --git a/test/async/run/concurrent_patternAlternative.scala b/test/async/run/concurrent_patternAlternative.scala new file mode 100644 index 000000000000..ececaf3c5667 --- /dev/null +++ b/test/async/run/concurrent_patternAlternative.scala @@ -0,0 +1,18 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { test + def test: Any = Await.result({ + def one = async { 1 } + + def test = async { + Option(true) match { + case null | None => false + case Some(v) => await(one); v + } + } + test + }, Duration.Inf) +} diff --git a/test/async/run/concurrent_patternAlternativeBothAnnotations.scala b/test/async/run/concurrent_patternAlternativeBothAnnotations.scala new file mode 100644 index 000000000000..252bb6f31307 --- /dev/null +++ b/test/async/run/concurrent_patternAlternativeBothAnnotations.scala @@ -0,0 +1,14 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { test + def func1() = async { "hello" } + + def func(a: Option[Boolean]) = async {a match { + case null | None => await(func1) + " world" + case _ => "okay" + }} + def test: Any = Await.result(func(None), Duration.Inf) +} diff --git a/test/async/run/concurrent_polymorphicMethod.check b/test/async/run/concurrent_polymorphicMethod.check new file mode 100644 index 000000000000..d0926f1ea991 --- /dev/null +++ b/test/async/run/concurrent_polymorphicMethod.check @@ -0,0 +1,3 @@ +concurrent_polymorphicMethod.scala:16: warning: unreachable code + case _ if false => ???; + ^ diff --git a/test/async/run/concurrent_polymorphicMethod.scala b/test/async/run/concurrent_polymorphicMethod.scala new file mode 100644 index 000000000000..c37075b13282 --- /dev/null +++ b/test/async/run/concurrent_polymorphicMethod.scala @@ -0,0 +1,22 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { assert(test.toString == "(C,C)") + + class C { + override def toString = "C" + } + + def foo[A <: C](a: A) = async {a} + + def test1[CC <: C](c: CC): Future[(CC, CC)] = async { + val x: (CC, CC) = 0 match { + case _ if false => ???; + case _ => (await(foo(c)), await(foo(c))) + } + x + } + def test(): (C, C) = Await.result(test1(new C), Duration.Inf) +} diff --git a/test/async/run/concurrent_shadowing.check b/test/async/run/concurrent_shadowing.check new file mode 100644 index 000000000000..f9715003cd51 --- /dev/null +++ b/test/async/run/concurrent_shadowing.check @@ -0,0 +1,3 @@ +concurrent_shadowing.scala:18: warning: a pure expression does nothing in statement position + case _ => foo; () + ^ diff --git a/test/async/run/concurrent_shadowing.scala b/test/async/run/concurrent_shadowing.scala new file mode 100644 index 000000000000..e45f90732b6e --- /dev/null +++ b/test/async/run/concurrent_shadowing.scala @@ -0,0 +1,24 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { test + + trait Foo + + trait Bar extends Foo + + def boundary = async { "" } + def test: Unit = Await.result(async { + (new Bar {}: Any) match { + case foo: Bar => + await { boundary } + 0 match { + case _ => foo; () + } + () + } + () + }, Duration.Inf) +} diff --git a/test/async/run/concurrent_shadowing0.scala b/test/async/run/concurrent_shadowing0.scala new file mode 100644 index 000000000000..f2161c39be16 --- /dev/null +++ b/test/async/run/concurrent_shadowing0.scala @@ -0,0 +1,27 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { test + + trait Foo + + trait Bar + + def test: Any = Await.result(test(new C), Duration.Inf) + def asyncBoundary: Future[String] = async { "" } + def test(foo: Foo): Future[Foo] = async { + foo match { + case foo: Bar => + val foo2: Foo with Bar = new Foo with Bar {} + await(asyncBoundary) + null match { + case _ => foo2 + } + case other => foo + } + } + + class C extends Foo with Bar +} diff --git a/test/async/run/concurrent_shadowing2.scala b/test/async/run/concurrent_shadowing2.scala new file mode 100644 index 000000000000..72604a9a067d --- /dev/null +++ b/test/async/run/concurrent_shadowing2.scala @@ -0,0 +1,31 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +object Test extends App { test + + trait Base; + + trait Foo[T <: Base] { + def func: Future[Option[Foo[T]]] = async { None } + } + + class Sub extends Base + + trait Bar extends Foo[Sub] + + def test: Any = test(new Bar {}) + def test[T <: Base](foo: Foo[T]): Future[Foo[T]] = async { + foo match { + case foo: Bar => + val res = foo.func + res match { + case _ => + } + foo + case other => foo + } + } + Await.result(test(new Bar {}), Duration.Inf) +} diff --git a/test/async/run/concurrent_shadowingRefinedTypes.scala b/test/async/run/concurrent_shadowingRefinedTypes.scala new file mode 100644 index 000000000000..abac1d806422 --- /dev/null +++ b/test/async/run/concurrent_shadowingRefinedTypes.scala @@ -0,0 +1,32 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration + +trait Base + +class Sub extends Base + +trait Foo[T <: Base] { + def func: Future[Option[Foo[T]]] = async { None } +} + +trait Bar extends Foo[Sub] + +object Test extends App { assert(test) + def func[T <: Base](foo: Foo[T]): Future[Foo[T]] = async { + foo match { // the whole pattern match will be wrapped with async{ } + case foo: Bar => + val res = await(foo.func) // will be rewritten into: await(foo.func) + res match { + case Some(v) => v // this will report type mismtach + case other => foo + } + case other => foo + } + } + def test: Boolean = { + val b = new Bar {}; + Await.result(func(b), Duration.Inf) == b + } +} diff --git a/test/async/run/auto_test0.scala b/test/async/run/concurrent_test0.scala similarity index 56% rename from test/async/run/auto_test0.scala rename to test/async/run/concurrent_test0.scala index edba257e7d80..7cb4a00f0ae3 100644 --- a/test/async/run/auto_test0.scala +++ b/test/async/run/concurrent_test0.scala @@ -11,13 +11,16 @@ */ -import scala.tools.nsc.transform.async.user.{async, autoawait} +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.async._ +import scala.concurrent.duration.Duration object Test extends App { assert(test == "foobar") - @async - def test: String = { - @autoawait def id(a: String) = a + def test: String = Await.result(async { + def id(a: String) = async { a } - id("foo") + id("bar") - } + await(id("foo")) + await(id("bar")) + }, Duration.Inf) } + diff --git a/test/async/run/futures.check b/test/async/run/futures.check new file mode 100644 index 000000000000..e0d860861a0c --- /dev/null +++ b/test/async/run/futures.check @@ -0,0 +1,13 @@ +futures.scala:120: warning: match may not be exhaustive. +It would fail on the following input: Failure(_) + f2 onComplete { case Success(_) => throw new ThrowableTest("dispatcher receive") } + ^ +futures.scala:127: warning: match may not be exhaustive. +It would fail on the following input: Failure(_) + f2 onComplete { case Success(_) => throw new ThrowableTest("current thread receive") } + ^ +futures.scala:172: warning: match may not be exhaustive. +It would fail on the following input: Req((x: T forSome x not in (Int, String))) + def asyncReq[T](req: Req[T]) = req match { + ^ +warning: there were 9 deprecation warnings (since 2.12.0); re-run with -deprecation for details diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala new file mode 100644 index 000000000000..1c635e415db9 --- /dev/null +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -0,0 +1,402 @@ +package scala.tools.nsc +package async + +import java.io.File +import java.nio.file.{Files, Paths} + +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.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 + +class AnnotationDrivenAsync { + @Test + def testBasicScalaConcurrent(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global, scala.async._ + | + |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 testCustomAsync(): Unit = { + val code = """ + |import scala.tools.nsc.async.{autoawait, customAsync} + | + |object Test { + | @customAsync + | def test: Any = { + | val x = reverse("abc") + | val y = reverse(x) + | (x, y) + | } + | @autoawait def reverse(a: String) = a.reverse + |} + | + |""".stripMargin + assertEquals(("cba", "abc"), run(code)) + } + + @Test + def testMixedAsync(): Unit = { + val code = """ + |import scala.tools.nsc.async.{autoawait, customAsync} + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global, scala.async._ + | + |object Test { + | @customAsync + | def test: Any = { + | class C { + | def repeat(s: String, i: Int): Future[String] = async { + | if (i == 0) s + | else await(repeat(s, i - 1)) + s + | } + | } + + | val x = reverse("abc") + | val y = reverse(x) + | val z = Await.result(new C().repeat("-", 5), Duration.Inf) + | (x, y, z) + | } + | @autoawait def reverse(a: String) = a.reverse + |} + | + |""".stripMargin + assertEquals(("cba", "abc", "------"), run(code)) + } + + + @Test + def testExtractor(): Unit = { + val code = """ + |import scala.tools.nsc.async.{autoawait, customAsync} + | + |object Test { + | + | object Extractor1 { + | @autoawait def unapply(a: String) = Some((a + 1, a + 2)) + | } + | object Extractor2 { + | @autoawait def unapply(a: String) = Some(a + 3) + | } + | @customAsync + | def test: Any = { + | @autoawait def id(a: String) = a + | + | println("Test.test") + | val r1 = Predef.identity("blerg") match { + | case x if " ".isEmpty => "case 2: " + x + | case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y + | x match { + | case Extractor1(Extractor2(x), y: String) => + | case _ => + | } + | case Extractor2(x) => "case 3: " + x + | } + | r1 + | } + |} + | + |""".stripMargin + assertEquals("case 3: blerg3", run(code)) + } + + + @Test + def testLocalModule(): Unit = { + val code = """ + |import scala.tools.nsc.async.{autoawait, customAsync} + | + |object Test { + | @customAsync def test: Any = { + | object Foo { + | @autoawait def id(a: String) = a + | } + | (Foo.id("a"), Foo.id("b")) + | } + |} + | + |""".stripMargin + assertEquals(("a", "b"), run(code)) + } + + @Test + def testGuard(): Unit = { + val code = """ + |import scala.tools.nsc.async.{autoawait, customAsync} + | + |object Test extends App { + | @customAsync + | def test: Any = { + | @autoawait def id[A](a: A) = a + | + | "" match { + | case _ if id(false) => ???; + | case _ => "okay" + | } + | } + |} + | + |""".stripMargin + assertEquals("okay", run(code)) + } + + + @Test + def testNestedMatchExtractor(): Unit = { + val code = """ + |import scala.tools.nsc.async.{autoawait, customAsync} + | + |object Test extends App { + | @customAsync def test = { + | object Extractor { + | @autoawait def unapply(a: String) = Some((a, a)) + | } + | "" match { + | case _ if "".isEmpty => + | "" match { + | case Extractor(a, b) => a == b + | } + | } + | } + |} + | + |""".stripMargin + assertEquals(true, run(code)) + } + + // Handy to debug the compiler + @Test @Ignore + def testManualRunPartestUnderJUnit(): Unit = { + val code = new String(Files.readAllBytes(Paths.get("../async/run/concurrent_ArrayIndexOutOfBoundIssue.scala"))) + assertEquals(("a", "b"), run(code)) + } + + private def createTempDir(): File = { + val f = File.createTempFile("output", "") + f.delete() + f.mkdirs() + f + } + + def run(code: String): Any = { + val out = createTempDir() + try { + val reporter = new StoreReporter + val settings = new Settings(println(_)) + settings.outdir.value = out.getAbsolutePath + settings.embeddedDefaults(getClass.getClassLoader) + // settings.debug.value = true + // settings.processArgumentString("-Xprint:all -nowarn") + val isInSBT = !settings.classpath.isSetByUser + if (isInSBT) settings.usejavacp.value = true + val global = new Global(settings, reporter) { + self => + + object late extends { + val global: self.type = self + } with AnnotationDrivenAsyncPlugin + + override protected def loadPlugins(): List[Plugin] = late :: Nil + } + import global._ + + val run = new Run + val source = newSourceFile(code) + run.compileSources(source :: Nil) + Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasWarnings) + Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors) + val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader) + val cls = loader.loadClass("Test") + cls.getMethod("test").invoke(null) match { + case t: scala.concurrent.Future[_] => + scala.concurrent.Await.result(t, Duration.Inf) + case cf: CustomFuture[_] => + cf._block + case value => value + } + } finally { + scala.reflect.io.Path.apply(out).deleteRecursively() + } + } +} + +abstract class AnnotationDrivenAsyncPlugin extends Plugin { + + import global._ + + override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers { + val global: AnnotationDrivenAsyncPlugin.this.global.type = AnnotationDrivenAsyncPlugin.this.global + + lazy val asyncModuleSym = symbolOf[CustomFuture.type] + lazy val awaitSym = symbolOf[CustomFuture.type].info.member(TermName("_await")) + lazy val autoAwaitSym = symbolOf[autoawait] + lazy val customAsyncSym = symbolOf[customAsync] + lazy val CustomFuture_class = symbolOf[CustomFuture.type] + lazy val CustomFuture_successful = CustomFuture_class.companionModule.info.member(TermName("_successful")) + + def newTransformer(unit: CompilationUnit) = new TypingTransformer(unit) { + override def transform(tree: Tree): Tree = { + def wrapAwait = { + localTyper.typedPos(tree.pos) { + Apply(TypeApply(gen.mkAttributedRef(asyncModuleSym.typeOfThis, awaitSym), TypeTree(tree.tpe) :: Nil), gen.mkMethodCall(CustomFuture_successful, tree :: Nil) :: Nil) + } + } + super.transform(tree) match { + case ap@Apply(fun, _) if fun.symbol.hasAnnotation(autoAwaitSym) => + wrapAwait + case sel@Select(_, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodType] || tree.tpe.isInstanceOf[PolyType]) => + wrapAwait + case dd: DefDef if dd.symbol.hasAnnotation(customAsyncSym) => + deriveDefDef(dd) { + rhs => + val wrapped = + q""" + { + val ${nme.execContextTemp} = () + class stateMachine$$async extends _root_.scala.tools.nsc.async.StateMachineBase { + ${q"""def apply(tr: _root_.scala.util.Either[_root_.scala.Throwable, _root_.scala.AnyRef]): _root_.scala.Unit = { + $rhs + () + }""".updateAttachment(ChangeOwnerAttachment(dd.symbol))} + } + 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 + } + """ + + wrapped.updateAttachment(new global.async.FutureSystemAttachment(CustomFutureFutureSystem)) + val tree = + q""" + val temp = ${wrapped} + temp._block + """ + val result = atOwner(dd.symbol) { + localTyper.typedPos(dd.pos) { + tree + } + } + result + } + case x => x + } + } + } + + override def newPhase(prev: Phase): Phase = new StdPhase(prev) { + override def apply(unit: CompilationUnit): Unit = { + newTransformer(unit).transformUnit(unit) + } + } + + override val runsAfter: List[String] = "refchecks" :: "patmat" :: Nil + override val phaseName: String = "postpatmat" + + }) + override val description: String = "postpatmat" + override val name: String = "postpatmat" +} + +// Calls to methods with this annotation are translated to `Async.await(Future.successful())` +// This lets us express and test async boundaries in extractor calls, which one can't do with the async/await macro. +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 tryTypeToResult(tp: Type): Type = tp.baseType(Either_class).typeArgs.headOption.getOrElse(NoType) + + 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] = { + Block(gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) :: Nil, literalUnitExpr) + } + + 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) + } + } +} diff --git a/test/junit/scala/tools/nsc/async/CustomFuture.scala b/test/junit/scala/tools/nsc/async/CustomFuture.scala new file mode 100644 index 000000000000..9911916dbe1b --- /dev/null +++ b/test/junit/scala/tools/nsc/async/CustomFuture.scala @@ -0,0 +1,39 @@ +/* + * 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.async + +import scala.concurrent.duration.Duration + +// Method names prefixed with `_` to better test flush out hard-coded names in the +// async implementation. +final class CustomFuture[T](val wrapped: scala.concurrent.Future[T]) { + def _onComplete[U](f: Either[Throwable, T] => U): Unit = { + wrapped.onComplete((tr => f(tr.toEither)))(scala.concurrent.ExecutionContext.Implicits.global) + } + def _isCompleted: Boolean = wrapped.isCompleted + def _getCompleted: Either[Throwable, T] = if (wrapped.isCompleted) wrapped.value.get.toEither else null + def _block: T = scala.concurrent.Await.result(wrapped, Duration.Inf) +} +object CustomFuture { + def _apply[T](f: => T): CustomFuture[T] = new CustomFuture[T](scala.concurrent.Future(f)(scala.concurrent.ExecutionContext.Implicits.global)) + val _unit = new CustomFuture[Unit](scala.concurrent.Future.unit) + def _successful[T](t: T): CustomFuture[T] = new CustomFuture[T](scala.concurrent.Future.successful(t)) + def _await[T](f: CustomFuture[T]): T = ??? +} + +final class CustomPromise[T](wrapped: scala.concurrent.Promise[T]) { + def _complete(either: Either[Throwable, T]): Unit = { + wrapped.complete(either.toTry) + } + def _future: CustomFuture[T] = new CustomFuture[T](wrapped.future) +} From 008137d9f5a9bb9b328e6da9defb1e33ef099952 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Wed, 12 Feb 2020 12:56:06 +0100 Subject: [PATCH 06/94] Move to scala.async.Async for compatibility with existing macros --- src/library/scala/{ => async}/Async.scala | 8 ++++---- src/reflect/scala/reflect/internal/Definitions.scala | 2 +- test/async/run/concurrent_AfterRefchecksIssue.scala | 2 +- test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala | 2 +- test/async/run/concurrent_GenericTypeBoundaryIssue.scala | 2 +- test/async/run/concurrent_MatchEndIssue.scala | 2 +- .../async/run/concurrent_NegativeArraySizeException.scala | 2 +- .../run/concurrent_NegativeArraySizeExceptionFine1.scala | 2 +- test/async/run/concurrent_ReturnTupleIssue.scala | 2 +- test/async/run/concurrent_fetch.scala | 2 +- test/async/run/concurrent_patternAlternative.scala | 2 +- .../concurrent_patternAlternativeBothAnnotations.scala | 2 +- test/async/run/concurrent_polymorphicMethod.scala | 2 +- test/async/run/concurrent_shadowing.scala | 2 +- test/async/run/concurrent_shadowing0.scala | 2 +- test/async/run/concurrent_shadowing2.scala | 2 +- test/async/run/concurrent_shadowingRefinedTypes.scala | 2 +- test/async/run/concurrent_test0.scala | 2 +- .../scala/tools/nsc/async/AnnotationDrivenAsync.scala | 5 +++-- 19 files changed, 24 insertions(+), 23 deletions(-) rename src/library/scala/{ => async}/Async.scala (96%) diff --git a/src/library/scala/Async.scala b/src/library/scala/async/Async.scala similarity index 96% rename from src/library/scala/Async.scala rename to src/library/scala/async/Async.scala index 26ea3e68adb5..99a86c7afeb0 100644 --- a/src/library/scala/Async.scala +++ b/src/library/scala/async/Async.scala @@ -10,11 +10,11 @@ * additional information regarding copyright ownership. */ -package scala +package scala.async -import scala.language.experimental.macros -import scala.concurrent.{Future, ExecutionContext} 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]]. @@ -45,7 +45,7 @@ import scala.annotation.compileTimeOnly * } * }}} */ -object async { +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. diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 0acc73683f5f..e876ba276f18 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1635,7 +1635,7 @@ trait Definitions extends api.StandardDefinitions { lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.runtime.java8") - lazy val AsyncModule = getModuleIfDefined("scala.async") + 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)) } diff --git a/test/async/run/concurrent_AfterRefchecksIssue.scala b/test/async/run/concurrent_AfterRefchecksIssue.scala index e6116f2047d2..3f15651fe9c3 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._ +import scala.concurrent._, ExecutionContext.Implicits.global, scala.async.Async._ trait Factory[T] { def create: T diff --git a/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala b/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala index c5a8bac5f893..bc0399b389be 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration sealed trait Result diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.scala b/test/async/run/concurrent_GenericTypeBoundaryIssue.scala index 08151d924110..cfc77b3ba346 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration trait InstrumentOfValue diff --git a/test/async/run/concurrent_MatchEndIssue.scala b/test/async/run/concurrent_MatchEndIssue.scala index 9d4c02ebd27e..591805e33599 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration sealed trait Subject diff --git a/test/async/run/concurrent_NegativeArraySizeException.scala b/test/async/run/concurrent_NegativeArraySizeException.scala index 40879f697152..d81cada34989 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala b/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala index 79d3ab12f1b7..20e924653fa2 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration case class FixedFoo(foo: Int) diff --git a/test/async/run/concurrent_ReturnTupleIssue.scala b/test/async/run/concurrent_ReturnTupleIssue.scala index d61f2cded98d..e6051c1dd176 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration class TestReturnExprIssue(str: String) { diff --git a/test/async/run/concurrent_fetch.scala b/test/async/run/concurrent_fetch.scala index 7b4ebd604400..10d85084225a 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, await} +import scala.async.Async.{async, await} object Test extends App { val out = Console.out diff --git a/test/async/run/concurrent_patternAlternative.scala b/test/async/run/concurrent_patternAlternative.scala index ececaf3c5667..759eadc49c26 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_patternAlternativeBothAnnotations.scala b/test/async/run/concurrent_patternAlternativeBothAnnotations.scala index 252bb6f31307..a7dd9b0038ef 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_polymorphicMethod.scala b/test/async/run/concurrent_polymorphicMethod.scala index c37075b13282..1a8eac38d127 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { assert(test.toString == "(C,C)") diff --git a/test/async/run/concurrent_shadowing.scala b/test/async/run/concurrent_shadowing.scala index e45f90732b6e..7c874c4e6eae 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_shadowing0.scala b/test/async/run/concurrent_shadowing0.scala index f2161c39be16..b9ee0ff4b1ee 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_shadowing2.scala b/test/async/run/concurrent_shadowing2.scala index 72604a9a067d..764b8b872fab 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { test diff --git a/test/async/run/concurrent_shadowingRefinedTypes.scala b/test/async/run/concurrent_shadowingRefinedTypes.scala index abac1d806422..123937e8d444 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration trait Base diff --git a/test/async/run/concurrent_test0.scala b/test/async/run/concurrent_test0.scala index 7cb4a00f0ae3..0b4fe8eadbc7 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._ +import scala.async.Async._ import scala.concurrent.duration.Duration object Test extends App { assert(test == "foobar") diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 1c635e415db9..b46da2279baf 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -23,7 +23,8 @@ class AnnotationDrivenAsync { def testBasicScalaConcurrent(): Unit = { val code = """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global, scala.async._ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.async.Async.{async, await} | |object Test { | def test: Future[Int] = async { await(f(1)) + await(f(2)) } @@ -56,7 +57,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._ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global, scala.async.Async._ | |object Test { | @customAsync From 5dd94954e004e7d5d760d1d3984cad23ce8b1865 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Wed, 12 Feb 2020 13:50:28 +0100 Subject: [PATCH 07/94] Add JUnit support to partest --- build.sbt | 2 +- .../scala/tools/partest/JUnitTest.scala | 41 +++++++++++++++++++ test/files/run/junitForwarders/C_1.scala | 4 +- 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src/partest-extras/scala/tools/partest/JUnitTest.scala diff --git a/build.sbt b/build.sbt index f10889a7a85f..1964588ddf4d 100644 --- a/build.sbt +++ b/build.sbt @@ -964,7 +964,7 @@ lazy val partestExtras = Project("partest-extras", file(".") / "src" / "partest- .settings( name := "scala-partest-extras", description := "Scala Compiler Testing Tool (compiler-specific extras)", - libraryDependencies += partestDep, + libraryDependencies ++= Seq(partestDep, junitDep), unmanagedSourceDirectories in Compile := List(baseDirectory.value) ) diff --git a/src/partest-extras/scala/tools/partest/JUnitTest.scala b/src/partest-extras/scala/tools/partest/JUnitTest.scala new file mode 100644 index 000000000000..dba97a023e18 --- /dev/null +++ b/src/partest-extras/scala/tools/partest/JUnitTest.scala @@ -0,0 +1,41 @@ +/* + * 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 + +import java.io.{PrintWriter, StringWriter} + +import org.junit.internal.TextListener +import org.junit.runner.JUnitCore +import org.junit.runner.notification.{Failure, RunListener} + +import scala.collection.JavaConverters._ + +abstract class JUnitTest(classes: Class[_]*) extends App { + + val junit = new JUnitCore + junit.addListener(new RunListener { + override def testFailure(failure: Failure): Unit = { + println(failure) + val ex = failure.getException + if(ex != null) { + val sw = new StringWriter() + val out = new PrintWriter(sw) + ex.printStackTrace(out) + out.flush() + val lines = sw.getBuffer.toString.split('\n') + lines.iterator.takeWhile(s => !s.contains("at org.junit.runners.")).foreach(println) + } + } + }) + junit.run(classes: _*) +} diff --git a/test/files/run/junitForwarders/C_1.scala b/test/files/run/junitForwarders/C_1.scala index 0361ef42ef9f..9fa7830a97bc 100644 --- a/test/files/run/junitForwarders/C_1.scala +++ b/test/files/run/junitForwarders/C_1.scala @@ -9,7 +9,7 @@ object Test extends App { val s = c.getDeclaredMethods.sortBy(_.getName).map(m => s"${m.getName} - ${m.getDeclaredAnnotations.mkString(", ")}").mkString(";") assert(s == e, s"found: $s\nexpected: $e") } - check(classOf[C], "foo - @org.junit.Test()") + check(classOf[C], "foo - @org.junit.Test(timeout=0, expected=class org.junit.Test$None)") // scala/scala-dev#213, scala/scala#5570: `foo$` should not have the @Test annotation - check(classOf[T], "$init$ - ;foo - @org.junit.Test();foo$ - ") + check(classOf[T], "$init$ - ;foo - @org.junit.Test(timeout=0, expected=class org.junit.Test$None);foo$ - ") } From 08fa083405d2c4f34a7e54c7f3e050d8f7141675 Mon Sep 17 00:00:00 2001 From: Stefan Zeiger Date: Wed, 12 Feb 2020 20:09:41 +0100 Subject: [PATCH 08/94] Add tests from scala-async MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - most regular scala.async.Async tests pass - AsyncId converted to ScalaConcurrentAsync - non-standard tests not properly integrated yet - actual test failure in `run/futures.scala` - neg/NakedAwait still missing (alternative first)   - remove late.scala (converted to AnnotationDrivenAsync)   - Remove uncheckedBounds.scala (the problem no longer exists post-erasure) --- test/async/run/anf.scala | 449 +++++++++++++++++++ test/async/run/await0.scala | 72 +++ test/async/run/block0.scala | 56 +++ test/async/run/block1.scala | 40 ++ test/async/run/exceptions.scala | 57 +++ test/async/run/futures.scala | 607 +++++++++++++++++++++++++ test/async/run/hygiene.scala | 83 ++++ test/async/run/ifelse0.scala | 55 +++ test/async/run/ifelse0_while.scala | 118 +++++ test/async/run/ifelse1.scala | 203 +++++++++ test/async/run/ifelse2.scala | 46 ++ test/async/run/ifelse3.scala | 49 +++ test/async/run/ifelse4.scala | 62 +++ test/async/run/late.scala | 612 ++++++++++++++++++++++++++ test/async/run/lazyval.scala | 27 ++ test/async/run/live.scala | 299 +++++++++++++ test/async/run/localclasses.scala | 34 ++ test/async/run/match0.scala | 146 ++++++ test/async/run/nesteddef.scala | 97 ++++ test/async/run/noawait.scala | 35 ++ test/async/run/stackoverflow.scala | 15 + test/async/run/syncOptimization.scala | 22 + test/async/run/toughtype.scala.scala | 353 +++++++++++++++ test/async/run/uncheckedBounds.scala | 47 ++ test/async/run/warning.scala | 105 +++++ 25 files changed, 3689 insertions(+) create mode 100644 test/async/run/anf.scala create mode 100644 test/async/run/await0.scala create mode 100644 test/async/run/block0.scala create mode 100644 test/async/run/block1.scala create mode 100644 test/async/run/exceptions.scala create mode 100644 test/async/run/futures.scala create mode 100644 test/async/run/hygiene.scala create mode 100644 test/async/run/ifelse0.scala create mode 100644 test/async/run/ifelse0_while.scala create mode 100644 test/async/run/ifelse1.scala create mode 100644 test/async/run/ifelse2.scala create mode 100644 test/async/run/ifelse3.scala create mode 100644 test/async/run/ifelse4.scala create mode 100644 test/async/run/late.scala create mode 100644 test/async/run/lazyval.scala create mode 100644 test/async/run/live.scala create mode 100644 test/async/run/localclasses.scala create mode 100644 test/async/run/match0.scala create mode 100644 test/async/run/nesteddef.scala create mode 100644 test/async/run/noawait.scala create mode 100644 test/async/run/stackoverflow.scala create mode 100644 test/async/run/syncOptimization.scala create mode 100644 test/async/run/toughtype.scala.scala create mode 100644 test/async/run/uncheckedBounds.scala create mode 100644 test/async/run/warning.scala diff --git a/test/async/run/anf.scala b/test/async/run/anf.scala new file mode 100644 index 000000000000..6cde888d4362 --- /dev/null +++ b/test/async/run/anf.scala @@ -0,0 +1,449 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.anf.AnfTransformSpec]) + +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 org.junit.Test + import scala.async.internal.AsyncId + + + class AnfTestClass { + + import ExecutionContext.Implicits.global + + def base(x: Int): Future[Int] = Future { + x + 2 + } + + def m(y: Int): Future[Int] = async { + val blerg = base(y) + await(blerg) + } + + def m2(y: Int): Future[Int] = async { + val f = base(y) + val f2 = base(y + 1) + await(f) + await(f2) + } + + def m3(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y > 0) { + z = await(f) + 2 + } else { + z = await(f) - 2 + } + z + } + + def m4(y: Int): Future[Int] = async { + val f = base(y) + val z = if (y > 0) { + await(f) + 2 + } else { + await(f) - 2 + } + z + 1 + } + + def futureUnitIfElse(y: Int): Future[Unit] = async { + val f = base(y) + if (y > 0) { + State.result = await(f) + 2 + } else { + State.result = await(f) - 2 + } + } + } + + object State { + @volatile var result: Int = 0 + } + + class AnfTransformSpec { + + @Test + def `simple ANF transform`(): Unit = { + val o = new AnfTestClass + val fut = o.m(10) + val res = Await.result(fut, 2 seconds) + res mustBe (12) + } + + @Test + def `simple ANF transform 2`(): Unit = { + val o = new AnfTestClass + val fut = o.m2(10) + val res = Await.result(fut, 2 seconds) + res mustBe (25) + } + + @Test + def `simple ANF transform 3`(): Unit = { + val o = new AnfTestClass + val fut = o.m3(10) + val res = Await.result(fut, 2 seconds) + res mustBe (14) + } + + @Test + def `ANF transform of assigning the result of an if-else`(): Unit = { + val o = new AnfTestClass + val fut = o.m4(10) + val res = Await.result(fut, 2 seconds) + res mustBe (15) + } + + @Test + def `Unit-typed if-else in tail position`(): Unit = { + val o = new AnfTestClass + val fut = o.futureUnitIfElse(10) + Await.result(fut, 2 seconds) + State.result mustBe (14) + } + + @Test + def `inlining block does not produce duplicate definition`(): Unit = { + AsyncId.async { + val f = 12 + val x = AsyncId.await(f) + + { + type X = Int + val x: X = 42 + println(x) + } + type X = Int + x: X + } + } + + @Test + def `inlining block in tail position does not produce duplicate definition`(): Unit = { + AsyncId.async { + val f = 12 + val x = AsyncId.await(f) + + { + val x = 42 + x + } + } mustBe (42) + } + + @Test + def `match as expression 1`(): Unit = { + import ExecutionContext.Implicits.global + val result = AsyncId.async { + val x = "" match { + case _ => AsyncId.await(1) + 1 + } + x + } + result mustBe (2) + } + + @Test + def `match as expression 2`(): Unit = { + import ExecutionContext.Implicits.global + val result = AsyncId.async { + val x = "" match { + case "" if false => AsyncId.await(1) + 1 + case _ => 2 + AsyncId.await(1) + } + val y = x + "" match { + case _ => AsyncId.await(y) + 100 + } + } + result mustBe (103) + } + + @Test + def nestedAwaitAsBareExpression(): Unit = { + import ExecutionContext.Implicits.global + import AsyncId.{async, await} + val result = async { + await(await("").isEmpty) + } + result mustBe (true) + } + + @Test + def nestedAwaitInBlock(): Unit = { + import ExecutionContext.Implicits.global + import AsyncId.{async, await} + val result = async { + () + await(await("").isEmpty) + } + result mustBe (true) + } + + @Test + def nestedAwaitInIf(): Unit = { + import ExecutionContext.Implicits.global + import AsyncId.{async, await} + val result = async { + if ("".isEmpty) + await(await("").isEmpty) + else 0 + } + result mustBe (true) + } + + @Test + def byNameExpressionsArentLifted(): Unit = { + import AsyncId.{async, await} + def foo(ignored: => Any, b: Int) = b + val result = async { + foo(???, await(1)) + } + result mustBe (1) + } + + @Test + def evaluationOrderRespected(): Unit = { + import AsyncId.{async, await} + def foo(a: Int, b: Int) = (a, b) + val result = async { + var i = 0 + def next() = { + i += 1 + i + } + foo(next(), await(next())) + } + result mustBe ((1, 2)) + } + + @Test + def awaitInNonPrimaryParamSection1(): Unit = { + import AsyncId.{async, await} + def foo(a0: Int)(b0: Int) = s"a0 = $a0, b0 = $b0" + val res = async { + var i = 0 + def get = {i += 1; i} + foo(get)(await(get)) + } + res mustBe "a0 = 1, b0 = 2" + } + + @Test + def awaitInNonPrimaryParamSection2(): Unit = { + import AsyncId.{async, await} + def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" + val res = async { + var i = 0 + def get = async {i += 1; i} + foo[Int](await(get))(await(get) :: await(async(Nil)) : _*) + } + res mustBe "a0 = 1, b0 = 2" + } + + @Test + def awaitInNonPrimaryParamSectionWithLazy1(): Unit = { + import AsyncId.{async, await} + def foo[T](a: => Int)(b: Int) = b + val res = async { + def get = async {0} + foo[Int](???)(await(get)) + } + res mustBe 0 + } + + @Test + def awaitInNonPrimaryParamSectionWithLazy2(): Unit = { + import AsyncId.{async, await} + def foo[T](a: Int)(b: => Int) = a + val res = async { + def get = async {0} + foo[Int](await(get))(???) + } + res mustBe 0 + } + + @Test + def awaitWithLazy(): Unit = { + import AsyncId.{async, await} + def foo[T](a: Int, b: => Int) = a + val res = async { + def get = async {0} + foo[Int](await(get), ???) + } + res mustBe 0 + } + + @Test + def awaitOkInReciever(): Unit = { + import AsyncId.{async, await} + class Foo { def bar(a: Int)(b: Int) = a + b } + async { + await(async(new Foo)).bar(1)(2) + } + } + + @Test + def namedArgumentsRespectEvaluationOrder(): Unit = { + import AsyncId.{async, await} + def foo(a: Int, b: Int) = (a, b) + val result = async { + var i = 0 + def next() = { + i += 1 + i + } + foo(b = next(), a = await(next())) + } + result mustBe ((2, 1)) + } + + @Test + def namedAndDefaultArgumentsRespectEvaluationOrder(): Unit = { + import AsyncId.{async, await} + var i = 0 + def next() = { + i += 1 + i + } + def foo(a: Int = next(), b: Int = next()) = (a, b) + async { + foo(b = await(next())) + } mustBe ((2, 1)) + i = 0 + async { + foo(a = await(next())) + } mustBe ((1, 2)) + } + + @Test + def repeatedParams1(): Unit = { + import AsyncId.{async, await} + var i = 0 + def foo(a: Int, b: Int*) = b.toList + def id(i: Int) = i + async { + foo(await(0), id(1), id(2), id(3), await(4)) + } mustBe (List(1, 2, 3, 4)) + } + + @Test + def repeatedParams2(): Unit = { + import AsyncId.{async, await} + var i = 0 + def foo(a: Int, b: Int*) = b.toList + def id(i: Int) = i + async { + foo(await(0), List(id(1), id(2), id(3)): _*) + } mustBe (List(1, 2, 3)) + } + + @Test + def awaitInThrow(): Unit = { + import _root_.scala.async.internal.AsyncId.{async, await} + intercept[Exception]( + async { + throw new Exception("msg: " + await(0)) + } + ).getMessage mustBe "msg: 0" + } + + @Test + def awaitInTyped(): Unit = { + import _root_.scala.async.internal.AsyncId.{async, await} + async { + (("msg: " + await(0)): String).toString + } mustBe "msg: 0" + } + + + @Test + def awaitInAssign(): Unit = { + import _root_.scala.async.internal.AsyncId.{async, await} + async { + var x = 0 + x = await(1) + x + } mustBe 1 + } + + @Test + def caseBodyMustBeTypedAsUnit(): Unit = { + import _root_.scala.async.internal.AsyncId.{async, await} + val Up = 1 + val Down = 2 + val sign = async { + await(1) match { + case Up => 1.0 + case Down => -1.0 + } + } + sign mustBe 1.0 + } + + @Test + def awaitInImplicitApply(): Unit = { + val tb = mkToolbox(s"-cp ${toolboxClasspath}") + val tree = tb.typeCheck(tb.parse { + """ + | import language.implicitConversions + | import _root_.scala.async.internal.AsyncId.{async, await} + | implicit def view(a: Int): String = "" + | async { + | await(0).length + | } + """.stripMargin + }) + val applyImplicitView = tree.collect { case x if x.getClass.getName.endsWith("ApplyImplicitView") => x } + println(applyImplicitView) + applyImplicitView.map(_.toString) mustStartWith List("view(") + } + + @Test + def nothingTypedIf(): Unit = { + import scala.async.internal.AsyncId.{async, await} + val result = util.Try(async { + if (true) { + val n = await(1) + if (n < 2) { + throw new RuntimeException("case a") + } + else { + throw new RuntimeException("case b") + } + } + else { + "case c" + } + }) + + assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a") + } + + @Test + def nothingTypedMatch(): Unit = { + import scala.async.internal.AsyncId.{async, await} + val result = util.Try(async { + 0 match { + case _ if "".isEmpty => + val n = await(1) + n match { + case _ if n < 2 => + throw new RuntimeException("case a") + case _ => + throw new RuntimeException("case b") + } + case _ => + "case c" + } + }) + + assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a") + } + } + +} diff --git a/test/async/run/await0.scala b/test/async/run/await0.scala new file mode 100644 index 000000000000..b2a9d1767379 --- /dev/null +++ b/test/async/run/await0.scala @@ -0,0 +1,72 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.await0.Await0Spec]) + +package scala.async.run.await0 { + + /** + * Copyright (C) 2012-2014 Lightbend Inc. + */ + + import language.{reflectiveCalls, postfixOps} + + import scala.concurrent.{Future, ExecutionContext, Await} + import scala.concurrent.duration._ + import scala.async.Async.{async, await} + import org.junit.Test + import org.junit.Assert._ + + class Await0Class { + + import ExecutionContext.Implicits.global + + def m1(x: Double): Future[Double] = Future { + x + 2.0 + } + + def m2(x: Float): Future[Float] = Future { + x + 2.0f + } + + def m3(x: Char): Future[Char] = Future { + (x.toInt + 2).toChar + } + + def m4(x: Short): Future[Short] = Future { + (x + 2).toShort + } + + def m5(x: Byte): Future[Byte] = Future { + (x + 2).toByte + } + + def m0(y: Int): Future[Double] = async { + val f1 = m1(y.toDouble) + val x1: Double = await(f1) + + val f2 = m2(y.toFloat) + val x2: Float = await(f2) + + val f3 = m3(y.toChar) + val x3: Char = await(f3) + + val f4 = m4(y.toShort) + val x4: Short = await(f4) + + val f5 = m5(y.toByte) + val x5: Byte = await(f5) + + x1 + x2 + 2.0 + } + } + + class Await0Spec { + + @Test + def `An async method support a simple await`(): Unit = { + val o = new Await0Class + val fut = o.m0(10) + val res = Await.result(fut, 10 seconds) + assertEquals(26.0, res, 0) + } + } + +} diff --git a/test/async/run/block0.scala b/test/async/run/block0.scala new file mode 100644 index 000000000000..49ec5889b7a3 --- /dev/null +++ b/test/async/run/block0.scala @@ -0,0 +1,56 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.block0.AsyncSpec]) + +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 org.junit.Test + import org.junit.Assert._ + + + class Test1Class { + + import ExecutionContext.Implicits.global + + def m1(x: Int): Future[Int] = Future { + x + 2 + } + + def m2(y: Int): Future[Int] = async { + val f = m1(y) + val x = await(f) + x + 2 + } + + def m3(y: Int): Future[Int] = async { + val f1 = m1(y) + val x1 = await(f1) + val f2 = m1(y + 2) + val x2 = await(f2) + x1 + x2 + } + } + + + class AsyncSpec { + + @Test + def `simple await`(): Unit = { + val o = new Test1Class + val fut = o.m2(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + @Test + def `several awaits in sequence`(): Unit = { + val o = new Test1Class + val fut = o.m3(10) + val res = Await.result(fut, 4 seconds) + assertEquals(26, res) + } + } + +} diff --git a/test/async/run/block1.scala b/test/async/run/block1.scala new file mode 100644 index 000000000000..ba46a9c83ab8 --- /dev/null +++ b/test/async/run/block1.scala @@ -0,0 +1,40 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.block1.Block1Spec]) + +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 org.junit.Test + import org.junit.Assert._ + + + class Test1Class { + + import ExecutionContext.Implicits.global + + def m1(x: Int): Future[Int] = Future { + x + 2 + } + + def m4(y: Int): Future[Int] = async { + val f1 = m1(y) + val f2 = m1(y + 2) + val x1 = await(f1) + val x2 = await(f2) + x1 + x2 + } + } + + class Block1Spec { + + @Test def `support a simple await`(): Unit = { + val o = new Test1Class + val fut = o.m4(10) + val res = Await.result(fut, 2 seconds) + assertEquals(26, res) + } + } + +} diff --git a/test/async/run/exceptions.scala b/test/async/run/exceptions.scala new file mode 100644 index 000000000000..d129445e62bd --- /dev/null +++ b/test/async/run/exceptions.scala @@ -0,0 +1,57 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.exceptions.ExceptionsSpec]) + +package scala.async.run.exceptions { + + import scala.async.Async.{async, await} + + import scala.concurrent.{Future, ExecutionContext, Await} + import ExecutionContext.Implicits._ + import scala.concurrent.duration._ + import scala.reflect.ClassTag + import scala.tools.partest.TestUtil.intercept + + import org.junit.Test + + class ExceptionsSpec { + + @Test + def `uncaught exception within async`(): Unit = { + val fut = async { throw new Exception("problem") } + intercept[Exception] { Await.result(fut, 2.seconds) } + } + + @Test + def `uncaught exception within async after await`(): Unit = { + val base = Future { "five!".length } + val fut = async { + val len = await(base) + throw new Exception(s"illegal length: $len") + } + intercept[Exception] { Await.result(fut, 2.seconds) } + } + + @Test + def `await failing future within async`(): Unit = { + val base = Future[Int] { throw new Exception("problem") } + val fut = async { + val x = await(base) + x * 2 + } + intercept[Exception] { Await.result(fut, 2.seconds) } + } + + @Test + def `await failing future within async after await`(): Unit = { + val base = Future[Any] { "five!".length } + val fut = async { + val a = await(base.mapTo[Int]) // result: 5 + val b = await((Future { (a * 2).toString }).mapTo[Int]) // result: ClassCastException + val c = await(Future { (7 * 2).toString }) // result: "14" + b + "-" + c + } + intercept[ClassCastException] { Await.result(fut, 2.seconds) } + } + + } + +} diff --git a/test/async/run/futures.scala b/test/async/run/futures.scala new file mode 100644 index 000000000000..29220b5c1708 --- /dev/null +++ b/test/async/run/futures.scala @@ -0,0 +1,607 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.futures.FutureSpec]) + +package scala.async { + + import concurrent.{CanAwait, Awaitable} + import concurrent.duration.Duration + import java.util.concurrent.{TimeoutException, CountDownLatch, TimeUnit} + + object TestLatch { + val DefaultTimeout = Duration(5, TimeUnit.SECONDS) + + def apply(count: Int = 1) = new TestLatch(count) + } + + + class TestLatch(count: Int = 1) extends Awaitable[Unit] { + private var latch = new CountDownLatch(count) + + def countDown() = latch.countDown() + + def isOpen: Boolean = latch.getCount == 0 + + def open() = while (!isOpen) countDown() + + def reset() = latch = new CountDownLatch(count) + + @throws(classOf[TimeoutException]) + def ready(atMost: Duration)(implicit permit: CanAwait) = { + val opened = latch.await(atMost.toNanos, TimeUnit.NANOSECONDS) + if (!opened) throw new TimeoutException(s"Timeout of ${(atMost.toString)}.") + this + } + + @throws(classOf[Exception]) + def result(atMost: Duration)(implicit permit: CanAwait): Unit = { + ready(atMost) + } + } +} + +package scala.async.run.futures { + + import java.util.concurrent.ConcurrentHashMap + + import scala.language.postfixOps + import scala.concurrent._ + import scala.concurrent.duration._ + import scala.concurrent.duration.Duration.Inf + import scala.collection._ + import scala.runtime.NonLocalReturnControl + import scala.util.{Try,Success,Failure} + import scala.reflect.{ClassTag, classTag} + + import scala.async.TestLatch + import scala.async.Async.{async, await} + + import org.junit.Test + + class FutureSpec { + + // scala.tools.partest.TestUtil.intercept is not good enough for us + def intercept[T <: Throwable : ClassTag](body: => Any): T = { + try { + body + throw new Exception(s"Exception of type ${classTag[T]} was not thrown") + } catch { + case t: Throwable => + if (!classTag[T].runtimeClass.isAssignableFrom(t.getClass)) throw t + else t.asInstanceOf[T] + } + } + + //TODO use normal Assert calls in the tests + implicit class objectops(obj: Any) { + def mustBe(other: Any) = assert(obj == other, obj + " is not " + other) + + def mustEqual(other: Any) = mustBe(other) + } + + /* some utils */ + + def testAsync(s: String)(implicit ec: ExecutionContext): Future[String] = s match { + case "Hello" => Future { "World" } + case "Failure" => Future.failed(new RuntimeException("Expected exception; to test fault-tolerance")) + case "NoReply" => Promise[String]().future + } + + val defaultTimeout = 5 seconds + + /* future specification */ + + @Test def `A future with custom ExecutionContext should handle Throwables`(): Unit = { + val ms = new ConcurrentHashMap[Throwable, Unit] + implicit val ec = scala.concurrent.ExecutionContext.fromExecutor(new java.util.concurrent.ForkJoinPool(), { + t => + ms.put(t, ()) + }) + + class ThrowableTest(m: String) extends Throwable(m) + + val f1 = Future[Any] { + throw new ThrowableTest("test") + } + + intercept[ThrowableTest] { + Await.result(f1, defaultTimeout) + } + + val latch = new TestLatch + val f2 = Future { + Await.ready(latch, 5 seconds) + "success" + } + val f3 = async { + val s = await(f2) + s.toUpperCase + } + + f2 foreach { _ => throw new ThrowableTest("dispatcher foreach") } + f2 onComplete { case Success(_) => throw new ThrowableTest("dispatcher receive") } + + latch.open() + + Await.result(f2, defaultTimeout) mustBe ("success") + + f2 foreach { _ => throw new ThrowableTest("current thread foreach") } + f2 onComplete { case Success(_) => throw new ThrowableTest("current thread receive") } + + Await.result(f3, defaultTimeout) mustBe ("SUCCESS") + + val waiting = Future { + Thread.sleep(1000) + } + Await.ready(waiting, 2000 millis) + + ms.size mustBe (4) + } + + import ExecutionContext.Implicits._ + + @Test def `A future with global ExecutionContext should compose with for-comprehensions`(): Unit = { + import scala.reflect.ClassTag + + def asyncInt(x: Int) = Future { (x * 2).toString } + val future0 = Future[Any] { + "five!".length + } + + val future1 = async { + val a = await(future0.mapTo[Int]) // returns 5 + val b = await(asyncInt(a)) // returns "10" + val c = await(asyncInt(7)) // returns "14" + b + "-" + c + } + + val future2 = async { + val a = await(future0.mapTo[Int]) + val b = await((Future { (a * 2).toString }).mapTo[Int]) + val c = await(Future { (7 * 2).toString }) + b + "-" + c + } + + Await.result(future1, defaultTimeout) mustBe ("10-14") + //assert(checkType(future1, manifest[String])) + intercept[ClassCastException] { Await.result(future2, defaultTimeout) } + } + + //TODO this is not yet supported by Async + @Test def `support pattern matching within a for-comprehension`(): Unit = { + case class Req[T](req: T) + case class Res[T](res: T) + def asyncReq[T](req: Req[T]) = req match { + case Req(s: String) => Future { Res(s.length) } + case Req(i: Int) => Future { Res((i * 2).toString) } + } + + val future1 = for { + Res(a: Int) <- asyncReq(Req("Hello")) + Res(b: String) <- asyncReq(Req(a)) + Res(c: String) <- asyncReq(Req(7)) + } yield b + "-" + c + + val future2 = for { + Res(a: Int) <- asyncReq(Req("Hello")) + Res(b: Int) <- asyncReq(Req(a)) + Res(c: Int) <- asyncReq(Req(7)) + } yield b + "-" + c + + Await.result(future1, defaultTimeout) mustBe ("10-14") + intercept[NoSuchElementException] { Await.result(future2, defaultTimeout) } + } + + @Test def mini(): Unit = { + val future4 = async { + await(Future.successful(0)).toString + } + Await.result(future4, defaultTimeout) + } + + @Test def `recover from exceptions`(): Unit = { + val future1 = Future(5) + val future2 = async { await(future1) / 0 } + val future3 = async { await(future2).toString } + + val future1Recovered = future1 recover { + case e: ArithmeticException => 0 + } + val future4 = async { await(future1Recovered).toString } + + val future2Recovered = future2 recover { + case e: ArithmeticException => 0 + } + val future5 = async { await(future2Recovered).toString } + + val future2Recovered2 = future2 recover { + case e: MatchError => 0 + } + val future6 = async { await(future2Recovered2).toString } + + val future7 = future3 recover { + case e: ArithmeticException => "You got ERROR" + } + + val future8 = testAsync("Failure") + val future9 = testAsync("Failure") recover { + case e: RuntimeException => "FAIL!" + } + val future10 = testAsync("Hello") recover { + case e: RuntimeException => "FAIL!" + } + val future11 = testAsync("Failure") recover { + case _ => "Oops!" + } + + Await.result(future1, defaultTimeout) mustBe (5) + intercept[ArithmeticException] { Await.result(future2, defaultTimeout) } + intercept[ArithmeticException] { Await.result(future3, defaultTimeout) } + Await.result(future4, defaultTimeout) mustBe ("5") + Await.result(future5, defaultTimeout) mustBe ("0") + intercept[ArithmeticException] { Await.result(future6, defaultTimeout) } + Await.result(future7, defaultTimeout) mustBe ("You got ERROR") + intercept[RuntimeException] { Await.result(future8, defaultTimeout) } + Await.result(future9, defaultTimeout) mustBe ("FAIL!") + Await.result(future10, defaultTimeout) mustBe ("World") + Await.result(future11, defaultTimeout) mustBe ("Oops!") + } + + @Test def `recoverWith from exceptions`(): Unit = { + val o = new IllegalStateException("original") + val r = new IllegalStateException("recovered") + + intercept[IllegalStateException] { + val failed = Future.failed[String](o) recoverWith { + case _ if false == true => Future.successful("yay!") + } + Await.result(failed, defaultTimeout) + } mustBe (o) + + val recovered = Future.failed[String](o) recoverWith { + case _ => Future.successful("yay!") + } + Await.result(recovered, defaultTimeout) mustBe ("yay!") + + intercept[IllegalStateException] { + val refailed = Future.failed[String](o) recoverWith { + case _ => Future.failed[String](r) + } + Await.result(refailed, defaultTimeout) + } mustBe (r) + } + + @Test def `andThen like a boss`(): Unit = { + val q = new java.util.concurrent.LinkedBlockingQueue[Int] + for (i <- 1 to 1000) { + val chained = Future { + q.add(1); 3 + } andThen { + case _ => q.add(2) + } andThen { + case Success(0) => q.add(Int.MaxValue) + } andThen { + case _ => q.add(3); + } + Await.result(chained, defaultTimeout) mustBe (3) + q.poll() mustBe (1) + q.poll() mustBe (2) + q.poll() mustBe (3) + q.clear() + } + } + + @Test def `firstCompletedOf`(): Unit = { + def futures = Vector.fill[Future[Int]](10) { + Promise[Int]().future + } :+ Future.successful[Int](5) + + Await.result(Future.firstCompletedOf(futures), defaultTimeout) mustBe (5) + Await.result(Future.firstCompletedOf(futures.iterator), defaultTimeout) mustBe (5) + } + + @Test def `find`(): Unit = { + val futures = for (i <- 1 to 10) yield Future { + i + } + + val result = Future.find[Int](futures)(_ == 3) + Await.result(result, defaultTimeout) mustBe (Some(3)) + + val notFound = Future.find[Int](futures)(_ == 11) + Await.result(notFound, defaultTimeout) mustBe (None) + } + + @Test def `zip`(): Unit = { + val timeout = 10000 millis + val f = new IllegalStateException("test") + intercept[IllegalStateException] { + val failed = Future.failed[String](f) zip Future.successful("foo") + Await.result(failed, timeout) + } mustBe (f) + + intercept[IllegalStateException] { + val failed = Future.successful("foo") zip Future.failed[String](f) + Await.result(failed, timeout) + } mustBe (f) + + intercept[IllegalStateException] { + val failed = Future.failed[String](f) zip Future.failed[String](f) + Await.result(failed, timeout) + } mustBe (f) + + val successful = Future.successful("foo") zip Future.successful("foo") + Await.result(successful, timeout) mustBe (("foo", "foo")) + } + + @Test def `fold`(): Unit = { + val timeout = 10000 millis + def async(add: Int, wait: Int) = Future { + Thread.sleep(wait) + add + } + + val futures = (0 to 9) map { + idx => async(idx, idx * 20) + } + // TODO: change to `foldLeft` after support for 2.11 is dropped + val folded = Future.fold(futures)(0)(_ + _) + Await.result(folded, timeout) mustBe (45) + + val futuresit = (0 to 9) map { + idx => async(idx, idx * 20) + } + // TODO: change to `foldLeft` after support for 2.11 is dropped + val foldedit = Future.fold(futures)(0)(_ + _) + Await.result(foldedit, timeout) mustBe (45) + } + + @Test def `fold by composing`(): Unit = { + val timeout = 10000 millis + def async(add: Int, wait: Int) = Future { + Thread.sleep(wait) + add + } + def futures = (0 to 9) map { + idx => async(idx, idx * 20) + } + val folded = futures.foldLeft(Future(0)) { + case (fr, fa) => for (r <- fr; a <- fa) yield (r + a) + } + Await.result(folded, timeout) mustBe (45) + } + + @Test def `fold with an exception`(): Unit = { + val timeout = 10000 millis + def async(add: Int, wait: Int) = Future { + Thread.sleep(wait) + if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected") + add + } + def futures = (0 to 9) map { + idx => async(idx, idx * 10) + } + // TODO: change to `foldLeft` after support for 2.11 is dropped + val folded = Future.fold(futures)(0)(_ + _) + intercept[IllegalArgumentException] { + Await.result(folded, timeout) + }.getMessage mustBe ("shouldFoldResultsWithException: expected") + } + + @Test def `fold mutable zeroes safely`(): Unit = { + import scala.collection.mutable.ArrayBuffer + def test(testNumber: Int): Unit = { + val fs = (0 to 1000) map (i => Future(i)) + // TODO: change to `foldLeft` after support for 2.11 is dropped + val f = Future.fold(fs)(ArrayBuffer.empty[AnyRef]) { + case (l, i) if i % 2 == 0 => l += i.asInstanceOf[AnyRef] + case (l, _) => l + } + val result = Await.result(f.mapTo[ArrayBuffer[Int]], 10000 millis).sum + + assert(result == 250500) + } + + (1 to 100) foreach test //Make sure it tries to provoke the problem + } + + @Test def `return zero value if folding empty list`(): Unit = { + // TODO: change to `foldLeft` after support for 2.11 is dropped + val zero = Future.fold(List[Future[Int]]())(0)(_ + _) + Await.result(zero, defaultTimeout) mustBe (0) + } + + @Test def `shouldReduceResults`(): Unit = { + def async(idx: Int) = Future { + Thread.sleep(idx * 20) + idx + } + val timeout = 10000 millis + + val futures = (0 to 9) map { async } + // TODO: change to `reduceLeft` after support for 2.11 is dropped + val reduced = Future.reduce(futures)(_ + _) + Await.result(reduced, timeout) mustBe (45) + + val futuresit = (0 to 9) map { async } + // TODO: change to `reduceLeft` after support for 2.11 is dropped + val reducedit = Future.reduce(futuresit)(_ + _) + Await.result(reducedit, timeout) mustBe (45) + } + + @Test def `shouldReduceResultsWithException`(): Unit = { + def async(add: Int, wait: Int) = Future { + Thread.sleep(wait) + if (add == 6) throw new IllegalArgumentException("shouldFoldResultsWithException: expected") + else add + } + val timeout = 10000 millis + def futures = (1 to 10) map { + idx => async(idx, idx * 10) + } + // TODO: change to `reduceLeft` after support for 2.11 is dropped + val failed = Future.reduce(futures)(_ + _) + intercept[IllegalArgumentException] { + Await.result(failed, timeout) + }.getMessage mustBe ("shouldFoldResultsWithException: expected") + } + + @Test def `shouldReduceThrowNSEEOnEmptyInput`(): Unit = { + intercept[java.util.NoSuchElementException] { + // TODO: change to `reduceLeft` after support for 2.11 is dropped + val emptyreduced = Future.reduce(List[Future[Int]]())(_ + _) + Await.result(emptyreduced, defaultTimeout) + } + } + + @Test def `shouldTraverseFutures`(): Unit = { + object counter { + var count = -1 + def incAndGet() = counter.synchronized { + count += 2 + count + } + } + + val oddFutures = List.fill(100)(Future { counter.incAndGet() }).iterator + val traversed = Future.sequence(oddFutures) + Await.result(traversed, defaultTimeout).sum mustBe (10000) + + val list = (1 to 100).toList + val traversedList = Future.traverse(list)(x => Future(x * 2 - 1)) + Await.result(traversedList, defaultTimeout).sum mustBe (10000) + + val iterator = (1 to 100).toList.iterator + val traversedIterator = Future.traverse(iterator)(x => Future(x * 2 - 1)) + Await.result(traversedIterator, defaultTimeout).sum mustBe (10000) + } + + @Test def `shouldBlockUntilResult`(): Unit = { + val latch = new TestLatch + + val f = Future { + Await.ready(latch, 5 seconds) + 5 + } + val f2 = Future { + val res = Await.result(f, Inf) + res + 9 + } + + intercept[TimeoutException] { + Await.ready(f2, 100 millis) + } + + latch.open() + + Await.result(f2, defaultTimeout) mustBe (14) + + val f3 = Future { + Thread.sleep(100) + 5 + } + + intercept[TimeoutException] { + Await.ready(f3, 0 millis) + } + } + + @Test def `run callbacks async`(): Unit = { + val latch = Vector.fill(10)(new TestLatch) + + val f1 = Future { + latch(0).open() + Await.ready(latch(1), TestLatch.DefaultTimeout) + "Hello" + } + val f2 = async { + val s = await(f1) + latch(2).open() + Await.ready(latch(3), TestLatch.DefaultTimeout) + s.length + } + for (_ <- f2) latch(4).open() + + Await.ready(latch(0), TestLatch.DefaultTimeout) + + f1.isCompleted mustBe (false) + f2.isCompleted mustBe (false) + + latch(1).open() + Await.ready(latch(2), TestLatch.DefaultTimeout) + + f1.isCompleted mustBe (true) + f2.isCompleted mustBe (false) + + val f3 = async { + val s = await(f1) + latch(5).open() + Await.ready(latch(6), TestLatch.DefaultTimeout) + s.length * 2 + } + for (_ <- f3) latch(3).open() + + Await.ready(latch(5), TestLatch.DefaultTimeout) + + f3.isCompleted mustBe (false) + + latch(6).open() + Await.ready(latch(4), TestLatch.DefaultTimeout) + + f2.isCompleted mustBe (true) + f3.isCompleted mustBe (true) + + val p1 = Promise[String]() + val f4 = async { + val s = await(p1.future) + latch(7).open() + Await.ready(latch(8), TestLatch.DefaultTimeout) + s.length + } + for (_ <- f4) latch(9).open() + + p1.future.isCompleted mustBe (false) + f4.isCompleted mustBe (false) + + p1 complete Success("Hello") + + Await.ready(latch(7), TestLatch.DefaultTimeout) + + p1.future.isCompleted mustBe (true) + f4.isCompleted mustBe (false) + + latch(8).open() + Await.ready(latch(9), TestLatch.DefaultTimeout) + + Await.ready(f4, defaultTimeout).isCompleted mustBe (true) + } + + @Test def `should not deadlock with nested await (ticket 1313)`(): Unit = { + val simple = async { + await { Future { } } + val unit = Future(()) + val umap = unit map { _ => () } + Await.result(umap, Inf) + } + Await.ready(simple, Inf).isCompleted mustBe (true) + + val l1, l2 = new TestLatch + val complex = async { + await{ Future { } } + blocking { + val nested = Future(()) + for (_ <- nested) l1.open() + Await.ready(l1, TestLatch.DefaultTimeout) // make sure nested is completed + for (_ <- nested) l2.open() + Await.ready(l2, TestLatch.DefaultTimeout) + } + } + Await.ready(complex, defaultTimeout).isCompleted mustBe (true) + } + + @Test def `should not throw when Await.ready`(): Unit = { + val expected = try Success(5 / 0) catch { case a: ArithmeticException => Failure(a) } + val f = async { await(Future(5)) / 0 } + Await.ready(f, defaultTimeout).value.get.toString mustBe expected.toString + } + } + +} diff --git a/test/async/run/hygiene.scala b/test/async/run/hygiene.scala new file mode 100644 index 000000000000..942e50357fc9 --- /dev/null +++ b/test/async/run/hygiene.scala @@ -0,0 +1,83 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.hygiene.HygieneSpec]) + +package scala.async.run.hygiene { + + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + class HygieneSpec { + + import AsyncId.{async, await} + + @Test + def `is hygenic`(): Unit = { + val state = 23 + val result: Any = "result" + def resume(): Any = "resume" + val res = async { + val f1 = state + 2 + val x = await(f1) + val y = await(result) + val z = await(resume()) + (x, y, z) + } + assertEquals((25, "result", "resume"), res) + } + + @Test + def `external var as result of await`(): Unit = { + var ext = 0 + async { + ext = await(12) + } + assertEquals(12, ext) + } + + @Test + def `external var as result of await 2`(): Unit = { + var ext = 0 + val inp = 10 + async { + if (inp > 0) + ext = await(12) + else + ext = await(10) + } + assertEquals(12, ext) + } + + @Test + def `external var as result of await 3`(): Unit = { + var ext = 0 + val inp = 10 + async { + val x = if (inp > 0) + await(12) + else + await(10) + ext = x + await(2) + } + assertEquals(14, ext) + } + + @Test + def `is hygenic nested`(): Unit = { + val state = 23 + val result: Any = "result" + def resume(): Any = "resume" + import AsyncId.{await, async} + val res = async { + val f1 = async { state + 2 } + val x = await(f1) + val y = await(async { result }) + val z = await(async(await(async { resume() }))) + (x, y, z) + } + assertEquals(25, res._1) + assertEquals("result", res._2) + assertEquals("resume", res._3) + } + } + +} diff --git a/test/async/run/ifelse0.scala b/test/async/run/ifelse0.scala new file mode 100644 index 000000000000..a9cafcfe2fe4 --- /dev/null +++ b/test/async/run/ifelse0.scala @@ -0,0 +1,55 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse0.IfElseSpec]) + +package scala.async.run.ifelse0 { + + import language.{reflectiveCalls, postfixOps} + import scala.concurrent.{Future, ExecutionContext, Await} + import scala.concurrent.duration._ + import scala.async.Async.{async, await} + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + + class TestIfElseClass { + + import ExecutionContext.Implicits.global + + def m1(x: Int): Future[Int] = Future { + x + 2 + } + + def m2(y: Int): Future[Int] = async { + val f = m1(y) + var z = 0 + if (y > 0) { + val x1 = await(f) + z = x1 + 2 + } else { + val x2 = await(f) + z = x2 - 2 + } + z + } + } + + + class IfElseSpec { + + @Test def `support await in a simple if-else expression`(): Unit = { + val o = new TestIfElseClass + val fut = o.m2(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + @Test def `await in condition`(): Unit = { + import AsyncId.{async, await} + val result = async { + if ({await(true); await(true)}) await(1) else ??? + } + assertEquals(1, result) + } + } + +} diff --git a/test/async/run/ifelse0_while.scala b/test/async/run/ifelse0_while.scala new file mode 100644 index 000000000000..54bbc6f4ca23 --- /dev/null +++ b/test/async/run/ifelse0_while.scala @@ -0,0 +1,118 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse0.WhileSpec]) + +package scala.async.run.ifelse0 { + + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + class WhileSpec { + + @Test + def whiling1(): Unit = { + import AsyncId._ + + val result = async { + var xxx: Int = 0 + var y = 0 + while (xxx < 3) { + y = await(xxx) + xxx = xxx + 1 + } + y + } + assertEquals(2, result) + } + + @Test + def whiling2(): Unit = { + import AsyncId._ + + val result = async { + var xxx: Int = 0 + var y = 0 + while (false) { + y = await(xxx) + xxx = xxx + 1 + } + y + } + assertEquals(0, result) + } + + @Test + def nestedWhile(): Unit = { + import AsyncId._ + + val result = async { + var sum = 0 + var i = 0 + while (i < 5) { + var j = 0 + while (j < 5) { + sum += await(i) * await(j) + j += 1 + } + i += 1 + } + sum + } + assertEquals(100, result) + } + + @Test + def whileExpr(): Unit = { + import AsyncId._ + + val result = async { + var cond = true + while (cond) { + cond = false + await { 22 } + } + } + assertEquals((), result) + } + + @Test def doWhile(): Unit = { + import AsyncId._ + val result = async { + var b = 0 + var x = "" + await(do { + x += "1" + x += await("2") + x += "3" + b += await(1) + } while (b < 2)) + await(x) + } + assertEquals("123123", result) + } + + @Test def whileAwaitCondition(): Unit = { + import AsyncId._ + val result = async { + var b = true + while(await(b)) { + b = false + } + await(b) + } + assertEquals(false, result) + } + + @Test def doWhileAwaitCondition(): Unit = { + import AsyncId._ + val result = async { + var b = true + do { + b = false + } while(await(b)) + b + } + assertEquals(false, result) + } + } + +} diff --git a/test/async/run/ifelse1.scala b/test/async/run/ifelse1.scala new file mode 100644 index 000000000000..9424804d1beb --- /dev/null +++ b/test/async/run/ifelse1.scala @@ -0,0 +1,203 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse1.IfElse1Spec]) + +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 org.junit.Test + import org.junit.Assert._ + + + class TestIfElse1Class { + + import ExecutionContext.Implicits.global + + def base(x: Int): Future[Int] = Future { + x + 2 + } + + def m1(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y > 0) { + if (y > 100) + 5 + else { + val x1 = await(f) + z = x1 + 2 + } + } else { + val x2 = await(f) + z = x2 - 2 + } + z + } + + def m2(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y > 0) { + if (y < 100) { + val x1 = await(f) + z = x1 + 2 + } + else + 5 + } else { + val x2 = await(f) + z = x2 - 2 + } + z + } + + def m3(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y < 0) { + val x2 = await(f) + z = x2 - 2 + } else { + if (y > 100) + 5 + else { + val x1 = await(f) + z = x1 + 2 + } + } + z + } + + def m4(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y < 0) { + val x2 = await(f) + z = x2 - 2 + } else { + if (y < 100) { + val x1 = await(f) + z = x1 + 2 + } else + 5 + } + z + } + + def pred: Future[Boolean] = async(true) + + def m5: Future[Boolean] = async { + if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(if(await(pred)) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false) + await(pred) + else + false + } + } + + class IfElse1Spec { + + @Test + def `await in a nested if-else expression`(): Unit = { + val o = new TestIfElse1Class + val fut = o.m1(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + @Test + def `await in a nested if-else expression 2`(): Unit = { + val o = new TestIfElse1Class + val fut = o.m2(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + + @Test + def `await in a nested if-else expression 3`(): Unit = { + val o = new TestIfElse1Class + val fut = o.m3(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + + @Test + def `await in a nested if-else expression 4`(): Unit = { + val o = new TestIfElse1Class + val fut = o.m4(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + @Test + def `await in deeply-nested if-else conditions`(): Unit = { + val o = new TestIfElse1Class + val fut = o.m5 + val res = Await.result(fut, 2 seconds) + assertEquals(true, res) + } + } + +} diff --git a/test/async/run/ifelse2.scala b/test/async/run/ifelse2.scala new file mode 100644 index 000000000000..4c8f57c265f2 --- /dev/null +++ b/test/async/run/ifelse2.scala @@ -0,0 +1,46 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse2.IfElse2Spec]) + +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 org.junit.Test + import org.junit.Assert._ + + + class TestIfElse2Class { + + import ExecutionContext.Implicits.global + + def base(x: Int): Future[Int] = Future { + x + 2 + } + + def m(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y > 0) { + val x = await(f) + z = x + 2 + } else { + val x = await(f) + z = x - 2 + } + z + } + } + + class IfElse2Spec { + + @Test + def `variables of the same name in different blocks`(): Unit = { + val o = new TestIfElse2Class + val fut = o.m(10) + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + } + +} diff --git a/test/async/run/ifelse3.scala b/test/async/run/ifelse3.scala new file mode 100644 index 000000000000..aa053eac4cdf --- /dev/null +++ b/test/async/run/ifelse3.scala @@ -0,0 +1,49 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse3.IfElse3Spec]) + +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 org.junit.Test + import org.junit.Assert._ + + + class TestIfElse3Class { + + import ExecutionContext.Implicits.global + + def base(x: Int): Future[Int] = Future { + x + 2 + } + + def m(y: Int): Future[Int] = async { + val f = base(y) + var z = 0 + if (y > 0) { + val x1 = await(f) + var w = x1 + 2 + z = w + 2 + } else { + val x2 = await(f) + var w = x2 + 2 + z = w - 2 + } + z + } + } + + + class IfElse3Spec { + + @Test + def `variables of the same name in different blocks`(): Unit = { + val o = new TestIfElse3Class + val fut = o.m(10) + val res = Await.result(fut, 2 seconds) + assertEquals(16, res) + } + } + +} diff --git a/test/async/run/ifelse4.scala b/test/async/run/ifelse4.scala new file mode 100644 index 000000000000..9d0c1a52fdc8 --- /dev/null +++ b/test/async/run/ifelse4.scala @@ -0,0 +1,62 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse4.IfElse4Spec]) + +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 org.junit.Test + import org.junit.Assert._ + + + class TestIfElse4Class { + + import ExecutionContext.Implicits.global + + class F[A] + class S[A](val id: String) + trait P + + case class K(f: F[_]) + + def result[A](f: F[A]) = async { + new S[A with P]("foo") + } + + def run(k: K) = async { + val res = await(result(k.f)) + // these triggered a crash with mismatched existential skolems + // found : S#10272[_$1#10308 with String#137] where type _$1#10308 + // required: S#10272[_$1#10311 with String#137] forSome { type _$1#10311 } + + // This variation of the crash could be avoided by fixing the over-eager + // generation of states in `If` nodes, which was caused by a bug in label + // detection code. + if(true) { + identity(res) + } + + // This variation remained after the aforementioned fix, however. + // It was fixed by manually typing the `Assign(liftedField, rhs)` AST, + // which is how we avoid these problems through the rest of the ANF transform. + if(true) { + identity(res) + await(result(k.f)) + } + res + } + } + + class IfElse4Spec { + + @Test + def `await result with complex type containing skolem`(): Unit = { + val o = new TestIfElse4Class + val fut = o.run(o.K(null)) + val res = Await.result(fut, 2 seconds) + assertEquals("foo", res.id) + } + } + +} diff --git a/test/async/run/late.scala b/test/async/run/late.scala new file mode 100644 index 000000000000..51dbdb282a13 --- /dev/null +++ b/test/async/run/late.scala @@ -0,0 +1,612 @@ +/* + * 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.run.late + +import java.io.File + +import junit.framework.Assert.assertEquals +import org.junit.{Assert, Ignore, Test} + +import scala.annotation.StaticAnnotation +import scala.annotation.meta.{field, getter} +import scala.async.internal.AsyncId +import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader +import scala.tools.nsc._ +import scala.tools.nsc.plugins.{Plugin, PluginComponent} +import scala.tools.nsc.reporters.StoreReporter +import scala.tools.nsc.transform.TypingTransformers + +// Tests for customized use of the async transform from a compiler plugin, which +// calls it from a new phase that runs after patmat. +class LateExpansion { + + @Test def testRewrittenApply(): Unit = { + val result = wrapAndRun( + """ + | object O { + | case class Foo(a: Any) + | } + | @autoawait def id(a: String) = a + | O.Foo + | id("foo") + id("bar") + | O.Foo(1) + | """.stripMargin) + assertEquals("Foo(1)", result.toString) + } + + @Ignore("Need to use adjustType more pervasively in AsyncTransform, but that exposes bugs in {Type, ... }Symbol's cache invalidation") + @Test def testIsInstanceOfType(): Unit = { + val result = wrapAndRun( + """ + | class Outer + | @autoawait def id(a: String) = a + | val o = new Outer + | id("foo") + id("bar") + | ("": Object).isInstanceOf[o.type] + | """.stripMargin) + assertEquals(false, result) + } + + @Test def testIsInstanceOfTerm(): Unit = { + val result = wrapAndRun( + """ + | class Outer + | @autoawait def id(a: String) = a + | val o = new Outer + | id("foo") + id("bar") + | o.isInstanceOf[Outer] + | """.stripMargin) + assertEquals(true, result) + } + + @Test def testArrayLocalModule(): Unit = { + val result = wrapAndRun( + """ + | class Outer + | @autoawait def id(a: String) = a + | val O = "" + | id("foo") + id("bar") + | new Array[O.type](0) + | """.stripMargin) + assertEquals(classOf[Array[String]], result.getClass) + } + + @Test def test0(): Unit = { + val result = wrapAndRun( + """ + | @autoawait def id(a: String) = a + | id("foo") + id("bar") + | """.stripMargin) + assertEquals("foobar", result) + } + + @Test def testGuard(): Unit = { + val result = wrapAndRun( + """ + | @autoawait def id[A](a: A) = a + | "" match { case _ if id(false) => ???; case _ => "okay" } + | """.stripMargin) + assertEquals("okay", result) + } + + @Test def testExtractor(): Unit = { + val result = wrapAndRun( + """ + | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) } + | "" match { case Extractor(a, b) if "".isEmpty => a == b } + | """.stripMargin) + assertEquals(true, result) + } + + @Test def testNestedMatchExtractor(): Unit = { + val result = wrapAndRun( + """ + | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) } + | "" match { + | case _ if "".isEmpty => + | "" match { case Extractor(a, b) => a == b } + | } + | """.stripMargin) + assertEquals(true, result) + } + + @Test def testCombo(): Unit = { + val result = wrapAndRun( + """ + | object Extractor1 { @autoawait def unapply(a: String) = Some((a + 1, a + 2)) } + | object Extractor2 { @autoawait def unapply(a: String) = Some(a + 3) } + | @autoawait def id(a: String) = a + | println("Test.test") + | val r1 = Predef.identity("blerg") match { + | case x if " ".isEmpty => "case 2: " + x + | case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y + | x match { + | case Extractor1(Extractor2(x), y: String) => + | case _ => + | } + | case Extractor2(x) => "case 3: " + x + | } + | r1 + | """.stripMargin) + assertEquals("case 3: blerg3", result) + } + + @Test def polymorphicMethod(): Unit = { + val result = run( + """ + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | class C { override def toString = "C" } + | @autoawait def foo[A <: C](a: A): A = a + | @lateasync + | def test1[CC <: C](c: CC): (CC, CC) = { + | val x: (CC, CC) = 0 match { case _ if false => ???; case _ => (foo(c), foo(c)) } + | x + | } + | def test(): (C, C) = test1(new C) + |} + | """.stripMargin) + assertEquals("(C,C)", result.toString) + } + + @Test def shadowing(): Unit = { + val result = run( + """ + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | trait Foo + | trait Bar extends Foo + | @autoawait def boundary = "" + | @lateasync + | def test: Unit = { + | (new Bar {}: Any) match { + | case foo: Bar => + | boundary + | 0 match { + | case _ => foo; () + | } + | () + | } + | () + | } + |} + | """.stripMargin) + } + + @Test def shadowing0(): Unit = { + val result = run( + """ + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | trait Foo + | trait Bar + | def test: Any = test(new C) + | @autoawait def asyncBoundary: String = "" + | @lateasync + | def test(foo: Foo): Foo = foo match { + | case foo: Bar => + | val foo2: Foo with Bar = new Foo with Bar {} + | asyncBoundary + | null match { + | case _ => foo2 + | } + | case other => foo + | } + | class C extends Foo with Bar + |} + | """.stripMargin) + } + + @Test def shadowing2(): Unit = { + val result = run( + """ + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | trait Base; trait Foo[T <: Base] { @autoawait def func: Option[Foo[T]] = None } + | class Sub extends Base + | trait Bar extends Foo[Sub] + | def test: Any = test(new Bar {}) + | @lateasync + | def test[T <: Base](foo: Foo[T]): Foo[T] = foo match { + | case foo: Bar => + | val res = foo.func + | res match { + | case _ => + | } + | foo + | case other => foo + | } + | test(new Bar {}) + |} + | """.stripMargin) + } + + @Test def patternAlternative(): Unit = { + val result = wrapAndRun( + """ + | @autoawait def one = 1 + | + | @lateasync def test = { + | Option(true) match { + | case null | None => false + | case Some(v) => one; v + | } + | } + | """.stripMargin) + } + + @Test def patternAlternativeBothAnnotations(): Unit = { + val result = wrapAndRun( + """ + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | @autoawait def func1() = "hello" + | @lateasync def func(a: Option[Boolean]) = a match { + | case null | None => func1 + " world" + | case _ => "okay" + | } + | def test: Any = func(None) + |} + | """.stripMargin) + } + + @Test def shadowingRefinedTypes(): Unit = { + val result = run( + s""" + |import scala.async.run.late.{autoawait,lateasync} + |trait Base + |class Sub extends Base + |trait Foo[T <: Base] { + | @autoawait def func: Option[Foo[T]] = None + |} + |trait Bar extends Foo[Sub] + |object Test { + | @lateasync def func[T <: Base](foo: Foo[T]): Foo[T] = foo match { // the whole pattern match will be wrapped with async{ } + | case foo: Bar => + | val res = foo.func // will be rewritten into: await(foo.func) + | res match { + | case Some(v) => v // this will report type mismtach + | case other => foo + | } + | case other => foo + | } + | def test: Any = { val b = new Bar{}; func(b) == b } + |}""".stripMargin) + assertEquals(true, result) + } + + @Test def testMatchEndIssue(): Unit = { + val result = run( + """ + |import scala.async.run.late.{autoawait,lateasync} + |sealed trait Subject + |final class Principal(val name: String) extends Subject + |object Principal { + | def unapply(p: Principal): Option[String] = Some(p.name) + |} + |object Test { + | @autoawait @lateasync + | def containsPrincipal(search: String, value: Subject): Boolean = value match { + | case Principal(name) if name == search => true + | case Principal(name) => containsPrincipal(search, value) + | case other => false + | } + | + | @lateasync + | def test = containsPrincipal("test", new Principal("test")) + |} + | """.stripMargin) + } + + @Test def testGenericTypeBoundaryIssue(): Unit = { + val result = run( + """ + + import scala.async.run.late.{autoawait,lateasync} + trait InstrumentOfValue + trait Security[T <: InstrumentOfValue] extends InstrumentOfValue + class Bound extends Security[Bound] + class Futures extends Security[Futures] + object TestGenericTypeBoundIssue { + @autoawait @lateasync def processBound(bound: Bound): Unit = { println("process Bound") } + @autoawait @lateasync def processFutures(futures: Futures): Unit = { println("process Futures") } + @autoawait @lateasync def doStuff(sec: Security[_]): Unit = { + sec match { + case bound: Bound => processBound(bound) + case futures: Futures => processFutures(futures) + case _ => throw new Exception("Unknown Security type: " + sec) + } + } + } + object Test { @lateasync def test: Unit = TestGenericTypeBoundIssue.doStuff(new Bound) } + """.stripMargin) + } + + @Test def testReturnTupleIssue(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + class TestReturnExprIssue(str: String) { + @autoawait @lateasync def getTestValue = Some(42) + @autoawait @lateasync def doStuff: Int = { + val opt: Option[Int] = getTestValue // here we have an async method invoke + opt match { + case Some(li) => li // use the result somehow + case None => + } + 42 // type mismatch; found : AnyVal required: Int + } + } + object Test { @lateasync def test: Unit = new TestReturnExprIssue("").doStuff } + """.stripMargin) + } + + + @Test def testAfterRefchecksIssue(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + trait Factory[T] { def create: T } + sealed trait TimePoint + class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) { + @autoawait @lateasync private[TimeLine] val tp: TP = tpInitial.create + @autoawait @lateasync def timePoint: TP = tp + } + object Test { + def test: Unit = () + } + """) + } + + @Test def testArrayIndexOutOfBoundIssue(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + + sealed trait Result + case object A extends Result + case object B extends Result + case object C extends Result + + object Test { + protected def doStuff(res: Result) = { + class C { + @autoawait def needCheck = false + + @lateasync def m = { + if (needCheck) "NO" + else { + res match { + case A => 1 + case _ => 2 + } + } + } + } + } + + + @lateasync + def test() = doStuff(B) + } + """) + } + + def wrapAndRun(code: String): Any = { + run( + s""" + |import scala.async.run.late.{autoawait,lateasync} + |object Test { + | @lateasync + | def test: Any = { + | $code + | } + |} + | """.stripMargin) + } + + + @Test def testNegativeArraySizeException(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + + object Test { + def foo(foo: Any, bar: Any) = () + @autoawait def getValue = 4.2 + @lateasync def func(f: Any) = { + foo(f match { case _ if "".isEmpty => 2 }, getValue); + } + + @lateasync + def test() = func(4) + } + """) + } + + @Test def testNegativeArraySizeExceptionFine1(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + case class FixedFoo(foo: Int) + class Foobar(val foo: Int, val bar: Double) { + @autoawait @lateasync def getValue = 4.2 + @autoawait @lateasync def func(f: Any) = { + new Foobar(foo = f match { + case FixedFoo(x) => x + case _ => 2 + }, + bar = getValue) + } + } + object Test { + @lateasync def test() = new Foobar(0, 0).func(4) + } + """) + } + + @Test def testByNameOwner(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + object Bleh { + @autoawait @lateasync def asyncCall(): Int = 0 + def byName[T](fn: => T): T = fn + } + object Boffo { + @autoawait @lateasync def jerk(): Unit = { + val pointlessSymbolOwner = 1 match { + case _ => + Bleh.asyncCall() + Bleh.byName { + val whyDoHateMe = 1 + whyDoHateMe + } + } + } + } + object Test { + @lateasync def test() = Boffo.jerk() + } + """) + } + + @Test def testByNameOwner2(): Unit = { + val result = run( + """ + import scala.async.run.late.{autoawait,lateasync} + object Bleh { + @autoawait @lateasync def bleh = Bleh + def byName[T](fn: => T): T = fn + } + object Boffo { + @autoawait @lateasync def slob(): Unit = { + val pointlessSymbolOwner = { + Bleh.bleh.byName { + val whyDoHateMeToo = 1 + whyDoHateMeToo + } + } + } + } + object Test { + @lateasync def test() = Boffo.slob() + } + """) + } + + private def createTempDir(): File = { + val f = File.createTempFile("output", "") + f.delete() + f.mkdirs() + f + } + + def run(code: String): Any = { + val out = createTempDir() + try { + val reporter = new StoreReporter + val settings = new Settings(println(_)) + settings.outdir.value = out.getAbsolutePath + settings.embeddedDefaults(getClass.getClassLoader) + // settings.processArgumentString("-Xprint:patmat,postpatmat,jvm -nowarn") + val isInSBT = !settings.classpath.isSetByUser + if (isInSBT) settings.usejavacp.value = true + val global = new Global(settings, reporter) { + self => + + object late extends { + val global: self.type = self + } with LatePlugin + + override protected def loadPlugins(): List[Plugin] = late :: Nil + } + import global._ + + val run = new Run + val source = newSourceFile(code) + // TreeInterrogation.withDebug { + run.compileSources(source :: Nil) + // } + Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors) + val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader) + val cls = loader.loadClass("Test") + cls.getMethod("test").invoke(null) + } finally { + scala.reflect.io.Path.apply(out).deleteRecursively() + } + } +} + +abstract class LatePlugin extends Plugin { + + import global._ + + override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers { + val global: LatePlugin.this.global.type = LatePlugin.this.global + + lazy val asyncIdSym = symbolOf[AsyncId.type] + lazy val asyncSym = asyncIdSym.info.member(TermName("async")) + lazy val awaitSym = asyncIdSym.info.member(TermName("await")) + lazy val autoAwaitSym = symbolOf[autoawait] + lazy val lateAsyncSym = symbolOf[lateasync] + + def newTransformer(unit: CompilationUnit) = new TypingTransformer(unit) { + override def transform(tree: Tree): Tree = { + super.transform(tree) match { + case ap@Apply(fun, args) if fun.symbol.hasAnnotation(autoAwaitSym) => + localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(ap.tpe) :: Nil), ap :: Nil)) + case sel@Select(fun, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodTypeApi] || tree.tpe.isInstanceOf[PolyTypeApi]) => + localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(sel.tpe) :: Nil), sel :: Nil)) + case dd: DefDef if dd.symbol.hasAnnotation(lateAsyncSym) => atOwner(dd.symbol) { + deriveDefDef(dd) { rhs: Tree => + val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs)) + localTyper.typed(atPos(dd.pos)(invoke)) + } + } + case vd: ValDef if vd.symbol.hasAnnotation(lateAsyncSym) => atOwner(vd.symbol) { + deriveValDef(vd) { rhs: Tree => + val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs)) + localTyper.typed(atPos(vd.pos)(invoke)) + } + } + case vd: ValDef => + vd + case x => x + } + } + } + + override def newPhase(prev: Phase): Phase = new StdPhase(prev) { + override def apply(unit: CompilationUnit): Unit = { + val translated = newTransformer(unit).transformUnit(unit) + //println(show(unit.body)) + translated + } + } + + override val runsAfter: List[String] = "refchecks" :: Nil + override val phaseName: String = "postpatmat" + + }) + override val description: String = "postpatmat" + override val name: String = "postpatmat" +} + +// Methods with this annotation are translated to having the RHS wrapped in `AsyncId.async { }` +@field +final class lateasync extends StaticAnnotation + +// Calls to methods with this annotation are translated to `AsyncId.await()` +@getter +final class autoawait extends StaticAnnotation diff --git a/test/async/run/lazyval.scala b/test/async/run/lazyval.scala new file mode 100644 index 000000000000..4abf6d6dc6f6 --- /dev/null +++ b/test/async/run/lazyval.scala @@ -0,0 +1,27 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.lazyval.LazyValSpec]) + +package scala.async.run.lazyval { + + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId._ + + class LazyValSpec { + @Test + def lazyValAllowed(): Unit = { + val result = async { + var x = 0 + lazy val y = { x += 1; 42 } + assert(x == 0, x) + val z = await(1) + val result = y + x + assert(x == 1, x) + identity(y) + assert(x == 1, x) + result + } + assertEquals(43, result) + } + } + +} diff --git a/test/async/run/live.scala b/test/async/run/live.scala new file mode 100644 index 000000000000..f4268a731720 --- /dev/null +++ b/test/async/run/live.scala @@ -0,0 +1,299 @@ +/* + * 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 +package run +package live + +import org.junit.Test + +import internal.AsyncTestLV +import AsyncTestLV._ + +case class Cell[T](v: T) + +class Meter(val len: Long) extends AnyVal + +case class MCell[T](var v: T) + + +class LiveVariablesSpec { + AsyncTestLV.clear() + + @Test + def `zero out fields of reference type`(): Unit = { + val f = async { Cell(1) } + + def m1(x: Cell[Int]): Cell[Int] = + async { Cell(x.v + 1) } + + def m2(x: Cell[Int]): String = + async { x.v.toString } + + def m3() = async { + val a: Cell[Int] = await(f) // await$1$1 + // a == Cell(1) + val b: Cell[Int] = await(m1(a)) // await$2$1 + // b == Cell(2) + assert(AsyncTestLV.log.exists(_._2 == Cell(1)), AsyncTestLV.log) + val res = await(m2(b)) // await$3$1 + assert(AsyncTestLV.log.exists(_._2 == Cell(2))) + res + } + + assert(m3() == "2") + } + + @Test + def `zero out fields of type Any`(): Unit = { + val f = async { Cell(1) } + + def m1(x: Cell[Int]): Cell[Int] = + async { Cell(x.v + 1) } + + def m2(x: Any): String = + async { x.toString } + + def m3() = async { + val a: Cell[Int] = await(f) // await$4$1 + // a == Cell(1) + val b: Any = await(m1(a)) // await$5$1 + // b == Cell(2) + assert(AsyncTestLV.log.exists(_._2 == Cell(1))) + val res = await(m2(b)) // await$6$1 + assert(AsyncTestLV.log.exists(_._2 == Cell(2))) + res + } + + assert(m3() == "Cell(2)") + } + + @Test + def `do not zero out fields of primitive type`(): Unit = { + val f = async { 1 } + + def m1(x: Int): Cell[Int] = + async { Cell(x + 1) } + + def m2(x: Any): String = + async { x.toString } + + def m3() = async { + val a: Int = await(f) // await$7$1 + // a == 1 + val b: Any = await(m1(a)) // await$8$1 + // b == Cell(2) + // assert(!AsyncTestLV.log.exists(p => p._1 == "await$7$1")) + val res = await(m2(b)) // await$9$1 + assert(AsyncTestLV.log.exists(_._2 == Cell(2))) + res + } + + assert(m3() == "Cell(2)") + } + + @Test + def `zero out fields of value class type`(): Unit = { + val f = async { Cell(1) } + + def m1(x: Cell[Int]): Meter = + async { new Meter(x.v + 1) } + + def m2(x: Any): String = + async { x.toString } + + def m3() = async { + val a: Cell[Int] = await(f) // await$10$1 + // a == Cell(1) + val b: Meter = await(m1(a)) // await$11$1 + // b == Meter(2) + assert(AsyncTestLV.log.exists(_._2 == Cell(1))) + val res = await(m2(b.len)) // await$12$1 + assert(AsyncTestLV.log.exists(_._2.asInstanceOf[Meter].len == 2L)) + res + } + + assert(m3() == "2") + } + + @Test + def `zero out fields after use in loop`(): Unit = { + val f = async { MCell(1) } + + def m1(x: MCell[Int], y: Int): Int = + async { x.v + y } + + def m3() = async { + // state #1 + val a: MCell[Int] = await(f) // await$13$1 + // state #2 + var y = MCell(0) + + while (a.v < 10) { + // state #4 + a.v = a.v + 1 + y = MCell(await(a).v + 1) // await$14$1 + // state #7 + } + + // state #3 + // assert(AsyncTestLV.log.exists(entry => entry._1 == "await$14$1")) + + val b = await(m1(a, y.v)) // await$15$1 + // state #8 + assert(AsyncTestLV.log.exists(_._2 == MCell(10)), AsyncTestLV.log) + assert(AsyncTestLV.log.exists(_._2 == MCell(11))) + b + } + + assert(m3() == 21, m3()) + } + + @Test + def `don't zero captured fields captured lambda`(): Unit = { + val f = async { + val x = "x" + val y = "y" + await(0) + y.reverse + val f = () => assert(x != null) + await(0) + f + } + AsyncTestLV.assertNotNulledOut("x") + AsyncTestLV.assertNulledOut("y") + f() + } + + @Test + def `don't zero captured fields captured by-name`(): Unit = { + def func0[A](a: => A): () => A = () => a + val f = async { + val x = "x" + val y = "y" + await(0) + y.reverse + val f = func0(assert(x != null)) + await(0) + f + } + AsyncTestLV.assertNotNulledOut("x") + AsyncTestLV.assertNulledOut("y") + f() + } + + @Test + def `don't zero captured fields nested class`(): Unit = { + def func0[A](a: => A): () => A = () => a + val f = async { + val x = "x" + val y = "y" + await(0) + y.reverse + val f = new Function0[Unit] { + def apply = assert(x != null) + } + await(0) + f + } + AsyncTestLV.assertNotNulledOut("x") + AsyncTestLV.assertNulledOut("y") + f() + } + + @Test + def `don't zero captured fields nested object`(): Unit = { + def func0[A](a: => A): () => A = () => a + val f = async { + val x = "x" + val y = "y" + await(0) + y.reverse + object f extends Function0[Unit] { + def apply = assert(x != null) + } + await(0) + f + } + AsyncTestLV.assertNotNulledOut("x") + AsyncTestLV.assertNulledOut("y") + f() + } + + @Test + def `don't zero captured fields nested def`(): Unit = { + val f = async { + val x = "x" + val y = "y" + await(0) + y.reverse + def xx = x + val f = xx _ + await(0) + f + } + AsyncTestLV.assertNotNulledOut("x") + AsyncTestLV.assertNulledOut("y") + f() + } + + @Test + def `capture bug`(): Unit = { + sealed trait Base + case class B1() extends Base + case class B2() extends Base + val outer = List[(Base, Int)]((B1(), 8)) + + def getMore(b: Base) = 4 + + def baz = async { + outer.head match { + case (a @ B1(), r) => { + val ents = await(getMore(a)) + + { () => + println(a) + assert(a ne null) + } + } + case (b @ B2(), x) => + () => ??? + } + } + baz() + } + + // https://github.com/scala/async/issues/104 + @Test def dontNullOutVarsOfTypeNothing_t104(): Unit = { + import scala.async.Async._ + import scala.concurrent.duration.Duration + import scala.concurrent.{Await, Future} + import scala.concurrent.ExecutionContext.Implicits.global + def errorGenerator(randomNum: Double) = { + Future { + if (randomNum < 0) { + throw new IllegalStateException("Random number was too low!") + } else { + throw new IllegalStateException("Random number was too high!") + } + } + } + def randomTimesTwo = async { + val num = _root_.scala.math.random + if (num < 0 || num > 1) { + await(errorGenerator(num)) + } + num * 2 + } + Await.result(randomTimesTwo, TestLatch.DefaultTimeout) // was: NotImplementedError + } +} diff --git a/test/async/run/localclasses.scala b/test/async/run/localclasses.scala new file mode 100644 index 000000000000..db9bc201e86a --- /dev/null +++ b/test/async/run/localclasses.scala @@ -0,0 +1,34 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.neg.LocalClasses0Spec]) + +package scala.async.neg { + + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + class LocalClasses0Spec { + @Test + def localClassCrashIssue16(): Unit = { + import AsyncId.{async, await} + assertEquals(1, async { + class B { def f = 1 } + await(new B()).f + }) + } + + @Test + def nestedCaseClassAndModuleAllowed(): Unit = { + import AsyncId.{await, async} + assertEquals("bob", async { + trait Base { def base = 0} + await(0) + case class Person(name: String) extends Base + val fut = async { "bob" } + val x = Person(await(fut)) + x.base + x.name + }) + } + } + +} diff --git a/test/async/run/match0.scala b/test/async/run/match0.scala new file mode 100644 index 000000000000..9c47df9a40cb --- /dev/null +++ b/test/async/run/match0.scala @@ -0,0 +1,146 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.match0.MatchSpec]) + +package scala.async.run.match0 { + + import language.{reflectiveCalls, postfixOps} + import scala.concurrent.{Future, ExecutionContext, Await} + import scala.concurrent.duration._ + import scala.async.Async.{async, await} + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + + class TestMatchClass { + + import ExecutionContext.Implicits.global + + def m1(x: Int): Future[Int] = Future { + x + 2 + } + + def m2(y: Int): Future[Int] = async { + val f = m1(y) + var z = 0 + y match { + case 10 => + val x1 = await(f) + z = x1 + 2 + case 20 => + val x2 = await(f) + z = x2 - 2 + } + z + } + + def m3(y: Int): Future[Int] = async { + val f = m1(y) + var z = 0 + y match { + case 0 => + val x2 = await(f) + z = x2 - 2 + case 1 => + val x1 = await(f) + z = x1 + 2 + } + z + } + } + + + class MatchSpec { + + @Test def `support await in a simple match expression`(): Unit = { + val o = new TestMatchClass + val fut = o.m2(10) // matches first case + val res = Await.result(fut, 2 seconds) + assertEquals(14, res) + } + + @Test def `support await in a simple match expression 2`(): Unit = { + val o = new TestMatchClass + val fut = o.m3(1) // matches second case + val res = Await.result(fut, 2 seconds) + assertEquals(5, res) + } + + @Test def `support await in a match expression with binds`(): Unit = { + val result = AsyncId.async { + val x = 1 + Option(x) match { + case op @ Some(x) => + assert(op.contains(1)) + x + AsyncId.await(x) + case None => AsyncId.await(0) + } + } + assertEquals(2, res) + } + + @Test def `support await referring to pattern matching vals`(): Unit = { + import AsyncId.{async, await} + val result = async { + val x = 1 + val opt = Some("") + await(0) + val o @ Some(y) = opt + + { + val o @ Some(y) = Some(".") + } + + await(0) + await((o, y.isEmpty)) + } + assertEquals((Some(""), true), result) + } + + @Test def `await in scrutinee`(): Unit = { + import AsyncId.{async, await} + val result = async { + await(if ("".isEmpty) await(1) else ???) match { + case x if x < 0 => ??? + case y: Int => y * await(3) + } + } + assertEquals(3, result) + } + + @Test def duplicateBindName(): Unit = { + import AsyncId.{async, await} + def m4(m: Any) = async { + m match { + case buf: String => + await(0) + case buf: Double => + await(2) + } + } + + assertEquals(0, m4("")) + } + + @Test def bugCastBoxedUnitToStringMatch(): Unit = { + import scala.async.internal.AsyncId.{async, await} + def foo = async { + val p2 = await(5) + "foo" match { + case p3: String => + p2.toString + } + } + assertEquals("5", foo) + } + + @Test def bugCastBoxedUnitToStringIf(): Unit = { + import scala.async.internal.AsyncId.{async, await} + def foo = async { + val p2 = await(5) + if (true) p2.toString else p2.toString + } + assertEquals("5", foo) + } + } + +} diff --git a/test/async/run/nesteddef.scala b/test/async/run/nesteddef.scala new file mode 100644 index 000000000000..cb789cb17188 --- /dev/null +++ b/test/async/run/nesteddef.scala @@ -0,0 +1,97 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.nesteddef.NestedDef]) + +package scala.async.run.nesteddef { + + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + class NestedDef { + + @Test + def nestedDef(): Unit = { + import AsyncId._ + val result = async { + val a = 0 + val x = await(a) - 1 + val local = 43 + def bar(d: Double) = -d + a + local + def foo(z: Any) = (a.toDouble, bar(x).toDouble, z) + foo(await(2)) + } + assertEquals((0d, 44d, 2), result) + } + + + @Test + def nestedFunction(): Unit = { + import AsyncId._ + val result = async { + val a = 0 + val x = await(a) - 1 + val local = 43 + val bar = (d: Double) => -d + a + local + val foo = (z: Any) => (a.toDouble, bar(x).toDouble, z) + foo(await(2)) + } + assertEquals((0d, 44d, 2), result) + } + + // We must lift `foo` and `bar` in the next two tests. + @Test + def nestedDefTransitive1(): Unit = { + import AsyncId._ + val result = async { + val a = 0 + val x = await(a) - 1 + def bar = a + def foo = bar + foo + } + assertEquals(0, result) + } + + @Test + def nestedDefTransitive2(): Unit = { + import AsyncId._ + val result = async { + val a = 0 + val x = await(a) - 1 + def bar = a + def foo = bar + 0 + } + assertEquals(0, result) + } + + + // checking that our use/definition analysis doesn't cycle. + @Test + def mutuallyRecursive1(): Unit = { + import AsyncId._ + val result = async { + val a = 0 + val x = await(a) - 1 + def foo: Int = if (true) 0 else bar + def bar: Int = if (true) 0 else foo + bar + } + assertEquals(0, result) + } + + // checking that our use/definition analysis doesn't cycle. + @Test + def mutuallyRecursive2(): Unit = { + import AsyncId._ + val result = async { + val a = 0 + def foo: Int = if (true) 0 else bar + def bar: Int = if (true) 0 else foo + val x = await(a) - 1 + bar + } + assertEquals(0, result) + } + } + +} diff --git a/test/async/run/noawait.scala b/test/async/run/noawait.scala new file mode 100644 index 000000000000..2d887702f3b9 --- /dev/null +++ b/test/async/run/noawait.scala @@ -0,0 +1,35 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.noawait.NoAwaitSpec]) + +package scala.async.run.noawait { + + import scala.async.internal.AsyncId + import AsyncId._ + import org.junit.Test + import org.junit.Assert._ + + class NoAwaitSpec { + @Test + def `async block without await`(): Unit = { + def foo = 1 + assertEquals(foo, async { + foo + foo + }) + } + + @Test + def `async block without await 2`(): Unit = { + assertEquals(1, async { + def x = 0 + if (x > 0) 0 else 1 + }) + } + + @Test + def `async expr without await`(): Unit = { + def foo = 1 + assertEquals(foo, async(foo)) + } + } + +} diff --git a/test/async/run/stackoverflow.scala b/test/async/run/stackoverflow.scala new file mode 100644 index 000000000000..b8d08b25b6ea --- /dev/null +++ b/test/async/run/stackoverflow.scala @@ -0,0 +1,15 @@ +import scala.async.internal.AsyncId._ + +object Test extends App { + + async { + var i = 100000000 + while (i > 0) { + if (false) { + await(()) + } + i -= 1 + } + } + +} diff --git a/test/async/run/syncOptimization.scala b/test/async/run/syncOptimization.scala new file mode 100644 index 000000000000..e5bd29d86c23 --- /dev/null +++ b/test/async/run/syncOptimization.scala @@ -0,0 +1,22 @@ +import scala.async.Async._ +import scala.concurrent._ +import scala.concurrent.duration._ +import ExecutionContext.Implicits._ + +object Test extends App { + + def stackDepth = Thread.currentThread().getStackTrace.length + + val future = 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) + } + Await.result(future, 10.seconds) + +} diff --git a/test/async/run/toughtype.scala.scala b/test/async/run/toughtype.scala.scala new file mode 100644 index 000000000000..b57fe038cab4 --- /dev/null +++ b/test/async/run/toughtype.scala.scala @@ -0,0 +1,353 @@ +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.toughtype.ToughTypeSpec]) + +package scala.async.run.toughtype { + + import language.{reflectiveCalls, postfixOps} + import scala.concurrent._ + import scala.concurrent.duration._ + import scala.async.Async._ + import org.junit.Test + import org.junit.Assert._ + import scala.async.internal.AsyncId + + + object ToughTypeObject { + + import ExecutionContext.Implicits.global + + class Inner + + def m2 = async[(List[_], ToughTypeObject.Inner)] { + val y = await(Future[List[_]](Nil)) + val z = await(Future[Inner](new Inner)) + (y, z) + } + } + + class ToughTypeSpec { + + @Test def `propogates tough types`(): Unit = { + val fut = ToughTypeObject.m2 + val res: (List[_], scala.async.run.toughtype.ToughTypeObject.Inner) = Await.result(fut, 2 seconds) + assertEquals(Nil, res._1) + } + + @Test def patternMatchingPartialFunction(): Unit = { + import AsyncId.{await, async} + assertEquals(3, async { + await(1) + val a = await(1) + val f = { case x => x + a }: PartialFunction[Int, Int] + await(f(2)) + }) + } + + @Test def patternMatchingPartialFunctionNested(): Unit = { + import AsyncId.{await, async} + assertEquals(-3, async { + await(1) + val neg1 = -1 + val a = await(1) + val f = { case x => ({case x => neg1 * x}: PartialFunction[Int, Int])(x + a) }: PartialFunction[Int, Int] + await(f(2)) + }) + } + + @Test def patternMatchingFunction(): Unit = { + import AsyncId.{await, async} + assertEquals(3, async { + await(1) + val a = await(1) + val f = { case x => x + a }: Function[Int, Int] + await(f(2)) + }) + } + + @Test def existentialBindIssue19(): Unit = { + import AsyncId.{await, async} + def m7(a: Any) = async { + a match { + case s: Seq[_] => + val x = s.size + var ss = s + ss = s + await(x) + } + } + assertEquals(0, m7(Nil)) + } + + @Test def existentialBind2Issue19(): Unit = { + import scala.async.Async._, scala.concurrent.ExecutionContext.Implicits.global + def conjure[T]: T = null.asInstanceOf[T] + + def m3 = async { + val p: List[Option[_]] = conjure[List[Option[_]]] + await(Future(1)) + } + + def m4 = async { + await(Future[List[_]](Nil)) + } + } + + @Test def singletonTypeIssue17(): Unit = { + import AsyncId.{async, await} + class A { class B } + async { + val a = new A + def foo(b: a.B) = 0 + await(foo(new a.B)) + } + } + + @Test def existentialMatch(): Unit = { + import AsyncId.{async, await} + trait Container[+A] + case class ContainerImpl[A](value: A) extends Container[A] + def foo: Container[_] = async { + val a: Any = List(1) + a match { + case buf: Seq[_] => + val foo = await(5) + val e0 = buf(0) + ContainerImpl(e0) + } + } + foo + } + + @Test def existentialIfElse0(): Unit = { + import AsyncId.{async, await} + trait Container[+A] + case class ContainerImpl[A](value: A) extends Container[A] + def foo: Container[_] = async { + val a: Any = List(1) + if (true) { + val buf: Seq[_] = List(1) + val foo = await(5) + val e0 = buf(0) + ContainerImpl(e0) + } else ??? + } + foo + } + + // This test was failing when lifting `def r` with: + // symbol value m#10864 does not exist in r$1 + // + // We generated: + // + // private[this] def r$1#5727[A#5728 >: Nothing#157 <: Any#156](m#5731: Foo#2349[A#5728]): Unit#208 = Bippy#2352.this.bar#5532({ + // m#5730; + // () + // }); + // + // Notice the incorrect reference to `m`. + // + // We compensated in `Lifter` by copying `ValDef` parameter symbols directly across. + // + // Turns out the behaviour stems from `thisMethodType` in `Namers`, which treats type parameter skolem symbols. + @Test def nestedMethodWithInconsistencyTreeAndInfoParamSymbols(): Unit = { + import language.{reflectiveCalls, postfixOps} + import scala.concurrent.{Future, ExecutionContext, Await} + import scala.concurrent.duration._ + import scala.async.Async.{async, await} + import scala.async.internal.AsyncId + + class Foo[A] + + object Bippy { + + import ExecutionContext.Implicits.global + + def bar(f: => Unit): Unit = f + + def quux: Future[String] = ??? + + def foo = async { + def r[A](m: Foo[A])(n: A) = { + bar { + locally(m) + locally(n) + identity[A] _ + } + } + + await(quux) + + r(new Foo[String])("") + } + } + Bippy + } + + @Test + def ticket63(): Unit = { + import scala.async.Async._ + import scala.concurrent.{ ExecutionContext, Future } + + object SomeExecutionContext extends ExecutionContext { + def reportFailure(t: Throwable): Unit = ??? + def execute(runnable: Runnable): Unit = ??? + } + + trait FunDep[W, S, R] { + def method(w: W, s: S): Future[R] + } + + object FunDep { + implicit def `Something to do with List`[W, S, R](implicit funDep: FunDep[W, S, R]) = + new FunDep[W, List[S], W] { + def method(w: W, l: List[S]) = async { + val it = l.iterator + while (it.hasNext) { + await(funDep.method(w, it.next())) + } + w + }(SomeExecutionContext) + } + } + + } + + @Test def ticket66Nothing(): Unit = { + import scala.concurrent.Future + import scala.concurrent.ExecutionContext.Implicits.global + val e = new Exception() + val f: Future[Nothing] = Future.failed(e) + val f1 = async { + await(f) + } + try { + Await.result(f1, 5.seconds) + } catch { + case `e` => + } + } + + @Test def ticket83ValueClass(): Unit = { + import scala.async.Async._ + import scala.concurrent._, duration._, ExecutionContext.Implicits.global + val f = async { + val uid = new IntWrapper("foo") + await(Future(uid)) + } + val result = Await.result(f, 5.seconds) + assertEquals(new IntWrapper("foo"), result) + } + + @Test def ticket86NestedValueClass(): Unit = { + import ExecutionContext.Implicits.global + + val f = async { + val a = Future.successful(new IntWrapper("42")) + await(await(a).plusStr) + } + val result = Await.result(f, 5.seconds) + assertEquals("42!", result) + } + + @Test def ticket86MatchedValueClass(): Unit = { + import ExecutionContext.Implicits.global + + def doAThing(param: IntWrapper) = Future(None) + + val fut = async { + Option(new IntWrapper("value!")) match { + case Some(valueHolder) => + await(doAThing(valueHolder)) + case None => + None + } + } + + val result = Await.result(fut, 5.seconds) + assertEquals(None, result) + } + + @Test def ticket86MatchedParameterizedValueClass(): Unit = { + import ExecutionContext.Implicits.global + + def doAThing(param: ParamWrapper[String]) = Future(None) + + val fut = async { + Option(new ParamWrapper("value!")) match { + case Some(valueHolder) => + await(doAThing(valueHolder)) + case None => + None + } + } + + val result = Await.result(fut, 5.seconds) + assertEquals(None, result) + } + + @Test def ticket86PrivateValueClass(): Unit = { + import ExecutionContext.Implicits.global + + def doAThing(param: PrivateWrapper) = Future(None) + + val fut = async { + Option(PrivateWrapper.Instance) match { + case Some(valueHolder) => + await(doAThing(valueHolder)) + case None => + None + } + } + + val result = Await.result(fut, 5.seconds) + assertEquals(None, result) + } + + @Test def awaitOfAbstractType(): Unit = { + import ExecutionContext.Implicits.global + + def combine[A](a1: A, a2: A): A = a1 + + def combineAsync[A](a1: Future[A], a2: Future[A]) = async { + combine(await(a1), await(a2)) + } + + val fut = combineAsync(Future(1), Future(2)) + + val result = Await.result(fut, 5.seconds) + assertEquals(1, result) + } + + // https://github.com/scala/async/issues/106 + @Test def valueClassT106(): Unit = { + import scala.async.internal.AsyncId._ + async { + "whatever value" match { + case _ => + await("whatever return type") + new IntWrapper("value class matters") + } + "whatever return type" + } + } + } + + class IntWrapper(val value: String) extends AnyVal { + def plusStr = Future.successful(value + "!") + } + class ParamWrapper[T](val value: T) extends AnyVal + + class PrivateWrapper private (private val value: String) extends AnyVal + object PrivateWrapper { + def Instance = new PrivateWrapper("") + } + + + trait A + + trait B + + trait L[A2, B2 <: A2] { + def bar(a: Any, b: Any) = 0 + } + +} diff --git a/test/async/run/uncheckedBounds.scala b/test/async/run/uncheckedBounds.scala new file mode 100644 index 000000000000..435a14bea7a8 --- /dev/null +++ b/test/async/run/uncheckedBounds.scala @@ -0,0 +1,47 @@ +/* + * 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 +package run +package uncheckedBounds + +import org.junit.{Test, Assert} +import scala.async.TreeInterrogation + +class UncheckedBoundsSpec { + @Test def insufficientLub_SI_7694(): Unit = { + eval( s""" + object Test { + import _root_.scala.async.run.toughtype._ + import _root_.scala.async.internal.AsyncId.{async, await} + async { + (if (true) await(null: L[A, A]) else await(null: L[B, B])) + } + } + """, compileOptions = s"-cp ${toolboxClasspath} ") + } + + @Test def insufficientLub_SI_7694_ScalaConcurrent(): Unit = { + eval( s""" + object Test { + import _root_.scala.async.run.toughtype._ + import _root_.scala.async.Async.{async, await} + import scala.concurrent._ + import scala.concurrent.ExecutionContext.Implicits.global + async { + (if (true) await(null: Future[L[A, A]]) else await(null: Future[L[B, B]])) + } + } + """, compileOptions = s"-cp ${toolboxClasspath} ") + } + +} diff --git a/test/async/run/warning.scala b/test/async/run/warning.scala new file mode 100644 index 000000000000..155794d36ec1 --- /dev/null +++ b/test/async/run/warning.scala @@ -0,0 +1,105 @@ +/* + * 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 +package run + +import org.junit.Test + +import scala.language.{postfixOps, reflectiveCalls} +import scala.tools.nsc.reporters.StoreReporter + + +class WarningsSpec { + + @Test + // https://github.com/scala/async/issues/74 + def noPureExpressionInStatementPositionWarning_t74(): Unit = { + val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xfatal-warnings") + // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses" + tb.eval(tb.parse { + """ + | import scala.async.internal.AsyncId._ + | async { + | if ("".isEmpty) { + | await(println("hello")) + | () + | } else 42 + | } + """.stripMargin + }) + } + + @Test + // https://github.com/scala/async/issues/74 + def noDeadCodeWarningForAsyncThrow(): Unit = { + val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks") + // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses" + val source = + """ + | class Test { + | import scala.async.Async._ + | import scala.concurrent.ExecutionContext.Implicits.global + | async { throw new Error() } + | } + """.stripMargin + val run = new global.Run + val sourceFile = global.newSourceFile(source) + run.compileSources(sourceFile :: Nil) + assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos) + } + + @Test + def noDeadCodeWarningInMacroExpansion(): Unit = { + val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks") + val source = """ + | class Test { + | def test = { + | import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global + | async { + | val opt = await(async(Option.empty[String => Future[Unit]])) + | opt match { + | case None => + | throw new RuntimeException("case a") + | case Some(f) => + | await(f("case b")) + | } + | } + | } + |} + """.stripMargin + val run = new global.Run + val sourceFile = global.newSourceFile(source) + run.compileSources(sourceFile :: Nil) + assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos) + } + + @Test + def ignoreNestedAwaitsInIDE_t1002561(): Unit = { + // https://www.assembla.com/spaces/scala-ide/tickets/1002561 + val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ystop-after:typer ") + val source = """ + | class Test { + | def test = { + | import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global + | async { + | 1 + await({def foo = (async(await(async(2)))); foo}) + | } + | } + |} + """.stripMargin + val run = new global.Run + val sourceFile = global.newSourceFile(source) + run.compileSources(sourceFile :: Nil) + assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos) + } +} From 3b38d951f7c2dca3370445792d070e917086113e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 11 Mar 2020 18:13:29 +1000 Subject: [PATCH 09/94] Fix bug in StateSet and add a remove operation --- src/compiler/scala/tools/nsc/transform/async/StateSet.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala index e4c79e250c26..487787a8128b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala +++ b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala @@ -21,8 +21,10 @@ import scala.collection.JavaConverters.{asScalaIteratorConverter, iterableAsScal final class StateSet { private val bitSet = new java.util.BitSet() private val caseSet = new util.HashSet[Integer]() - def +=(stateId: Int): Unit = if (stateId > 0) bitSet.set(stateId) else caseSet.add(stateId) - def contains(stateId: Int): Boolean = if (stateId > 0 && stateId < 1024) bitSet.get(stateId) else caseSet.contains(stateId) + private def useBitSet(i: Int) = i > 0 && i < 1024 + def +=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.set(stateId) else caseSet.add(stateId) + def -=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.clear(stateId) else caseSet.remove(stateId) + def contains(stateId: Int): Boolean = if (useBitSet(stateId)) bitSet.get(stateId) else caseSet.contains(stateId) def iterator: Iterator[Integer] = { bitSet.stream().iterator().asScala ++ caseSet.asScala.iterator } From b61e65b5f514a9d1d46645c601df01cbde7fbaaa Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2020 18:06:01 +1000 Subject: [PATCH 10/94] Delete dead code. --- .../scala/tools/nsc/transform/async/AsyncAnalysis.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index fba59849f575..7d2ffe1ae2a5 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -27,12 +27,9 @@ trait AsyncAnalysis extends TransformUtils { def reportUnsupportedAwaits(tree: Tree): Unit = { val analyzer = new UnsupportedAwaitAnalyzer analyzer.traverse(tree) - // analyzer.hasUnsupportedAwaits // XB: not used?! } private class UnsupportedAwaitAnalyzer extends AsyncTraverser { - var hasUnsupportedAwaits = false - override def nestedClass(classDef: ClassDef): Unit = { val kind = if (classDef.symbol.asClass.isTrait) "trait" else "class" reportUnsupportedAwait(classDef, s"nested $kind") @@ -70,7 +67,6 @@ trait AsyncAnalysis extends TransformUtils { case ValDef(mods, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => reportUnsupportedAwait(tree, "lazy val initializer") case CaseDef(_, guard, _) if guard exists isAwait => - // TODO lift this restriction reportUnsupportedAwait(tree, "pattern guard") case _ => super.traverse(tree) @@ -102,7 +98,6 @@ trait AsyncAnalysis extends TransformUtils { } private def reportError(pos: Position, msg: String): Unit = { - hasUnsupportedAwaits = true abort(pos, msg) } } From 5a9517bd668a97b7665383d853c164a8ff399a82 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 17 Feb 2020 22:22:03 +1000 Subject: [PATCH 11/94] Convert tests to ScalaConcurrent Future System MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also:   - remove late.scala (converted to AnnotationDrivenAsync)   - Remove uncheckedBounds.scala (the problem no longer exists post-erasure) --- test/async/run/anf.scala | 199 +++--- test/async/run/hygiene.scala | 35 +- test/async/run/ifelse0.scala | 23 +- test/async/run/ifelse0_while.scala | 47 +- test/async/run/late.scala | 612 ------------------ test/async/run/lazyval.scala | 16 +- test/async/run/localclasses.scala | 21 +- test/async/run/match0.scala | 52 +- test/async/run/nesteddef.scala | 42 +- test/async/run/noawait.scala | 23 +- test/async/run/stackoverflow.scala | 16 +- ...{toughtype.scala.scala => toughtype.scala} | 55 +- test/async/run/uncheckedBounds.scala | 47 -- test/async/run/warning.scala | 2 +- 14 files changed, 290 insertions(+), 900 deletions(-) delete mode 100644 test/async/run/late.scala rename test/async/run/{toughtype.scala.scala => toughtype.scala} (92%) delete mode 100644 test/async/run/uncheckedBounds.scala diff --git a/test/async/run/anf.scala b/test/async/run/anf.scala index 6cde888d4362..44f8b0513dda 100644 --- a/test/async/run/anf.scala +++ b/test/async/run/anf.scala @@ -6,13 +6,37 @@ package scala.async.run.anf { import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ import scala.async.Async.{async, await} + import scala.reflect.{ClassTag, classTag} import org.junit.Test - import scala.async.internal.AsyncId + object `package` { - class AnfTestClass { + implicit class FutureOps[T](f: Future[T]) { + def block: T = Await.result(f, Duration.Inf) + } + // scala.tools.partest.TestUtil.intercept is not good enough for us + def intercept[T <: Throwable : ClassTag](body: => Any): T = { + try { + body + throw new Exception(s"Exception of type ${classTag[T]} was not thrown") + } catch { + case t: Throwable => + if (!classTag[T].runtimeClass.isAssignableFrom(t.getClass)) throw t + else t.asInstanceOf[T] + } + } + implicit class objectops(obj: Any) { + def mustBe(other: Any) = assert(obj == other, obj + " is not " + other) + + def mustEqual(other: Any) = mustBe(other) + } + + } + import Future.{successful => fut} - import ExecutionContext.Implicits.global + import ExecutionContext.Implicits.global + + class AnfTestClass { def base(x: Int): Future[Int] = Future { x + 2 @@ -108,107 +132,102 @@ package scala.async.run.anf { @Test def `inlining block does not produce duplicate definition`(): Unit = { - AsyncId.async { + async { val f = 12 - val x = AsyncId.await(f) + val x = await(fut(f)) { type X = Int val x: X = 42 - println(x) + identity(x) } type X = Int x: X - } + }.block } @Test def `inlining block in tail position does not produce duplicate definition`(): Unit = { - AsyncId.async { + async { val f = 12 - val x = AsyncId.await(f) + val x = await(fut(f)) { val x = 42 x } - } mustBe (42) + }.block mustBe (42) } @Test def `match as expression 1`(): Unit = { import ExecutionContext.Implicits.global - val result = AsyncId.async { + val result = async { val x = "" match { - case _ => AsyncId.await(1) + 1 + case _ => await(fut(1)) + 1 } x } - result mustBe (2) + result.block mustBe (2) } @Test def `match as expression 2`(): Unit = { import ExecutionContext.Implicits.global - val result = AsyncId.async { + val result = async { val x = "" match { - case "" if false => AsyncId.await(1) + 1 - case _ => 2 + AsyncId.await(1) + case "" if false => await(fut(1)) + 1 + case _ => 2 + await(fut(1)) } val y = x "" match { - case _ => AsyncId.await(y) + 100 + case _ => await(fut(y)) + 100 } } - result mustBe (103) + result.block mustBe (103) } @Test def nestedAwaitAsBareExpression(): Unit = { import ExecutionContext.Implicits.global - import AsyncId.{async, await} val result = async { - await(await("").isEmpty) + await(fut(await(fut("")).isEmpty)) } - result mustBe (true) + result.block mustBe (true) } @Test def nestedAwaitInBlock(): Unit = { import ExecutionContext.Implicits.global - import AsyncId.{async, await} val result = async { () - await(await("").isEmpty) + await(fut(await(fut("")).isEmpty)) } - result mustBe (true) + result.block mustBe (true) } @Test def nestedAwaitInIf(): Unit = { import ExecutionContext.Implicits.global - import AsyncId.{async, await} val result = async { if ("".isEmpty) - await(await("").isEmpty) + await(fut(await(fut("")).isEmpty)) else 0 } - result mustBe (true) + result.block mustBe (true) } @Test def byNameExpressionsArentLifted(): Unit = { - import AsyncId.{async, await} def foo(ignored: => Any, b: Int) = b val result = async { - foo(???, await(1)) + foo(???, await(fut(1))) } - result mustBe (1) + result.block mustBe (1) } @Test def evaluationOrderRespected(): Unit = { - import AsyncId.{async, await} def foo(a: Int, b: Int) = (a, b) val result = async { var i = 0 @@ -216,71 +235,65 @@ package scala.async.run.anf { i += 1 i } - foo(next(), await(next())) + foo(next(), await(fut(next()))) } - result mustBe ((1, 2)) + result.block mustBe ((1, 2)) } @Test def awaitInNonPrimaryParamSection1(): Unit = { - import AsyncId.{async, await} def foo(a0: Int)(b0: Int) = s"a0 = $a0, b0 = $b0" val res = async { var i = 0 def get = {i += 1; i} - foo(get)(await(get)) + foo(get)(await(fut(get))) } - res mustBe "a0 = 1, b0 = 2" + res.block mustBe "a0 = 1, b0 = 2" } @Test def awaitInNonPrimaryParamSection2(): Unit = { - import AsyncId.{async, await} def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" val res = async { var i = 0 - def get = async {i += 1; i} + def get = async{i += 1; i} foo[Int](await(get))(await(get) :: await(async(Nil)) : _*) } - res mustBe "a0 = 1, b0 = 2" + res.block mustBe "a0 = 1, b0 = 2" } @Test def awaitInNonPrimaryParamSectionWithLazy1(): Unit = { - import AsyncId.{async, await} def foo[T](a: => Int)(b: Int) = b val res = async { def get = async {0} foo[Int](???)(await(get)) } - res mustBe 0 + res.block mustBe 0 } @Test def awaitInNonPrimaryParamSectionWithLazy2(): Unit = { - import AsyncId.{async, await} def foo[T](a: Int)(b: => Int) = a val res = async { def get = async {0} foo[Int](await(get))(???) } - res mustBe 0 + res.block mustBe 0 } @Test def awaitWithLazy(): Unit = { - import AsyncId.{async, await} def foo[T](a: Int, b: => Int) = a val res = async { def get = async {0} foo[Int](await(get), ???) } - res mustBe 0 + res.block mustBe 0 } @Test def awaitOkInReciever(): Unit = { - import AsyncId.{async, await} class Foo { def bar(a: Int)(b: Int) = a + b } async { await(async(new Foo)).bar(1)(2) @@ -289,7 +302,6 @@ package scala.async.run.anf { @Test def namedArgumentsRespectEvaluationOrder(): Unit = { - import AsyncId.{async, await} def foo(a: Int, b: Int) = (a, b) val result = async { var i = 0 @@ -297,14 +309,13 @@ package scala.async.run.anf { i += 1 i } - foo(b = next(), a = await(next())) + foo(b = next(), a = await(fut(next()))) } - result mustBe ((2, 1)) + result.block mustBe ((2, 1)) } @Test def namedAndDefaultArgumentsRespectEvaluationOrder(): Unit = { - import AsyncId.{async, await} var i = 0 def next() = { i += 1 @@ -312,103 +323,98 @@ package scala.async.run.anf { } def foo(a: Int = next(), b: Int = next()) = (a, b) async { - foo(b = await(next())) - } mustBe ((2, 1)) + foo(b = await(fut(next()))) + }.block mustBe ((2, 1)) i = 0 async { - foo(a = await(next())) - } mustBe ((1, 2)) + foo(a = await(fut(next()))) + }.block mustBe ((1, 2)) } @Test def repeatedParams1(): Unit = { - import AsyncId.{async, await} var i = 0 def foo(a: Int, b: Int*) = b.toList def id(i: Int) = i async { - foo(await(0), id(1), id(2), id(3), await(4)) - } mustBe (List(1, 2, 3, 4)) + foo(await(fut(0)), id(1), id(2), id(3), await(fut(4))) + }.block mustBe (List(1, 2, 3, 4)) } @Test def repeatedParams2(): Unit = { - import AsyncId.{async, await} var i = 0 def foo(a: Int, b: Int*) = b.toList def id(i: Int) = i async { - foo(await(0), List(id(1), id(2), id(3)): _*) - } mustBe (List(1, 2, 3)) + foo(await(fut(0)), List(id(1), id(2), id(3)): _*) + }.block mustBe (List(1, 2, 3)) } @Test def awaitInThrow(): Unit = { - import _root_.scala.async.internal.AsyncId.{async, await} intercept[Exception]( async { - throw new Exception("msg: " + await(0)) - } + throw new Exception("msg: " + await(fut(0))) + }.block ).getMessage mustBe "msg: 0" } @Test def awaitInTyped(): Unit = { - import _root_.scala.async.internal.AsyncId.{async, await} async { - (("msg: " + await(0)): String).toString - } mustBe "msg: 0" + (("msg: " + await(fut(0))): String).toString + }.block mustBe "msg: 0" } @Test def awaitInAssign(): Unit = { - import _root_.scala.async.internal.AsyncId.{async, await} async { var x = 0 - x = await(1) + x = await(fut(1)) x - } mustBe 1 + }.block mustBe 1 } @Test def caseBodyMustBeTypedAsUnit(): Unit = { - import _root_.scala.async.internal.AsyncId.{async, await} val Up = 1 val Down = 2 val sign = async { - await(1) match { + await(fut(1)) match { case Up => 1.0 case Down => -1.0 } } - sign mustBe 1.0 - } - - @Test - def awaitInImplicitApply(): Unit = { - val tb = mkToolbox(s"-cp ${toolboxClasspath}") - val tree = tb.typeCheck(tb.parse { - """ - | import language.implicitConversions - | import _root_.scala.async.internal.AsyncId.{async, await} - | implicit def view(a: Int): String = "" - | async { - | await(0).length - | } - """.stripMargin - }) - val applyImplicitView = tree.collect { case x if x.getClass.getName.endsWith("ApplyImplicitView") => x } - println(applyImplicitView) - applyImplicitView.map(_.toString) mustStartWith List("view(") - } + sign.block mustBe 1.0 + } + +// @Test +// def awaitInImplicitApply(): Unit = { +// val tb = mkToolbox(s"-cp ${toolboxClasspath}") +// val tree = tb.typeCheck(tb.parse { +// """ +// | import language.implicitConversions +// | import _root_.scala.async.Async.{async, await} +// | import _root_.scala.concurrent._ +// | import ExecutionContext.Implicits.global +// | implicit def view(a: Int): String = "" +// | async { +// | await(0).length +// | } +// """.stripMargin +// }) +// val applyImplicitView = tree.collect { case x if x.getClass.getName.endsWith("ApplyImplicitView") => x } +// println(applyImplicitView) +// applyImplicitView.map(_.toString) mustStartWith List("view(") +// } @Test def nothingTypedIf(): Unit = { - import scala.async.internal.AsyncId.{async, await} val result = util.Try(async { if (true) { - val n = await(1) + val n = await(fut(1)) if (n < 2) { throw new RuntimeException("case a") } @@ -419,18 +425,17 @@ package scala.async.run.anf { else { "case c" } - }) + }.block) assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a") } @Test def nothingTypedMatch(): Unit = { - import scala.async.internal.AsyncId.{async, await} val result = util.Try(async { 0 match { case _ if "".isEmpty => - val n = await(1) + val n = await(fut(1)) n match { case _ if n < 2 => throw new RuntimeException("case a") @@ -440,7 +445,7 @@ package scala.async.run.anf { case _ => "case c" } - }) + }.block) assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a") } diff --git a/test/async/run/hygiene.scala b/test/async/run/hygiene.scala index 942e50357fc9..b9f7a9e2b2d8 100644 --- a/test/async/run/hygiene.scala +++ b/test/async/run/hygiene.scala @@ -4,11 +4,19 @@ package scala.async.run.hygiene { import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId - class HygieneSpec { + import scala.concurrent._ + import ExecutionContext.Implicits.global + import scala.async.Async._ + import scala.concurrent.duration.Duration + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ - import AsyncId.{async, await} + class HygieneSpec { @Test def `is hygenic`(): Unit = { @@ -22,15 +30,15 @@ package scala.async.run.hygiene { val z = await(resume()) (x, y, z) } - assertEquals((25, "result", "resume"), res) + assertEquals((25, "result", "resume"), block(res)) } @Test def `external var as result of await`(): Unit = { var ext = 0 - async { + block(async { ext = await(12) - } + }) assertEquals(12, ext) } @@ -38,12 +46,12 @@ package scala.async.run.hygiene { def `external var as result of await 2`(): Unit = { var ext = 0 val inp = 10 - async { + block(async { if (inp > 0) ext = await(12) else ext = await(10) - } + }) assertEquals(12, ext) } @@ -51,13 +59,13 @@ package scala.async.run.hygiene { def `external var as result of await 3`(): Unit = { var ext = 0 val inp = 10 - async { + block(async { val x = if (inp > 0) await(12) else await(10) ext = x + await(2) - } + }) assertEquals(14, ext) } @@ -66,14 +74,13 @@ package scala.async.run.hygiene { val state = 23 val result: Any = "result" def resume(): Any = "resume" - import AsyncId.{await, async} - val res = async { + val res = block(async { val f1 = async { state + 2 } val x = await(f1) - val y = await(async { result }) + val y = await(block(async { result })) val z = await(async(await(async { resume() }))) (x, y, z) - } + }) assertEquals(25, res._1) assertEquals("result", res._2) assertEquals("resume", res._3) diff --git a/test/async/run/ifelse0.scala b/test/async/run/ifelse0.scala index a9cafcfe2fe4..348a1d0d1e96 100644 --- a/test/async/run/ifelse0.scala +++ b/test/async/run/ifelse0.scala @@ -2,19 +2,23 @@ object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.ifelse package scala.async.run.ifelse0 { + import org.junit.Test + import org.junit.Assert._ import language.{reflectiveCalls, postfixOps} - import scala.concurrent.{Future, ExecutionContext, Await} + + import scala.concurrent._ import scala.concurrent.duration._ + import ExecutionContext.Implicits.global import scala.async.Async.{async, await} - import org.junit.Test - import org.junit.Assert._ - import scala.async.internal.AsyncId + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ class TestIfElseClass { - - import ExecutionContext.Implicits.global - def m1(x: Int): Future[Int] = Future { x + 2 } @@ -44,10 +48,9 @@ package scala.async.run.ifelse0 { } @Test def `await in condition`(): Unit = { - import AsyncId.{async, await} - val result = async { + val result = block(async { if ({await(true); await(true)}) await(1) else ??? - } + }) assertEquals(1, result) } } diff --git a/test/async/run/ifelse0_while.scala b/test/async/run/ifelse0_while.scala index 54bbc6f4ca23..863a5d04fac0 100644 --- a/test/async/run/ifelse0_while.scala +++ b/test/async/run/ifelse0_while.scala @@ -4,15 +4,23 @@ package scala.async.run.ifelse0 { import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ class WhileSpec { @Test def whiling1(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { var xxx: Int = 0 var y = 0 while (xxx < 3) { @@ -20,15 +28,14 @@ package scala.async.run.ifelse0 { xxx = xxx + 1 } y - } + }) assertEquals(2, result) } @Test def whiling2(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { var xxx: Int = 0 var y = 0 while (false) { @@ -36,15 +43,14 @@ package scala.async.run.ifelse0 { xxx = xxx + 1 } y - } + }) assertEquals(0, result) } @Test def nestedWhile(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { var sum = 0 var i = 0 while (i < 5) { @@ -56,27 +62,24 @@ package scala.async.run.ifelse0 { i += 1 } sum - } + }) assertEquals(100, result) } @Test def whileExpr(): Unit = { - import AsyncId._ - - val result = async { + val result = block(async { var cond = true while (cond) { cond = false await { 22 } } - } + }) assertEquals((), result) } @Test def doWhile(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { var b = 0 var x = "" await(do { @@ -86,31 +89,29 @@ package scala.async.run.ifelse0 { b += await(1) } while (b < 2)) await(x) - } + }) assertEquals("123123", result) } @Test def whileAwaitCondition(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { var b = true while(await(b)) { b = false } await(b) - } + }) assertEquals(false, result) } @Test def doWhileAwaitCondition(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { var b = true do { b = false } while(await(b)) b - } + }) assertEquals(false, result) } } diff --git a/test/async/run/late.scala b/test/async/run/late.scala deleted file mode 100644 index 51dbdb282a13..000000000000 --- a/test/async/run/late.scala +++ /dev/null @@ -1,612 +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.run.late - -import java.io.File - -import junit.framework.Assert.assertEquals -import org.junit.{Assert, Ignore, Test} - -import scala.annotation.StaticAnnotation -import scala.annotation.meta.{field, getter} -import scala.async.internal.AsyncId -import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader -import scala.tools.nsc._ -import scala.tools.nsc.plugins.{Plugin, PluginComponent} -import scala.tools.nsc.reporters.StoreReporter -import scala.tools.nsc.transform.TypingTransformers - -// Tests for customized use of the async transform from a compiler plugin, which -// calls it from a new phase that runs after patmat. -class LateExpansion { - - @Test def testRewrittenApply(): Unit = { - val result = wrapAndRun( - """ - | object O { - | case class Foo(a: Any) - | } - | @autoawait def id(a: String) = a - | O.Foo - | id("foo") + id("bar") - | O.Foo(1) - | """.stripMargin) - assertEquals("Foo(1)", result.toString) - } - - @Ignore("Need to use adjustType more pervasively in AsyncTransform, but that exposes bugs in {Type, ... }Symbol's cache invalidation") - @Test def testIsInstanceOfType(): Unit = { - val result = wrapAndRun( - """ - | class Outer - | @autoawait def id(a: String) = a - | val o = new Outer - | id("foo") + id("bar") - | ("": Object).isInstanceOf[o.type] - | """.stripMargin) - assertEquals(false, result) - } - - @Test def testIsInstanceOfTerm(): Unit = { - val result = wrapAndRun( - """ - | class Outer - | @autoawait def id(a: String) = a - | val o = new Outer - | id("foo") + id("bar") - | o.isInstanceOf[Outer] - | """.stripMargin) - assertEquals(true, result) - } - - @Test def testArrayLocalModule(): Unit = { - val result = wrapAndRun( - """ - | class Outer - | @autoawait def id(a: String) = a - | val O = "" - | id("foo") + id("bar") - | new Array[O.type](0) - | """.stripMargin) - assertEquals(classOf[Array[String]], result.getClass) - } - - @Test def test0(): Unit = { - val result = wrapAndRun( - """ - | @autoawait def id(a: String) = a - | id("foo") + id("bar") - | """.stripMargin) - assertEquals("foobar", result) - } - - @Test def testGuard(): Unit = { - val result = wrapAndRun( - """ - | @autoawait def id[A](a: A) = a - | "" match { case _ if id(false) => ???; case _ => "okay" } - | """.stripMargin) - assertEquals("okay", result) - } - - @Test def testExtractor(): Unit = { - val result = wrapAndRun( - """ - | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) } - | "" match { case Extractor(a, b) if "".isEmpty => a == b } - | """.stripMargin) - assertEquals(true, result) - } - - @Test def testNestedMatchExtractor(): Unit = { - val result = wrapAndRun( - """ - | object Extractor { @autoawait def unapply(a: String) = Some((a, a)) } - | "" match { - | case _ if "".isEmpty => - | "" match { case Extractor(a, b) => a == b } - | } - | """.stripMargin) - assertEquals(true, result) - } - - @Test def testCombo(): Unit = { - val result = wrapAndRun( - """ - | object Extractor1 { @autoawait def unapply(a: String) = Some((a + 1, a + 2)) } - | object Extractor2 { @autoawait def unapply(a: String) = Some(a + 3) } - | @autoawait def id(a: String) = a - | println("Test.test") - | val r1 = Predef.identity("blerg") match { - | case x if " ".isEmpty => "case 2: " + x - | case Extractor1(Extractor2(x), y: String) if x == "xxx" => "case 1: " + x + ":" + y - | x match { - | case Extractor1(Extractor2(x), y: String) => - | case _ => - | } - | case Extractor2(x) => "case 3: " + x - | } - | r1 - | """.stripMargin) - assertEquals("case 3: blerg3", result) - } - - @Test def polymorphicMethod(): Unit = { - val result = run( - """ - |import scala.async.run.late.{autoawait,lateasync} - |object Test { - | class C { override def toString = "C" } - | @autoawait def foo[A <: C](a: A): A = a - | @lateasync - | def test1[CC <: C](c: CC): (CC, CC) = { - | val x: (CC, CC) = 0 match { case _ if false => ???; case _ => (foo(c), foo(c)) } - | x - | } - | def test(): (C, C) = test1(new C) - |} - | """.stripMargin) - assertEquals("(C,C)", result.toString) - } - - @Test def shadowing(): Unit = { - val result = run( - """ - |import scala.async.run.late.{autoawait,lateasync} - |object Test { - | trait Foo - | trait Bar extends Foo - | @autoawait def boundary = "" - | @lateasync - | def test: Unit = { - | (new Bar {}: Any) match { - | case foo: Bar => - | boundary - | 0 match { - | case _ => foo; () - | } - | () - | } - | () - | } - |} - | """.stripMargin) - } - - @Test def shadowing0(): Unit = { - val result = run( - """ - |import scala.async.run.late.{autoawait,lateasync} - |object Test { - | trait Foo - | trait Bar - | def test: Any = test(new C) - | @autoawait def asyncBoundary: String = "" - | @lateasync - | def test(foo: Foo): Foo = foo match { - | case foo: Bar => - | val foo2: Foo with Bar = new Foo with Bar {} - | asyncBoundary - | null match { - | case _ => foo2 - | } - | case other => foo - | } - | class C extends Foo with Bar - |} - | """.stripMargin) - } - - @Test def shadowing2(): Unit = { - val result = run( - """ - |import scala.async.run.late.{autoawait,lateasync} - |object Test { - | trait Base; trait Foo[T <: Base] { @autoawait def func: Option[Foo[T]] = None } - | class Sub extends Base - | trait Bar extends Foo[Sub] - | def test: Any = test(new Bar {}) - | @lateasync - | def test[T <: Base](foo: Foo[T]): Foo[T] = foo match { - | case foo: Bar => - | val res = foo.func - | res match { - | case _ => - | } - | foo - | case other => foo - | } - | test(new Bar {}) - |} - | """.stripMargin) - } - - @Test def patternAlternative(): Unit = { - val result = wrapAndRun( - """ - | @autoawait def one = 1 - | - | @lateasync def test = { - | Option(true) match { - | case null | None => false - | case Some(v) => one; v - | } - | } - | """.stripMargin) - } - - @Test def patternAlternativeBothAnnotations(): Unit = { - val result = wrapAndRun( - """ - |import scala.async.run.late.{autoawait,lateasync} - |object Test { - | @autoawait def func1() = "hello" - | @lateasync def func(a: Option[Boolean]) = a match { - | case null | None => func1 + " world" - | case _ => "okay" - | } - | def test: Any = func(None) - |} - | """.stripMargin) - } - - @Test def shadowingRefinedTypes(): Unit = { - val result = run( - s""" - |import scala.async.run.late.{autoawait,lateasync} - |trait Base - |class Sub extends Base - |trait Foo[T <: Base] { - | @autoawait def func: Option[Foo[T]] = None - |} - |trait Bar extends Foo[Sub] - |object Test { - | @lateasync def func[T <: Base](foo: Foo[T]): Foo[T] = foo match { // the whole pattern match will be wrapped with async{ } - | case foo: Bar => - | val res = foo.func // will be rewritten into: await(foo.func) - | res match { - | case Some(v) => v // this will report type mismtach - | case other => foo - | } - | case other => foo - | } - | def test: Any = { val b = new Bar{}; func(b) == b } - |}""".stripMargin) - assertEquals(true, result) - } - - @Test def testMatchEndIssue(): Unit = { - val result = run( - """ - |import scala.async.run.late.{autoawait,lateasync} - |sealed trait Subject - |final class Principal(val name: String) extends Subject - |object Principal { - | def unapply(p: Principal): Option[String] = Some(p.name) - |} - |object Test { - | @autoawait @lateasync - | def containsPrincipal(search: String, value: Subject): Boolean = value match { - | case Principal(name) if name == search => true - | case Principal(name) => containsPrincipal(search, value) - | case other => false - | } - | - | @lateasync - | def test = containsPrincipal("test", new Principal("test")) - |} - | """.stripMargin) - } - - @Test def testGenericTypeBoundaryIssue(): Unit = { - val result = run( - """ - - import scala.async.run.late.{autoawait,lateasync} - trait InstrumentOfValue - trait Security[T <: InstrumentOfValue] extends InstrumentOfValue - class Bound extends Security[Bound] - class Futures extends Security[Futures] - object TestGenericTypeBoundIssue { - @autoawait @lateasync def processBound(bound: Bound): Unit = { println("process Bound") } - @autoawait @lateasync def processFutures(futures: Futures): Unit = { println("process Futures") } - @autoawait @lateasync def doStuff(sec: Security[_]): Unit = { - sec match { - case bound: Bound => processBound(bound) - case futures: Futures => processFutures(futures) - case _ => throw new Exception("Unknown Security type: " + sec) - } - } - } - object Test { @lateasync def test: Unit = TestGenericTypeBoundIssue.doStuff(new Bound) } - """.stripMargin) - } - - @Test def testReturnTupleIssue(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - class TestReturnExprIssue(str: String) { - @autoawait @lateasync def getTestValue = Some(42) - @autoawait @lateasync def doStuff: Int = { - val opt: Option[Int] = getTestValue // here we have an async method invoke - opt match { - case Some(li) => li // use the result somehow - case None => - } - 42 // type mismatch; found : AnyVal required: Int - } - } - object Test { @lateasync def test: Unit = new TestReturnExprIssue("").doStuff } - """.stripMargin) - } - - - @Test def testAfterRefchecksIssue(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - trait Factory[T] { def create: T } - sealed trait TimePoint - class TimeLine[TP <: TimePoint](val tpInitial: Factory[TP]) { - @autoawait @lateasync private[TimeLine] val tp: TP = tpInitial.create - @autoawait @lateasync def timePoint: TP = tp - } - object Test { - def test: Unit = () - } - """) - } - - @Test def testArrayIndexOutOfBoundIssue(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - - sealed trait Result - case object A extends Result - case object B extends Result - case object C extends Result - - object Test { - protected def doStuff(res: Result) = { - class C { - @autoawait def needCheck = false - - @lateasync def m = { - if (needCheck) "NO" - else { - res match { - case A => 1 - case _ => 2 - } - } - } - } - } - - - @lateasync - def test() = doStuff(B) - } - """) - } - - def wrapAndRun(code: String): Any = { - run( - s""" - |import scala.async.run.late.{autoawait,lateasync} - |object Test { - | @lateasync - | def test: Any = { - | $code - | } - |} - | """.stripMargin) - } - - - @Test def testNegativeArraySizeException(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - - object Test { - def foo(foo: Any, bar: Any) = () - @autoawait def getValue = 4.2 - @lateasync def func(f: Any) = { - foo(f match { case _ if "".isEmpty => 2 }, getValue); - } - - @lateasync - def test() = func(4) - } - """) - } - - @Test def testNegativeArraySizeExceptionFine1(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - case class FixedFoo(foo: Int) - class Foobar(val foo: Int, val bar: Double) { - @autoawait @lateasync def getValue = 4.2 - @autoawait @lateasync def func(f: Any) = { - new Foobar(foo = f match { - case FixedFoo(x) => x - case _ => 2 - }, - bar = getValue) - } - } - object Test { - @lateasync def test() = new Foobar(0, 0).func(4) - } - """) - } - - @Test def testByNameOwner(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - object Bleh { - @autoawait @lateasync def asyncCall(): Int = 0 - def byName[T](fn: => T): T = fn - } - object Boffo { - @autoawait @lateasync def jerk(): Unit = { - val pointlessSymbolOwner = 1 match { - case _ => - Bleh.asyncCall() - Bleh.byName { - val whyDoHateMe = 1 - whyDoHateMe - } - } - } - } - object Test { - @lateasync def test() = Boffo.jerk() - } - """) - } - - @Test def testByNameOwner2(): Unit = { - val result = run( - """ - import scala.async.run.late.{autoawait,lateasync} - object Bleh { - @autoawait @lateasync def bleh = Bleh - def byName[T](fn: => T): T = fn - } - object Boffo { - @autoawait @lateasync def slob(): Unit = { - val pointlessSymbolOwner = { - Bleh.bleh.byName { - val whyDoHateMeToo = 1 - whyDoHateMeToo - } - } - } - } - object Test { - @lateasync def test() = Boffo.slob() - } - """) - } - - private def createTempDir(): File = { - val f = File.createTempFile("output", "") - f.delete() - f.mkdirs() - f - } - - def run(code: String): Any = { - val out = createTempDir() - try { - val reporter = new StoreReporter - val settings = new Settings(println(_)) - settings.outdir.value = out.getAbsolutePath - settings.embeddedDefaults(getClass.getClassLoader) - // settings.processArgumentString("-Xprint:patmat,postpatmat,jvm -nowarn") - val isInSBT = !settings.classpath.isSetByUser - if (isInSBT) settings.usejavacp.value = true - val global = new Global(settings, reporter) { - self => - - object late extends { - val global: self.type = self - } with LatePlugin - - override protected def loadPlugins(): List[Plugin] = late :: Nil - } - import global._ - - val run = new Run - val source = newSourceFile(code) - // TreeInterrogation.withDebug { - run.compileSources(source :: Nil) - // } - Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors) - val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader) - val cls = loader.loadClass("Test") - cls.getMethod("test").invoke(null) - } finally { - scala.reflect.io.Path.apply(out).deleteRecursively() - } - } -} - -abstract class LatePlugin extends Plugin { - - import global._ - - override val components: List[PluginComponent] = List(new PluginComponent with TypingTransformers { - val global: LatePlugin.this.global.type = LatePlugin.this.global - - lazy val asyncIdSym = symbolOf[AsyncId.type] - lazy val asyncSym = asyncIdSym.info.member(TermName("async")) - lazy val awaitSym = asyncIdSym.info.member(TermName("await")) - lazy val autoAwaitSym = symbolOf[autoawait] - lazy val lateAsyncSym = symbolOf[lateasync] - - def newTransformer(unit: CompilationUnit) = new TypingTransformer(unit) { - override def transform(tree: Tree): Tree = { - super.transform(tree) match { - case ap@Apply(fun, args) if fun.symbol.hasAnnotation(autoAwaitSym) => - localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(ap.tpe) :: Nil), ap :: Nil)) - case sel@Select(fun, _) if sel.symbol.hasAnnotation(autoAwaitSym) && !(tree.tpe.isInstanceOf[MethodTypeApi] || tree.tpe.isInstanceOf[PolyTypeApi]) => - localTyper.typed(Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, awaitSym), TypeTree(sel.tpe) :: Nil), sel :: Nil)) - case dd: DefDef if dd.symbol.hasAnnotation(lateAsyncSym) => atOwner(dd.symbol) { - deriveDefDef(dd) { rhs: Tree => - val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs)) - localTyper.typed(atPos(dd.pos)(invoke)) - } - } - case vd: ValDef if vd.symbol.hasAnnotation(lateAsyncSym) => atOwner(vd.symbol) { - deriveValDef(vd) { rhs: Tree => - val invoke = Apply(TypeApply(gen.mkAttributedRef(asyncIdSym.typeOfThis, asyncSym), TypeTree(rhs.tpe) :: Nil), List(rhs)) - localTyper.typed(atPos(vd.pos)(invoke)) - } - } - case vd: ValDef => - vd - case x => x - } - } - } - - override def newPhase(prev: Phase): Phase = new StdPhase(prev) { - override def apply(unit: CompilationUnit): Unit = { - val translated = newTransformer(unit).transformUnit(unit) - //println(show(unit.body)) - translated - } - } - - override val runsAfter: List[String] = "refchecks" :: Nil - override val phaseName: String = "postpatmat" - - }) - override val description: String = "postpatmat" - override val name: String = "postpatmat" -} - -// Methods with this annotation are translated to having the RHS wrapped in `AsyncId.async { }` -@field -final class lateasync extends StaticAnnotation - -// Calls to methods with this annotation are translated to `AsyncId.await()` -@getter -final class autoawait extends StaticAnnotation diff --git a/test/async/run/lazyval.scala b/test/async/run/lazyval.scala index 4abf6d6dc6f6..c94f3e6f1bba 100644 --- a/test/async/run/lazyval.scala +++ b/test/async/run/lazyval.scala @@ -4,12 +4,21 @@ package scala.async.run.lazyval { import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId._ + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ class LazyValSpec { @Test def lazyValAllowed(): Unit = { - val result = async { + val result = block(async { var x = 0 lazy val y = { x += 1; 42 } assert(x == 0, x) @@ -19,7 +28,8 @@ package scala.async.run.lazyval { identity(y) assert(x == 1, x) result - } + }) + assertEquals(43, result) } } diff --git a/test/async/run/localclasses.scala b/test/async/run/localclasses.scala index db9bc201e86a..b3813f2bd836 100644 --- a/test/async/run/localclasses.scala +++ b/test/async/run/localclasses.scala @@ -4,22 +4,29 @@ package scala.async.neg { import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ class LocalClasses0Spec { @Test def localClassCrashIssue16(): Unit = { - import AsyncId.{async, await} - assertEquals(1, async { + assertEquals(1, block(async { class B { def f = 1 } await(new B()).f - }) + })) } @Test def nestedCaseClassAndModuleAllowed(): Unit = { - import AsyncId.{await, async} - assertEquals("bob", async { + assertEquals("bob", block(async { trait Base { def base = 0} await(0) case class Person(name: String) extends Base @@ -27,7 +34,7 @@ package scala.async.neg { val x = Person(await(fut)) x.base x.name - }) + })) } } diff --git a/test/async/run/match0.scala b/test/async/run/match0.scala index 9c47df9a40cb..8af85f7154f6 100644 --- a/test/async/run/match0.scala +++ b/test/async/run/match0.scala @@ -3,18 +3,23 @@ object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.match0 package scala.async.run.match0 { import language.{reflectiveCalls, postfixOps} - import scala.concurrent.{Future, ExecutionContext, Await} - import scala.concurrent.duration._ - import scala.async.Async.{async, await} + import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ class TestMatchClass { - import ExecutionContext.Implicits.global - def m1(x: Int): Future[Int] = Future { x + 2 } @@ -66,21 +71,20 @@ package scala.async.run.match0 { } @Test def `support await in a match expression with binds`(): Unit = { - val result = AsyncId.async { + val result = block(async { val x = 1 Option(x) match { case op @ Some(x) => assert(op.contains(1)) - x + AsyncId.await(x) - case None => AsyncId.await(0) + x + await(x) + case None => await(0) } - } - assertEquals(2, res) + }) + assertEquals(2, result) } @Test def `support await referring to pattern matching vals`(): Unit = { - import AsyncId.{async, await} - val result = async { + val result = block(async { val x = 1 val opt = Some("") await(0) @@ -92,53 +96,49 @@ package scala.async.run.match0 { await(0) await((o, y.isEmpty)) - } + }) assertEquals((Some(""), true), result) } @Test def `await in scrutinee`(): Unit = { - import AsyncId.{async, await} - val result = async { + val result = block(async { await(if ("".isEmpty) await(1) else ???) match { case x if x < 0 => ??? case y: Int => y * await(3) } - } + }) assertEquals(3, result) } @Test def duplicateBindName(): Unit = { - import AsyncId.{async, await} - def m4(m: Any) = async { + def m4(m: Any) = block(async { m match { case buf: String => await(0) case buf: Double => await(2) } - } + }) assertEquals(0, m4("")) } @Test def bugCastBoxedUnitToStringMatch(): Unit = { - import scala.async.internal.AsyncId.{async, await} - def foo = async { + def foo = block(async { val p2 = await(5) "foo" match { case p3: String => p2.toString } - } + }) assertEquals("5", foo) } @Test def bugCastBoxedUnitToStringIf(): Unit = { - import scala.async.internal.AsyncId.{async, await} - def foo = async { + def foo = block(async { val p2 = await(5) if (true) p2.toString else p2.toString - } + }) assertEquals("5", foo) } } diff --git a/test/async/run/nesteddef.scala b/test/async/run/nesteddef.scala index cb789cb17188..854db64c18cc 100644 --- a/test/async/run/nesteddef.scala +++ b/test/async/run/nesteddef.scala @@ -4,63 +4,67 @@ package scala.async.run.nesteddef { import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId - + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ class NestedDef { @Test def nestedDef(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { val a = 0 val x = await(a) - 1 val local = 43 def bar(d: Double) = -d + a + local def foo(z: Any) = (a.toDouble, bar(x).toDouble, z) foo(await(2)) - } + }) assertEquals((0d, 44d, 2), result) } @Test def nestedFunction(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { val a = 0 val x = await(a) - 1 val local = 43 val bar = (d: Double) => -d + a + local val foo = (z: Any) => (a.toDouble, bar(x).toDouble, z) foo(await(2)) - } + }) assertEquals((0d, 44d, 2), result) } // We must lift `foo` and `bar` in the next two tests. @Test def nestedDefTransitive1(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { val a = 0 val x = await(a) - 1 def bar = a def foo = bar foo - } + }) assertEquals(0, result) } @Test def nestedDefTransitive2(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { val a = 0 val x = await(a) - 1 def bar = a def foo = bar 0 - } + }) assertEquals(0, result) } @@ -68,28 +72,26 @@ package scala.async.run.nesteddef { // checking that our use/definition analysis doesn't cycle. @Test def mutuallyRecursive1(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { val a = 0 val x = await(a) - 1 def foo: Int = if (true) 0 else bar def bar: Int = if (true) 0 else foo bar - } + }) assertEquals(0, result) } // checking that our use/definition analysis doesn't cycle. @Test def mutuallyRecursive2(): Unit = { - import AsyncId._ - val result = async { + val result = block(async { val a = 0 def foo: Int = if (true) 0 else bar def bar: Int = if (true) 0 else foo val x = await(a) - 1 bar - } + }) assertEquals(0, result) } } diff --git a/test/async/run/noawait.scala b/test/async/run/noawait.scala index 2d887702f3b9..6d74c4dd7226 100644 --- a/test/async/run/noawait.scala +++ b/test/async/run/noawait.scala @@ -2,33 +2,42 @@ object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.noawai package scala.async.run.noawait { - import scala.async.internal.AsyncId - import AsyncId._ import org.junit.Test import org.junit.Assert._ + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ + class NoAwaitSpec { @Test def `async block without await`(): Unit = { def foo = 1 - assertEquals(foo, async { + assertEquals(foo, block(async { foo foo - }) + })) } @Test def `async block without await 2`(): Unit = { - assertEquals(1, async { + assertEquals(1, block(async { def x = 0 if (x > 0) 0 else 1 - }) + })) } @Test def `async expr without await`(): Unit = { def foo = 1 - assertEquals(foo, async(foo)) + assertEquals(foo, block(async(foo))) } } diff --git a/test/async/run/stackoverflow.scala b/test/async/run/stackoverflow.scala index b8d08b25b6ea..75f32fc11bfd 100644 --- a/test/async/run/stackoverflow.scala +++ b/test/async/run/stackoverflow.scala @@ -1,8 +1,17 @@ -import scala.async.internal.AsyncId._ +import scala.concurrent._ +import scala.concurrent.duration._ +import ExecutionContext.Implicits.global +import scala.async.Async.{async, await} +object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) +} +import TestUtil._ object Test extends App { - async { + block( async { var i = 100000000 while (i > 0) { if (false) { @@ -10,6 +19,5 @@ object Test extends App { } i -= 1 } - } - + }) } diff --git a/test/async/run/toughtype.scala.scala b/test/async/run/toughtype.scala similarity index 92% rename from test/async/run/toughtype.scala.scala rename to test/async/run/toughtype.scala index b57fe038cab4..220654f46d1a 100644 --- a/test/async/run/toughtype.scala.scala +++ b/test/async/run/toughtype.scala @@ -3,13 +3,19 @@ object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.tought package scala.async.run.toughtype { import language.{reflectiveCalls, postfixOps} - import scala.concurrent._ - import scala.concurrent.duration._ - import scala.async.Async._ import org.junit.Test import org.junit.Assert._ - import scala.async.internal.AsyncId + import scala.concurrent._ + import scala.concurrent.duration._ + import ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) + } + import TestUtil._ object ToughTypeObject { @@ -33,39 +39,35 @@ package scala.async.run.toughtype { } @Test def patternMatchingPartialFunction(): Unit = { - import AsyncId.{await, async} - assertEquals(3, async { + assertEquals(3, block(async { await(1) val a = await(1) val f = { case x => x + a }: PartialFunction[Int, Int] await(f(2)) - }) + })) } @Test def patternMatchingPartialFunctionNested(): Unit = { - import AsyncId.{await, async} - assertEquals(-3, async { + assertEquals(-3, block(async { await(1) val neg1 = -1 val a = await(1) val f = { case x => ({case x => neg1 * x}: PartialFunction[Int, Int])(x + a) }: PartialFunction[Int, Int] await(f(2)) - }) + })) } @Test def patternMatchingFunction(): Unit = { - import AsyncId.{await, async} - assertEquals(3, async { + assertEquals(3, block(async { await(1) val a = await(1) val f = { case x => x + a }: Function[Int, Int] await(f(2)) - }) + })) } @Test def existentialBindIssue19(): Unit = { - import AsyncId.{await, async} - def m7(a: Any) = async { + def m7(a: Any) = block(async { a match { case s: Seq[_] => val x = s.size @@ -73,7 +75,7 @@ package scala.async.run.toughtype { ss = s await(x) } - } + }) assertEquals(0, m7(Nil)) } @@ -92,20 +94,18 @@ package scala.async.run.toughtype { } @Test def singletonTypeIssue17(): Unit = { - import AsyncId.{async, await} class A { class B } - async { + block(async { val a = new A def foo(b: a.B) = 0 await(foo(new a.B)) - } + }) } @Test def existentialMatch(): Unit = { - import AsyncId.{async, await} trait Container[+A] case class ContainerImpl[A](value: A) extends Container[A] - def foo: Container[_] = async { + def foo: Container[_] = block(async { val a: Any = List(1) a match { case buf: Seq[_] => @@ -113,15 +113,14 @@ package scala.async.run.toughtype { val e0 = buf(0) ContainerImpl(e0) } - } + }) foo } @Test def existentialIfElse0(): Unit = { - import AsyncId.{async, await} trait Container[+A] case class ContainerImpl[A](value: A) extends Container[A] - def foo: Container[_] = async { + def foo: Container[_] = block(async { val a: Any = List(1) if (true) { val buf: Seq[_] = List(1) @@ -129,7 +128,7 @@ package scala.async.run.toughtype { val e0 = buf(0) ContainerImpl(e0) } else ??? - } + }) foo } @@ -153,7 +152,6 @@ package scala.async.run.toughtype { import scala.concurrent.{Future, ExecutionContext, Await} import scala.concurrent.duration._ import scala.async.Async.{async, await} - import scala.async.internal.AsyncId class Foo[A] @@ -319,15 +317,14 @@ package scala.async.run.toughtype { // https://github.com/scala/async/issues/106 @Test def valueClassT106(): Unit = { - import scala.async.internal.AsyncId._ - async { + block(async { "whatever value" match { case _ => await("whatever return type") new IntWrapper("value class matters") } "whatever return type" - } + }) } } diff --git a/test/async/run/uncheckedBounds.scala b/test/async/run/uncheckedBounds.scala deleted file mode 100644 index 435a14bea7a8..000000000000 --- a/test/async/run/uncheckedBounds.scala +++ /dev/null @@ -1,47 +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 -package run -package uncheckedBounds - -import org.junit.{Test, Assert} -import scala.async.TreeInterrogation - -class UncheckedBoundsSpec { - @Test def insufficientLub_SI_7694(): Unit = { - eval( s""" - object Test { - import _root_.scala.async.run.toughtype._ - import _root_.scala.async.internal.AsyncId.{async, await} - async { - (if (true) await(null: L[A, A]) else await(null: L[B, B])) - } - } - """, compileOptions = s"-cp ${toolboxClasspath} ") - } - - @Test def insufficientLub_SI_7694_ScalaConcurrent(): Unit = { - eval( s""" - object Test { - import _root_.scala.async.run.toughtype._ - import _root_.scala.async.Async.{async, await} - import scala.concurrent._ - import scala.concurrent.ExecutionContext.Implicits.global - async { - (if (true) await(null: Future[L[A, A]]) else await(null: Future[L[B, B]])) - } - } - """, compileOptions = s"-cp ${toolboxClasspath} ") - } - -} diff --git a/test/async/run/warning.scala b/test/async/run/warning.scala index 155794d36ec1..d9584e21ab5d 100644 --- a/test/async/run/warning.scala +++ b/test/async/run/warning.scala @@ -28,7 +28,7 @@ class WarningsSpec { // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses" tb.eval(tb.parse { """ - | import scala.async.internal.AsyncId._ + | import scala.tools.nsc.transform.async.user.AsyncId._ | async { | if ("".isEmpty) { | await(println("hello")) From 50150ca8b0703cdd514bdfeca9a6f6ba2a151ffa Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 18 Feb 2020 10:54:13 +1000 Subject: [PATCH 12/94] Fix nested async transforms UseFields needs to do what explicit outer would have done: access inner classes must access fields via the outer parameter and we must ensure fields accessed from inners are not JVM private. --- .../nsc/transform/async/AsyncTransform.scala | 11 +++++++--- .../nsc/async/AnnotationDrivenAsync.scala | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index ce33153381a0..e66e15de43d3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -223,9 +223,14 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li // Replace the ValDefs in the async block with Assigns to the corresponding lifted // fields. Similarly, replace references to them with references to the field. - object UseFields extends TypingTransformer(currentTransformState.unit) { - private def fieldSel(tree: Tree) = - atPos(tree.pos)(Select(This(stateMachineClass).setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) + object UseFields extends explicitOuter.OuterPathTransformer(currentTransformState.unit) { + private def fieldSel(tree: Tree) = { + val outerOrThis = if (stateMachineClass == currentClass) This(stateMachineClass) else { + tree.symbol.makeNotPrivate(tree.symbol.owner) + outerPath(outerValue, currentClass.outerClass, stateMachineClass) + } + atPos(tree.pos)(Select(outerOrThis.setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) + } override def transform(tree: Tree): Tree = tree match { case _ if currentOwner == stateMachineClass => super.transform(tree) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index b46da2279baf..a811b37c5828 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -34,6 +34,26 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def testScalaConcurrentAsyncNested(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.async.Async.{async, await} + | + |object Test { + | def foo[T](a0: Int)(b0: Int*) = s"a0 = $a0, b0 = ${b0.head}" + | + | def test: String = Await.result(async { + | var i = 0 + | def get = async{i += 1; i} + | foo[Int](await(get))(await(get) :: await(async(Nil)) : _*) + | }, Duration.Inf) + |} + |""".stripMargin + assertEquals("a0 = 1, b0 = 2", run(code)) + } + @Test def testCustomAsync(): Unit = { val code = """ @@ -204,6 +224,8 @@ class AnnotationDrivenAsync { settings.embeddedDefaults(getClass.getClassLoader) // settings.debug.value = true // settings.processArgumentString("-Xprint:all -nowarn") + // sys.props("scala.async.trace") = "true" + // sys.props("scala.async.debug") = "true" val isInSBT = !settings.classpath.isSetByUser if (isInSBT) settings.usejavacp.value = true val global = new Global(settings, reporter) { From 8de6b1bca3825462b4fca29b26ff4bc499a5d4bf Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 13:36:16 +1000 Subject: [PATCH 13/94] We'll never see `isAsync` again in nested trees. This code path was only relevant when async was run as a macro in the presentation compiler. --- .../nsc/transform/async/AsyncAnalysis.scala | 3 +-- .../nsc/transform/async/TransformUtils.scala | 18 ++++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index 7d2ffe1ae2a5..c0447bbd6a80 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -80,8 +80,7 @@ trait AsyncAnalysis extends TransformUtils { val badAwaits = ListBuffer[Tree]() object traverser extends Traverser { override def traverse(tree: Tree): Unit = { - if (!isAsync(tree)) - super.traverse(tree) + super.traverse(tree) tree match { case rt: RefTree if isAwait(rt) => badAwaits += rt diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index df64fa7da8c3..f5230a5e2632 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -302,8 +302,6 @@ private[async] trait TransformUtils extends PhasedTransform { override def traverse(tree: Tree): Unit = { tree match { - case _ if isAsync(tree) => - // Under -Ymacro-expand:discard, used in the IDE, nested async blocks will be visible to the outer blocks case cd: ClassDef => nestedClass(cd) case md: ModuleDef => nestedModule(md) case dd: DefDef => nestedMethod(dd) @@ -367,7 +365,7 @@ private[async] trait TransformUtils extends PhasedTransform { private def treeCannotContainAwait(t: Tree) = t match { case _: CannotHaveAttrs => true case _: Ident | _: TypeTree | _: Literal => true - case _ => isAsync(t) + case _ => false } private def attachContainsAwait(t: Tree): Unit = if (shouldAttach(t)) { t.updateAttachment(ContainsAwait) @@ -382,15 +380,11 @@ private[async] trait TransformUtils extends PhasedTransform { override def traverse(tree: Tree): Unit = { stack ::= tree try { - if (isAsync(tree)) { - ; - } else { - if (isAwait(tree)) - stack.foreach(attachContainsAwait) - else - attachNoAwait(tree) - super.traverse(tree) - } + if (isAwait(tree)) + stack.foreach(attachContainsAwait) + else + attachNoAwait(tree) + super.traverse(tree) } finally stack = stack.tail } } From 9666175e0dbc43b23d690045cb6598e5f1f16b00 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 18 Feb 2020 11:40:25 +1000 Subject: [PATCH 14/94] Deal with await in ArrayValue As a late-running ANF transform must do. --- .../scala/tools/nsc/transform/async/AnfTransform.scala | 4 ++++ test/async/run/anf.scala | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 313407fc4df2..f59d6ce2e179 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -218,6 +218,10 @@ private[async] trait AnfTransform extends TransformUtils { val stats :+ expr1 = linearize.transformToList(expr) stats :+ treeCopy.Typed(tree, expr1, tpt) + case ArrayValue(elemtp, elems) => + val blks = elems.map(elem => linearize.transformToBlock(elem)) + blks.flatMap(_.stats) :+ treeCopy.ArrayValue(tree, elemtp, blks.map(_.expr)) + case Applied(fun, targs, argss) if argss.nonEmpty => // we can assume that no await call appears in a by-name argument position, // this has already been checked. diff --git a/test/async/run/anf.scala b/test/async/run/anf.scala index 44f8b0513dda..2195a9a55969 100644 --- a/test/async/run/anf.scala +++ b/test/async/run/anf.scala @@ -430,6 +430,15 @@ package scala.async.run.anf { assert(result.asInstanceOf[util.Failure[_]].exception.getMessage == "case a") } + @Test + def awaitInArrayValue(): Unit = { + val result = async { + Array(1, await(fut(2)), await(fut(3))).sum + }.block + + result mustBe 6 + } + @Test def nothingTypedMatch(): Unit = { val result = util.Try(async { From e82ed140819be990df4c9bf0fa8fd5c8fb9ae8f3 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 10:54:36 +1000 Subject: [PATCH 15/94] Live variables: detect usages in Awaitable.expr --- .../scala/tools/nsc/transform/async/LiveVariables.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 0d38387e1111..34722c6d6a7c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -131,6 +131,10 @@ trait LiveVariables extends ExprBuilder { val findUses = new FindUseTraverser findUses.traverse(Block(as.stats: _*)) + as match { + case aswa: AsyncStateWithAwait => findUses.traverse(aswa.awaitable.expr) + case _ => + } ReferencedFields(findUses.usedFields, findUses.capturedFields) } case class ReferencedFields(used: Set[Symbol], captured: Set[Symbol]) { From 0619d4e876d0aa861385ba140052430950fcd1fe Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 24 Feb 2020 17:38:52 +1000 Subject: [PATCH 16/94] Companion Detection should be post-erasure aware Deal with the representation of local modules at async's new place in the compiler pipeline. Use more efficient/idiomatic collection of local def trees. (`Tree#children` creates temporary lists.) --- .../tools/nsc/transform/async/Lifter.scala | 105 +++++++++++------- .../nsc/async/AnnotationDrivenAsync.scala | 24 ++++ 2 files changed, 88 insertions(+), 41 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index 3a01ff64402b..482dc750d3f3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -27,43 +27,63 @@ trait Lifter extends ExprBuilder { def liftables(asyncStates: List[AsyncState]): List[Tree] = { object companionship { private val companions = collection.mutable.Map[Symbol, Symbol]() - private val companionsInverse = collection.mutable.Map[Symbol, Symbol]() - private def record(sym1: Symbol, sym2: Symbol): Unit = { - companions(sym1) = sym2 - companions(sym2) = sym1 - } - def record(defs: List[Tree]): Unit = { - // Keep note of local companions so we rename them consistently - // when lifting. + def record(classes: Seq[Symbol], moduleClasses: Seq[Symbol]): Unit = { + // Keep note of local companions so we rename them consistently when lifting. for { - cd@ClassDef(_, _, _, _) <- defs - md@ModuleDef(_, _, _) <- defs - if (cd.name.toTermName == md.name) - } record(cd.symbol, md.symbol) + cd <- classes + md <- moduleClasses + if (cd.name == md.name) + } { + companions(cd) = md + companions(md) = cd + } } def companionOf(sym: Symbol): Symbol = { - companions.get(sym).orElse(companionsInverse.get(sym)).getOrElse(NoSymbol) + companions.getOrElse(sym, NoSymbol) } } val defs: mutable.LinkedHashMap[Tree, Int] = { /** Collect the DefTrees directly enclosed within `t` that have the same owner */ - def collectDirectlyEnclosedDefs(t: Tree): List[DefTree] = t match { - case ld: LabelDef => Nil - case dt: DefTree => dt :: Nil - case _: Function => Nil - case t => - val childDefs = t.children.flatMap(collectDirectlyEnclosedDefs(_)) - companionship.record(childDefs) - childDefs + object traverser extends Traverser { + val childDefs = mutable.ArrayBuffer[Tree]() + private val classesBuffer, moduleClassesBuffer = mutable.ArrayBuffer[Symbol]() + override def traverse(tree: Tree): Unit = tree match { + case _: LabelDef => + case _: DefTree => childDefs += tree + case _: Function => Nil + case Block(stats, expr) => + classesBuffer.clear() + moduleClassesBuffer.clear() + for (stat <- stats) { + stat match { + case _: ClassDef => + val sym = stat.symbol + val buffer = if (sym.isModuleClass) moduleClassesBuffer else classesBuffer + buffer += sym + case _ => + } + } + companionship.record(classesBuffer, moduleClassesBuffer) + assert(!expr.isInstanceOf[ClassDef]) + super.traverse(tree) + case _ => + super.traverse(tree) + } + } + + val result = mutable.LinkedHashMap[Tree, Int]() + + for (asyncState <- asyncStates) { + traverser.childDefs.clear() + traverser.traverse(Block(asyncState.allStats, EmptyTree)) + for (defTree <-traverser.childDefs) { + result(defTree) = asyncState.state + } } - mutable.LinkedHashMap(asyncStates.flatMap { - asyncState => - val defs = collectDirectlyEnclosedDefs(Block(asyncState.allStats: _*)) - defs.map((_, asyncState.state)) - }: _*) + result } // In which block are these symbols defined? @@ -137,22 +157,25 @@ trait Lifter extends ExprBuilder { // due to the handling of type parameter skolems in `thisMethodType` in `Namers` treeCopy.DefDef(dd, Modifiers(sym.flags), sym.name, tparams, vparamss, tpt, rhs) case cd@ClassDef(_, _, tparams, impl) => - sym.setName(name.freshen(sym.name.toTypeName)) - companionship.companionOf(cd.symbol) match { - case NoSymbol => - case moduleSymbol => - moduleSymbol.setName(sym.name.toTermName) - moduleSymbol.asModule.moduleClass.setName(moduleSymbol.name.toTypeName) - } - treeCopy.ClassDef(cd, Modifiers(sym.flags), sym.name, tparams, impl) - case md@ModuleDef(_, _, impl) => - companionship.companionOf(md.symbol) match { - case NoSymbol => - sym.setName(name.freshen(sym.name.toTermName)) - sym.asModule.moduleClass.setName(sym.name.toTypeName) - case classSymbol => // will be renamed by `case ClassDef` above. + val companion = companionship.companionOf(cd.symbol) + if (!cd.symbol.isModuleClass) { + sym.setName(name.freshen(sym.name.toTypeName)) + companion match { + case NoSymbol => + case moduleClassSymbol => + moduleClassSymbol.setName(sym.name.toTypeName) + // TODO rename the other lazy artifacts? Foo$lazy + } + treeCopy.ClassDef(cd, Modifiers(sym.flags), sym.name, tparams, impl) + } else { + companion match { + case NoSymbol => + sym.setName(name.freshen(sym.name.toTypeName)) + sym.setName(sym.name.toTypeName) + case classSymbol => // will be renamed by above. + } + treeCopy.ClassDef(cd, Modifiers(sym.flags), sym.name, tparams, impl) } - treeCopy.ModuleDef(md, Modifiers(sym.flags), sym.name, impl) case td@TypeDef(_, _, tparams, rhs) => sym.setName(name.freshen(sym.name.toTypeName)) treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index a811b37c5828..76226cb664a2 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -34,6 +34,30 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def testCaseClassLifting(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + import scala.async.Async.{async, await} + import Future.{successful => f} + object Test { + def test = async { + { + trait Base { def base = 0} + await(f(0)) + case class Person(name: String) extends Base + val fut = f("bob") + val x = Person(await(fut)) + x.base + assert(Person.getClass.getName == classOf[Person].getName + "$", (Person.getClass.getName, classOf[Person].getName)) + x.name + } + } + } + """ + assertEquals("bob", run(code)) + } + @Test def testScalaConcurrentAsyncNested(): Unit = { val code = From 2b580edb39400ba0b8d614b4ebb88aa1b59dd184 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Mar 2020 16:06:31 +1000 Subject: [PATCH 17/94] Look for references in types as well as in terms. --- src/compiler/scala/tools/nsc/transform/async/Lifter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index 482dc750d3f3..a5f3e6d0fd3c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -100,7 +100,7 @@ trait Lifter extends ExprBuilder { val defSymToReferenced: mutable.LinkedHashMap[Symbol, List[Symbol]] = defs.map { case (tree, _) => (tree.symbol, tree.collect { case rt: RefTree if symToDefiningState.contains(rt.symbol) => rt.symbol - }) + } ::: tree.symbol.info.collect { case TypeRef(_, sym, _) if symToDefiningState.contains(sym) => sym }) } // The direct references of each block, excluding references of `DefTree`-s which From ec8166354f0c28a57f26cc78e2e571411c170f79 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 18 Feb 2020 11:03:31 +1000 Subject: [PATCH 18/94] A simpler and faster ANF transform A long overdue overhaul. Originally, we defined this in terms of a pair of mutually recursive transforms (`anf` and `linearize`). We can simplfy the implementation by collapsing these the cases of single Transformer. This transformer is designed for "thicket" based transforms, where a single input tree can map to a list of output trees which will be flattened into the enclosing `Block`. An enclosing block will be automatically added if there was none before. Make the transform more idiomatic by more widespread use of utilities to generated attributed trees rather than passing untyped trees through `localTyper.typed`. Rework special casing of Unit/Nothing typed expressions to avoid temporary vals of these types to avoid littering the resulting trees with `BoxedUnit` references. Avoid unnecessary temporaries in: - the suffix of the args of an Apply the follows the final `await` - the result of If that only awaits the condition - similarly, the result of a Match that only awaits the scrutinee - "safe to inline" expressions like simple Idents or constants. Remove special cases for derived value classes which are eliminated now before the ANF transform. Furthermore: - Deal with ArrayValue tree - Remove cast which was used ot suppress typer warning - Don't need to deal with nested Applys after uncurry - Remove an new-unneeded adjustment from the entry point to the ANF and from MatchResultTransformer. --- .../nsc/backend/jvm/BCodeBodyBuilder.scala | 3 +- .../nsc/transform/async/AnfTransform.scala | 607 ++++++++---------- .../nsc/transform/async/AsyncPhase.scala | 3 +- .../nsc/transform/async/AsyncTransform.scala | 18 +- .../nsc/transform/async/ExprBuilder.scala | 25 +- .../nsc/transform/async/TransformUtils.scala | 211 +----- .../transform/async/user/FutureSystem.scala | 2 +- .../scala/reflect/internal/StdNames.scala | 1 + .../scala/reflect/internal/Trees.scala | 1 + .../nsc/async/AnnotationDrivenAsync.scala | 3 +- 10 files changed, 342 insertions(+), 532 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index f9a2ed24b8f2..b9f073133e2d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -655,7 +655,8 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val sym = fun.symbol if (sym.isLabel) { // jump to a label - genLoadLabelArguments(args, labelDef(sym), app.pos) + def notFound() = abort("Not found: " + sym + " in " + labelDef) + genLoadLabelArguments(args, labelDef.getOrElse(sym, notFound()), app.pos) bc goTo programPoint(sym) } else if (isPrimitive(sym)) { // primitive method call generatedType = genPrimitiveOp(app, expectedType) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index f59d6ce2e179..96e1ac04ceb5 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -12,320 +12,253 @@ package scala.tools.nsc.transform.async +import scala.annotation.tailrec +import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.reflect.internal.Flags private[async] trait AnfTransform extends TransformUtils { import global._ + + /** + * Transform `tree` into "A-Normal Form", such that within subtrees that enclose an `await`: + * + * - `if`, `match`, and other control-flow constructs are only used as statements; they cannot be used as expressions; + * - calls to `await` are not allowed in compound expressions; + * - execution order is reified in the tree by extracting temporary vals + */ final def anfTransform(tree: Tree, owner: Symbol): Block = { - val trans = new AnfTransform(owner) - // Must prepend the () for issue #31. - val block = typecheck(atPos(tree.pos)(Block(List(literalUnit), tree))).setType(tree.tpe) - val tree1 = adjustTypeOfTranslatedPatternMatches(block, owner) - trans.transformAtOwner(owner, tree1).asInstanceOf[Block] + val trans = new AnfTransformer() + trans.atOwner(tree, owner) { trans.apply(tree) } } - class AnfTransform(owner: Symbol) extends TypingTransformer(currentTransformState.unit) { - - sealed abstract class AnfMode - - case object Anf extends AnfMode - - case object Linearizing extends AnfMode - - var mode: AnfMode = Anf - - object trace { - private var indent = -1 - - private def indentString = " " * indent - - def apply[T](args: Any)(t: => T): T = { - def prefix = mode.toString.toLowerCase - - indent += 1 - - def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(127) - - try { - if (AsyncUtils.trace) - AsyncUtils.trace(s"$indentString$prefix(${oneLine(args)})") - val result = t - if (AsyncUtils.trace) - AsyncUtils.trace(s"$indentString= ${oneLine(result)}") - result - } finally { - indent -= 1 - } + private final class AnfTransformer() extends TypingTransformer(currentTransformState.unit) { + /** Main entry point to the ANF transform. */ + def apply(tree: Tree): Block = { + transformNewControlFlowBlock(tree) match { + case blk: Block => blk + case t => atPos(t.pos)(Block(Nil, t).setType(t.tpe)) } } - def typed(tree: Tree) = localTyper.typed(tree) - - def typedAt(exprPos: Position, tree: Tree) = localTyper.typed(atPos(exprPos)(tree)) - - def typedAssign(lhs: Tree, varSym: Symbol) = - typedAt(lhs.pos, Assign(Ident(varSym), lhs)) - - object linearize { - def transformToList(tree: Tree): List[Tree] = { - mode = Linearizing; - blockToList(transform(tree)) - } - - def transformToBlock(tree: Tree): Block = listToBlock(transformToList(tree)) + // This transform typically transforms a single tree into a list of trees. This is somewhat awkward to + // express as the standard `Transformer` doesn't support the notion of `Thickets` (a tree representing + // as list of trees that will be flattened into its enclosing tree). + // + // Instead, `AnfTransformer` uses this mutable side-channel for the statements of the + // current control flow block. This is convenient but requires some discipline: we need to + // make sure we perform recursive transforms in the correct order (e.g. transform the + // `qual` before the `args` of a `Apply`). This is the default transform behaviour and the + // conventional way to write transforms in any case. + private var currentStats = ListBuffer[Tree]() + + override def transform(tree: Tree): Tree = trace(tree) { + val treeContainsAwait = containsAwait(tree) + tree match { + case _: ClassDef | _: ModuleDef | _: Function | _: DefDef => + tree + case _: RefTree if tree.symbol.hasPackageFlag => + tree + case _ if !treeContainsAwait => + tree + case Apply(fun, args) if !isBooleanShortCircuit(fun.symbol) => + val lastAwaitArgIndex: Int = args.lastIndexWhere(containsAwait) + val simpleFun = transform(fun) + var i = 0 + val argExprss = map2(args, fun.symbol.paramss.head) { (arg: Tree, param: Symbol) => + transform(arg) match { + case expr1 => + val argName = param.name.toTermName + // No need to extract the argument into a val if is non-side-effecting or if we are beyond the final + // argument containing an `await` calls. + val elideVal = treeInfo.isExprSafeToInline(expr1) || lastAwaitArgIndex < 0 || i > lastAwaitArgIndex || !treeContainsAwait + val result = if (elideVal) { + expr1 + } else { + if (isUnitType(expr1.tpe)) { + currentStats += expr1 + literalBoxedUnit + } else { + val valDef = defineVal(name.freshen(argName), expr1, expr1.pos) + currentStats += valDef + gen.mkAttributedIdent(valDef.symbol) + } + } + i += 1 + result + } + } + val simpleApply = treeCopy.Apply(tree, simpleFun, argExprss) + simpleApply.attachments.remove[ContainsAwait.type] + if (isAwait(fun)) { + val valDef = defineVal(name.await(), treeCopy.Apply(tree, fun, argExprss), tree.pos) + val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe) + currentStats += valDef + atPos(tree.pos)(ref) + } else { + simpleApply + } - def _transformToList(tree: Tree): List[Tree] = trace(tree) { - val stats :+ expr = _anf.transformToList(tree) + case Block(stats, expr) => + // First, transform the block contents into a separate List[Tree] + val (trees, _) = withNewControlFlowBlock { + stats.foreach(stat => { + val expr = transform(stat); + if (!isLiteralUnit(expr)) currentStats += expr + }) + currentStats += transform(expr) + () + } - def statsExprUnit = { - stats :+ expr :+ typedAt(expr.pos, literalUnit) + // Identify groups of statements compiled from pattern matches and process them separately to + // replace the label parameter of the `matchEnd` `LabelDef` with a `var matchRes: T` result var. + // + // The results are appended into the ambient `currentStats`, which has the desired effect of flattening + // nested blocks. + foreachGroupsEndingWith(trees)( + isGroupEnd = isMatchEnd, + onGroup = (ts: Array[Tree]) => + eliminateMatchEndLabelParameter(tree.pos, ts).foreach(t => flattenBlock(t)(currentStats += _)), + onTail = (ts: List[Tree]) => + ts.foreach(t => flattenBlock(t)(currentStats += _)) + ) + + // However, we let `onTail` add the expr to `currentStats` (that was more efficient than using `ts.dropRight(1).foreach(addToStats)`) + // Compensate by removing it from the buffer and returning the expr. + currentStats.remove(currentStats.size - 1) + + case ValDef(mods, name, tpt, rhs) => atOwner(tree.symbol) { + // Capture current cursor of a non-empty `stats` buffer so we can efficiently restrict the + // `changeOwner` to the newly added items... + var statsIterator = if (currentStats.isEmpty) null else currentStats.iterator + + val expr = atOwner(currentOwner.owner)(transform(rhs)) + + // But, ListBuffer.empty.iterator doesn't reflect later mutation. Luckily we can just start + // from the beginning of the buffer + if (statsIterator == null) statsIterator = currentStats.iterator + + // Definitions within stats lifted out of the `ValDef` rhs should no longer be owned by the + // the ValDef. + statsIterator.foreach(_.changeOwner((currentOwner, currentOwner.owner))) + + treeCopy.ValDef(tree, mods, name, tpt, expr) } - def statsExprThrow = - stats :+ expr :+ typedAt(expr.pos, Throw(Apply(Select(New(gen.mkAttributedRef(IllegalStateExceptionClass)), nme.CONSTRUCTOR), Nil))) - - expr match { - case Apply(fun, args) if isAwait(fun) => - val awaitResType = transformType(expr.tpe) - val valDef = defineVal(name.await(), expr, tree.pos)(awaitResType) - val ref = gen.mkAttributedStableRef(valDef.symbol).setType(awaitResType) - // https://github.com/scala/async/issues/74 - // Use a cast to hide from "pure expression does nothing" error - // TODO avoid creating a ValDef for the result of this await to avoid this tree shape altogether. - // This will require some deeper changes to the later parts of the macro which currently assume regular - // tree structure around `await` calls. - val refNoPureExpr = - if (!isPastErasure && typeEqualsUnit(ref.tpe)) typedAt(tree.pos, gen.mkCast(ref, ref.tpe)) - else atPos(tree.pos)(ref) - - stats :+ valDef :+ refNoPureExpr - - case If(cond, thenp, elsep) => - // If we run the ANF transform post patmat, deal with trees like `(if (cond) jump1(){String} else jump2(){String}){String}` - // as though it was typed with `Unit`. - def isPatMatGeneratedJump(t: Tree): Boolean = t match { - case Block(_, expr) => isPatMatGeneratedJump(expr) - case If(_, thenp, elsep) => isPatMatGeneratedJump(thenp) && isPatMatGeneratedJump(elsep) - case _: Apply if isLabel(t.symbol) => true - case _ => false - } - - if (isPatMatGeneratedJump(expr)) - assignUnitType(expr) - - // if type of if-else is Unit don't introduce assignment, - // but add Unit value to bring it into form expected by async transform - if (typeEqualsUnit(expr.tpe)) { - statsExprUnit - } else if (typeEqualsNothing(expr.tpe)) { - statsExprThrow - } else { - val varDef = defineVar(name.ifRes(), expr.tpe, tree.pos) - - def branchWithAssign(t: Tree): Tree = { - t match { - case MatchEnd(ld) => - deriveLabelDef(ld, branchWithAssign) - case blk@Block(thenStats, thenExpr) => - assignUnitType(treeCopy.Block(blk, thenStats, branchWithAssign(thenExpr))) - case _ => - typedAssign(t, varDef.symbol) - } - } + case If(cond, thenp, elsep) => + val needsResultVar = (containsAwait(thenp) || containsAwait(elsep)) + transformMatchOrIf(tree, needsResultVar, name.ifRes) { varSym => + val condExpr = transform(cond) + val thenBlock = transformNewControlFlowBlock(thenp) + val elseBlock = transformNewControlFlowBlock(elsep) + treeCopy.If(tree, condExpr, pushAssignmentIntoExpr(varSym, thenBlock), pushAssignmentIntoExpr(varSym, elseBlock)) + } - val ifWithAssign = assignUnitType(treeCopy.If(tree, cond, branchWithAssign(thenp), branchWithAssign(elsep))) - stats :+ varDef :+ ifWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) + case Match(scrut, cases) => + val needResultVar = cases.exists(containsAwait) + transformMatchOrIf(tree, needResultVar, name.matchRes) { varSym => + val scrutExpr = transform(scrut) + val casesWithAssign = cases map { + case cd@CaseDef(pat, guard, body) => + assignUnitType(treeCopy.CaseDef(cd, pat, transformNewControlFlowBlock(guard), pushAssignmentIntoExpr(varSym, transformNewControlFlowBlock(body)))) } - case ld@LabelDef(name, params, rhs) => - if (isUnitType(ld.symbol.info.resultType)) statsExprUnit - else stats :+ expr - - case Match(scrut, cases) => - // if type of match is Unit don't introduce assignment, - // but add Unit value to bring it into form expected by async transform - if (typeEqualsUnit(expr.tpe)) { - statsExprUnit - } else if (typeEqualsNothing(expr.tpe)) { - statsExprThrow - } else { - val varDef = defineVar(name.matchRes(), expr.tpe, tree.pos) - val casesWithAssign = cases map { - case cd@CaseDef(pat, guard, body) => - def bodyWithAssign(t: Tree): Tree = { - t match { - case MatchEnd(ld) => deriveLabelDef(ld, bodyWithAssign) - case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, bodyWithAssign(caseExpr))) - case _ => typedAssign(t, varDef.symbol) - } - } + treeCopy.Match(tree, scrutExpr, casesWithAssign) + } - assignUnitType(treeCopy.CaseDef(cd, pat, guard, bodyWithAssign(body))) - } - val matchWithAssign = assignUnitType(treeCopy.Match(tree, scrut, casesWithAssign)) - require(matchWithAssign.tpe != null, matchWithAssign) - stats :+ varDef :+ matchWithAssign :+ atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) - } - case _ => - stats :+ expr - } - } + case LabelDef(name, params, rhs) => + treeCopy.LabelDef(tree, name, params, transformNewControlFlowBlock(rhs)) - def defineVar(name: TermName, tp: Type, pos: Position): ValDef = { - val sym = currentOwner.newTermSymbol(name, pos, Flags.MUTABLE | Flags.SYNTHETIC).setInfo(transformType(tp)) - ValDef(sym, mkZero(tp, pos)).setType(NoType).setPos(pos) + case _ => + super.transform(tree) } } - def defineVal(name: TermName, lhs: Tree, pos: Position)(tp: Type = transformType(lhs.tpe)): ValDef = { - val sym = currentOwner.newTermSymbol(name, pos, Flags.SYNTHETIC).setInfo(tp) - - val lhsOwned = lhs.changeOwner((currentOwner, sym)) - val rhs = - if (isPastErasure && isUnitType(tp)) Block(lhsOwned :: Nil, literalUnit) - else lhsOwned - ValDef(sym, rhs).setType(NoType).setPos(pos) - + private def pushAssignmentIntoExpr(varSym: Symbol, t: Tree): Tree = { + t match { + case _ if varSym == NoSymbol || t.tpe.typeSymbol == definitions.NothingClass => t + case MatchEnd(ld) => treeCopy.LabelDef(ld, ld.name, ld.params, pushAssignmentIntoExpr(varSym, ld.rhs)) + case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, pushAssignmentIntoExpr(varSym, caseExpr))) + case _ => typedAssign(t, varSym) + } } - object _anf { - import treeInfo.Applied - - def transformToList(tree: Tree): List[Tree] = { - mode = Anf; - blockToList(transform(tree)) + @tailrec + private def transformMatchOrIf[T <: Tree](tree: Tree, needsResultVar: Boolean, nameSource: asyncNames.NameSource[TermName])(core: Symbol => T): Tree = { + // if type of if/match is Unit don't introduce assignment, + // but add Unit value to bring it into form expected by async transform + if (typeEqualsUnit(tree.tpe)) { + currentStats += assignUnitType(core(NoSymbol)) + atPos(tree.pos)(literalUnit) + } else if (tree.tpe =:= definitions.NothingTpe) { + currentStats += assignUnitType(core(NoSymbol)) + localTyper.typedPos(tree.pos)(Throw(New(IllegalStateExceptionClass))) + } else if (isPatMatGeneratedJump(tree)) { + transformMatchOrIf(assignUnitType(tree), needsResultVar, nameSource)(core) + } else if (!needsResultVar) { + core(NoSymbol) + } else { + val varDef = defineVar(nameSource(), tree.tpe, tree.pos) + currentStats += varDef + currentStats += assignUnitType(core(varDef.symbol)) + atPos(tree.pos)(gen.mkAttributedStableRef(varDef.symbol)).setType(tree.tpe) } + } - def _transformToList(tree: Tree): List[Tree] = trace(tree) { - if (!containsAwait(tree)) { - tree match { - case Block(stats, expr) => - // avoids nested block in `while(await(false)) ...`. - // TODO I think `containsAwait` really should return true if the code contains a label jump to an enclosing - // while/doWhile and there is an await *anywhere* inside that construct. - stats :+ expr - case _ => List(tree) - } - } else tree match { - case Select(qual, sel) => - val stats :+ expr = linearize.transformToList(qual) - stats :+ treeCopy.Select(tree, expr, sel) - - case Throw(expr) => - val stats :+ expr1 = linearize.transformToList(expr) - stats :+ treeCopy.Throw(tree, expr1) - - case Typed(expr, tpt) => - val stats :+ expr1 = linearize.transformToList(expr) - stats :+ treeCopy.Typed(tree, expr1, tpt) - - case ArrayValue(elemtp, elems) => - val blks = elems.map(elem => linearize.transformToBlock(elem)) - blks.flatMap(_.stats) :+ treeCopy.ArrayValue(tree, elemtp, blks.map(_.expr)) - - case Applied(fun, targs, argss) if argss.nonEmpty => - // we can assume that no await call appears in a by-name argument position, - // this has already been checked. - val funStats :+ simpleFun = linearize.transformToList(fun) - val (argStatss, argExprss): (List[List[List[Tree]]], List[List[Tree]]) = - mapArgumentss[List[Tree]](fun, argss) { - case Arg(expr, byName, _) if byName /*|| isPure(expr) TODO */ => (Nil, expr) - case Arg(expr, _, argName) => - linearize.transformToList(expr) match { - case stats :+ expr1 => - val valDef = defineVal(name.freshen(argName), expr1, expr1.pos)() - require(valDef.tpe != null, valDef) - val stats1 = stats :+ valDef - (stats1, atPos(tree.pos.makeTransparent)(gen.stabilize(gen.mkAttributedIdent(valDef.symbol)))) - } - } - - def copyApplied(tree: Tree, depth: Int): Tree = { - tree match { - case TypeApply(_, targs) => treeCopy.TypeApply(tree, simpleFun, targs) - case _ if depth == 0 => simpleFun - case Apply(fun, args) => - val newTypedArgs = map2(args.map(_.pos), argExprss(depth - 1))((pos, arg) => typedAt(pos, arg)) - treeCopy.Apply(tree, copyApplied(fun, depth - 1), newTypedArgs) - } - } - - val typedNewApply = copyApplied(tree, argss.length) - - funStats ++ argStatss.flatten.flatten :+ typedNewApply - - case Block(stats, expr) => - val stats1 = stats.flatMap(linearize.transformToList).filterNot(isLiteralUnit) - val exprs1 = linearize.transformToList(expr) - val trees = stats1 ::: exprs1 - - def groupsEndingWith[T](ts: List[T])(f: T => Boolean): List[List[T]] = if (ts.isEmpty) Nil else { - ts.indexWhere(f) match { - case -1 => List(ts) - case i => - val (ts1, ts2) = ts.splitAt(i + 1) - ts1 :: groupsEndingWith(ts2)(f) - } - } - - val matchGroups = groupsEndingWith(trees) { case MatchEnd(_) => true; case _ => false } - val trees1 = matchGroups.flatMap(group => eliminateMatchEndLabelParameter(tree.pos, group)) - val result = trees1 flatMap { - case Block(stats, expr) => stats :+ expr - case t => t :: Nil - } - result - - case ValDef(mods, name, tpt, rhs) => - if (containsAwait(rhs)) { - val stats :+ expr = atOwner(currentOwner.owner)(linearize.transformToList(rhs)) - stats.foreach(_.changeOwner((currentOwner, currentOwner.owner))) - stats :+ treeCopy.ValDef(tree, mods, name, tpt, expr) - } else List(tree) - - case Assign(lhs, rhs) => - val stats :+ expr = linearize.transformToList(rhs) - stats :+ treeCopy.Assign(tree, lhs, expr) - - case If(cond, thenp, elsep) => - val condStats :+ condExpr = linearize.transformToList(cond) - val thenBlock = linearize.transformToBlock(thenp) - val elseBlock = linearize.transformToBlock(elsep) - condStats :+ treeCopy.If(tree, condExpr, thenBlock, elseBlock) - - case Match(scrut, cases) => - val scrutStats :+ scrutExpr = linearize.transformToList(scrut) - val caseDefs = cases map { - case CaseDef(pat, guard, body) => - // extract local variables for all names bound in `pat`, and rewrite `body` - // to refer to these. - // TODO we can move this into ExprBuilder once we get rid of `AsyncDefinitionUseAnalyzer`. - val block = linearize.transformToBlock(body) - val (valDefs, mappings) = (pat collect { - case b@Bind(bindName, _) => - val vd = defineVal(name.freshen(bindName.toTermName), gen.mkAttributedStableRef(b.symbol).setPos(b.pos), b.pos)() - vd.symbol.updateAttachment(SyntheticBindVal) - (vd, (b.symbol, vd.symbol)) - }).unzip - val (from, to) = mappings.unzip - val b@Block(stats1, expr1) = block.substituteSymbols(from, to).asInstanceOf[Block] - val newBlock = treeCopy.Block(b, valDefs ++ stats1, expr1) - treeCopy.CaseDef(tree, pat, guard, newBlock) - } - scrutStats :+ treeCopy.Match(tree, scrutExpr, caseDefs) + // Transform `tree` into with a new block. A new `currentStats` buffer will be pushed onto the stack and + // the resulting stats will be included in the returned `Tree`. Use when the `tree` is not sequentially evaluated + // after the preceding sibling, but rather will be the target of a control flow jump. + private def transformNewControlFlowBlock(tree: Tree): Tree = { + val savedStats = currentStats + this.currentStats = new ListBuffer[Tree] + try transform(tree) match { + case b@Block(stats, expr) => + treeCopy.Block(b, currentStats.prependToList(stats), expr) + case expr => currentStats.toList match { + case Nil => expr + case stats => treeCopy.Block(expr, stats, expr) + } + } finally { + this.currentStats = savedStats + } + } - case LabelDef(name, params, rhs) => - if (!isPastErasure && isUnitType(tree.symbol.info)) // erasure has already inserted unit - List(treeCopy.LabelDef(tree, name, params, typed(Block(linearize.transformToList(rhs), literalUnit))).setSymbol(tree.symbol)) - else - List(treeCopy.LabelDef(tree, name, params, typed(listToBlock(linearize.transformToList(rhs)))).setSymbol(tree.symbol)) + private def withNewControlFlowBlock[T](f: => T): (List[Tree], T) = { + val savedStats = currentStats + this.currentStats = new ListBuffer[Tree] + try { + val result = f + (currentStats.toList, result) + } finally { + this.currentStats = savedStats + } + } - case TypeApply(fun, targs) => - val funStats :+ simpleFun = linearize.transformToList(fun) - funStats :+ treeCopy.TypeApply(tree, simpleFun, targs) + // If we run the ANF transform post patmat, deal with trees like `(if (cond) jump1(){String} else jump2(){String}){String}` + // as though it was typed with `Unit`. + private def isPatMatGeneratedJump(t: Tree): Boolean = t match { + case Block(_, expr) => isPatMatGeneratedJump(expr) + case If(_, thenp, elsep) => isPatMatGeneratedJump(thenp) && isPatMatGeneratedJump(elsep) + case _: Apply if isLabel(t.symbol) => true + case _ => false + } - case _ => - List(tree) - } + /** + * Identifies groups in a list of elements by a predicate on the terminal element. + * + * @param ts The elements to be grouped + * @param isGroupEnd Identifies the terminal element of a group + * @param onGroup Callback to process each group + * @param onTail Callback to process the tail of the list that does not satisfy `isGroupEnd` + */ + @tailrec + private def foreachGroupsEndingWith[T <: AnyRef : reflect.ClassTag](ts: List[T])(isGroupEnd: T => Boolean, onGroup: Array[T] => Unit, onTail: List[T] => Unit): Unit = if (!ts.isEmpty) { + ts.indexWhere(isGroupEnd) match { + case -1 => + onTail(ts) + case i => + val group = new Array[T](i + 1) + ts.copyToArray(group) + onGroup(group) + foreachGroupsEndingWith(ts.drop(i + 1))(isGroupEnd, onGroup, onTail) } } @@ -338,7 +271,7 @@ private[async] trait AnfTransform extends TransformUtils { // - extract a `matchRes` variable // - rewrite the terminal label def to take no parameters, and instead read this temp variable // - change jumps to the terminal label to an assignment and a no-arg label application - def eliminateMatchEndLabelParameter(pos: Position, statsExpr: List[Tree]): List[Tree] = { + def eliminateMatchEndLabelParameter(pos: Position, statsExpr: Array[Tree]): Iterator[Tree] = { val caseDefToMatchResult = collection.mutable.Map[Symbol, Symbol]() val matchResults = collection.mutable.Buffer[Tree]() @@ -348,86 +281,116 @@ private[async] trait AnfTransform extends TransformUtils { def unitLabelDef = { setUnitMethodInfo(ld.symbol) - assignUnitType(treeCopy.LabelDef(ld, ld.name, Nil, typed(literalUnit))) + assignUnitType(treeCopy.LabelDef(ld, ld.name, Nil, literalUnit)) } if (isUnitType(ld.params.head.tpe)) { // Unit typed match: eliminate the label def parameter, but don't create a matchres temp variable to // store the result for cleaner generated code. caseDefToMatchResult(ld.symbol) = NoSymbol - (unitLabelDef, substituteTrees(ld.rhs, param.symbol :: Nil, typed(literalUnit) :: Nil)) + (unitLabelDef, substituteTrees(ld.rhs, param.symbol :: Nil, literalUnit :: Nil)) } else { // Otherwise, create the matchres var. We'll callers of the label def below. // Remember: we're iterating through the statement sequence in reverse, so we'll get // to the LabelDef and mutate `matchResults` before we'll get to its callers. - val matchResult = linearize.defineVar(name.matchRes(), param.tpe, ld.pos) + val matchResult = defineVar(name.matchRes(), param.tpe, ld.pos) matchResults += matchResult caseDefToMatchResult(ld.symbol) = matchResult.symbol (unitLabelDef, ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)) } } - val statsExpr0 = statsExpr.reverse.flatMap { + val statsExpr0: ArrayBuffer[Tree] = new ArrayBuffer[Tree](statsExpr.length) + + statsExpr.reverseIterator.foreach { case ld@LabelDef(_, param :: Nil, _) => val (ld1, after) = modifyLabelDef(ld) - List(after, ld1) + statsExpr0 += after + statsExpr0 += ld1 case a@ValDef(mods, name, tpt, ld@LabelDef(_, param :: Nil, _)) => val (ld1, after) = modifyLabelDef(ld) - List(treeCopy.ValDef(a, mods, name, tpt, after), ld1) + statsExpr0 += treeCopy.ValDef(a, mods, name, tpt, after) + statsExpr0 += ld1 case t => - if (caseDefToMatchResult.isEmpty) t :: Nil + if (caseDefToMatchResult.isEmpty) statsExpr0 += t else { val matchResultTransformer = new MatchResultTransformer(caseDefToMatchResult) - val tree1 = matchResultTransformer.transformAtOwner(owner, t) - tree1 :: Nil + val tree1 = matchResultTransformer.transformAtOwner(currentOwner, t) + statsExpr0 += tree1 } } + matchResults.toList match { case _ if caseDefToMatchResult.isEmpty => - statsExpr // return the original trees if nothing changed + statsExpr.iterator // return the original trees if nothing changed case Nil => - statsExpr0.reverse :+ literalUnit // must have been a unit-typed match, no matchRes variable to definne or refer to + statsExpr0.reverseIterator ++ List(literalUnit) // must have been a unit-typed match, no matchRes variable to definne or refer to case r1 :: Nil => // { var matchRes = _; ....; matchRes } - (r1 +: statsExpr0.reverse) :+ atPos(pos)(gen.mkAttributedIdent(r1.symbol)) - case _ => error(pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr + List(r1).iterator ++ statsExpr0.reverseIterator + case _ => error(pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr.iterator } } - def anfLinearize(tree: Tree): Block = { - val trees: List[Tree] = mode match { - case Anf => _anf._transformToList(tree) - case Linearizing => linearize._transformToList(tree) - } - listToBlock(trees) + @inline final def trace[T](args: Any)(t: => T): T = { + if (AsyncUtils.trace) { + tracing.apply("", args)({val tree = t; ("" + currentStats.mkString(";") + " ;; " + tree, tree)}) + } else t } - override def transform(tree: Tree): Tree = { - tree match { - case _: ValDef | _: DefDef | _: Function | _: ClassDef | _: TypeDef => - atOwner(tree.symbol)(anfLinearize(tree)) - case _: ModuleDef => - atOwner(tree.symbol.asModule.moduleClass orElse tree.symbol)(anfLinearize(tree)) - case _ => - anfLinearize(tree) + def defineVal(name: global.TermName, rhs: global.Tree, pos: Position): ValDef = { + val sym = currentOwner.newTermSymbol(name, pos, Flags.SYNTHETIC).setInfo(rhs.tpe) + ValDef(sym, rhs.changeOwner((currentOwner, sym))).setType(NoType) + } + + def defineVar(name: TermName, tp: Type, pos: Position): ValDef = { + val sym = currentOwner.newTermSymbol(name, pos, Flags.MUTABLE | Flags.SYNTHETIC).setInfo(tp) + ValDef(sym, gen.mkZero(tp).setPos(pos)).setType(NoType) + } + } + + private def typedAssign(lhs: Tree, varSym: Symbol) = + Assign(gen.mkAttributedRef(varSym), lhs).setType(definitions.UnitTpe).setPos(lhs.pos) + + val tracing: Tracing + class Tracing { + private var indent = -1 + + private def indentString = " " * indent + + def apply[T](prefix: String, args: Any)(t: => (String, T)): T = { + + indent += 1 + + def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(300) + + try { + if (AsyncUtils.trace) + AsyncUtils.trace(s"$indentString$prefix(${oneLine(args)})") + val result = t + if (AsyncUtils.trace) + AsyncUtils.trace(s"$indentString= ${oneLine(result._1)}") + result._2 + } finally { + indent -= 1 } } } final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) extends TypingTransformer(currentTransformState.unit) { override def transform(tree: Tree): Tree = { - def typedPos(pos: Position)(t: Tree): Tree = localTyper.typed(atPos(pos)(t)) - tree match { + case _: Function | _: MemberDef => + tree case Apply(fun, arg :: Nil) if isLabel(fun.symbol) && caseDefToMatchResult.contains(fun.symbol) => val temp = caseDefToMatchResult(fun.symbol) if (temp == NoSymbol) - typedPos(tree.pos)(Block(transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil))) - else - // setType needed for LateExpansion.shadowingRefinedType test case. There seems to be an inconsistency - // in the trees after pattern matcher. - // TODO miminize the problem in patmat and fix in scalac. - typedPos(tree.pos)(Block(Assign(Ident(temp), transform(arg.setType(transformType(fun.tpe.paramLists.head.head.info)))) :: Nil, treeCopy.Apply(tree, fun, Nil))) + treeCopy.Block(tree, transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil)) + else if (arg.tpe.typeSymbol == definitions.NothingClass) { + transform(arg) + } else { + treeCopy.Block(tree, typedAssign(transform(arg), temp) :: Nil, treeCopy.Apply(tree, fun, Nil)) + } case Block(stats, expr: Apply) if isLabel(expr.symbol) => super.transform(tree) match { case Block(stats0, Block(stats1, expr1)) => diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 0989816b73ef..dda8e0567fff 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -22,6 +22,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr import global._ val asyncNames = new AsyncNames[global.type](global) + val tracing = new Tracing val phaseName: String = "async" override def enabled = true // TODO: should be off by default, enabled by flag @@ -77,7 +78,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr case _ => false } val asyncBody = (dd.rhs: @unchecked) match { - case Block(stats, Literal(Constant(()))) => Block(stats.init, stats.last) + case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last) } val (newRhs, liftables) = asyncTransform(asyncBody, dd.symbol, dd.vparamss.head.head.symbol) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index e66e15de43d3..36342566b5e7 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -190,7 +190,8 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li val anfTree = futureSystemOps.postAnfTransform(anfTree0) - // TODO: why redo this? + // The ANF transform re-parents some trees, so the previous traversal to mark ancestors of + // await is no longer reliable. cleanupContainsAwaitAttachments(anfTree) markContainsAwait(anfTree) @@ -205,7 +206,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li for ((state, flds) <- assignsOf) { val assigns = flds.map { fld => val fieldSym = fld.symbol - Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), mkZero(fieldSym.info, asyncPos)) + Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos)) } val asyncState = asyncBlock.asyncStates.find(_.state == state).get @@ -225,6 +226,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li // fields. Similarly, replace references to them with references to the field. object UseFields extends explicitOuter.OuterPathTransformer(currentTransformState.unit) { private def fieldSel(tree: Tree) = { + assert(currentOwner != NoSymbol) val outerOrThis = if (stateMachineClass == currentClass) This(stateMachineClass) else { tree.symbol.makeNotPrivate(tree.symbol.owner) outerPath(outerValue, currentClass.outerClass, stateMachineClass) @@ -236,9 +238,15 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li super.transform(tree) case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner((tree.symbol, currentOwner))))) - case _: DefTree if liftedSyms(tree.symbol) => EmptyTree - case Ident(name) if liftedSyms(tree.symbol) => fieldSel(tree).setType(tree.tpe) - case _ => super.transform(tree) + case _: DefTree if liftedSyms(tree.symbol) => + EmptyTree + case Assign(i @ Ident(name), rhs) if liftedSyms(i.symbol) => + // Use localTyper to adapt () to BoxedUnit in `val ifRes: Object; if (cond) "" else ()` + treeCopy.Assign(tree, fieldSel(i), localTyper.typed(transform(rhs), i.symbol.tpe)) + case Ident(name) if liftedSyms(tree.symbol) => + fieldSel(tree).setType(tree.tpe) + case _ => + super.transform(tree) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 3fa88e587b51..dad05ff2ffa3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -49,6 +49,7 @@ trait ExprBuilder extends TransformUtils { final def body: Tree = stats match { case stat :: Nil => stat case init :+ last => Block(init, last) + case Nil => literalUnit } } @@ -92,21 +93,23 @@ trait ExprBuilder extends TransformUtils { val futureSystem = currentTransformState.futureSystem val futureSystemOps = futureSystem.mkOps(global) val fun = This(tpnme.EMPTY) - val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, - fun, Ident(nme.execContext)) val tryGetOrCallOnComplete: List[Tree] = if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempName = nme.completed - val initTemp = ValDef(NoMods, tempName, TypeTree(tryAny), futureSystemOps.getCompleted[Any](awaitable.expr)) + val tempAwaitableSym = symLookup.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) + val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) + val tempCompletedSym = symLookup.applyTrParam.owner.newTermSymbol(nme.completed).setInfo(tryAny) + val initTempCompleted = ValDef(tempCompletedSym, futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) val null_ne = Select(Literal(Constant(null)), TermName("ne")) + val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) val ifTree = - If(Apply(null_ne, Ident(tempName) :: Nil), - adaptToUnit(ifIsFailureTree[T](Ident(tempName)) :: Nil), + If(Apply(null_ne, Ident(tempCompletedSym) :: Nil), + adaptToUnit(ifIsFailureTree[T](Ident(tempCompletedSym)) :: Nil), Block(toList(callOnComplete), Return(literalUnit))) - - initTemp :: ifTree :: Nil - } else + initAwaitableTemp :: initTempCompleted :: ifTree :: Nil + } else { + val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) toList(callOnComplete) ::: Return(literalUnit) :: Nil + } mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup)) ++ tryGetOrCallOnComplete) } @@ -294,7 +297,7 @@ trait ExprBuilder extends TransformUtils { case vd @ ValDef(mods, name, tpt, UnwrapBoxedUnit(Apply(fun, arg :: Nil))) if isAwait(fun) => val onCompleteState = nextState() val afterAwaitState = afterState.getOrElse(nextState()) - val awaitable = Awaitable(arg, stat.symbol, tpt.tpe, vd) + val awaitable = Awaitable(arg.changeOwner(vd.symbol, vd.symbol.owner), stat.symbol, tpt.tpe, vd) asyncStates += stateBuilder.resultWithAwait(awaitable, onCompleteState, afterAwaitState) // complete with await currState = afterAwaitState stateBuilder = new AsyncStateBuilder(currState, symLookup) @@ -432,7 +435,7 @@ trait ExprBuilder extends TransformUtils { (t match { case Block(stats, expr) => stats ::: expr :: Nil case t => t :: Nil - }).iterator.map(t => showCode(t)).mkString("\n") + }).iterator.map(t => global.show(t)).mkString("\n") } if (i != length - 1) { val CaseDef(_, _, body) = state.mkHandlerCaseForState diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index f5230a5e2632..ceaea9038cc1 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -41,8 +41,6 @@ trait PhasedTransform extends TypingTransformers { def literalNull = Literal(Constant(null)) - def typeEqualsNothing(tp: Type) = tp =:= definitions.NothingTpe - def typeEqualsUnit(tp: Type) = tp =:= definitions.UnitTpe || (isPastErasure && tp =:= definitions.BoxedUnitTpe) def assignUnitType(t: Tree): t.type = @@ -54,8 +52,9 @@ trait PhasedTransform extends TypingTransformers { def isNothingClass(sym: Symbol) = sym == definitions.NothingClass def literalUnit = - if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) - else Literal(Constant(())) // a def to avoid sharing trees + Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees + def literalBoxedUnit = + gen.mkAttributedRef(definitions.BoxedUnit_UNIT) def isLiteralUnit(t: Tree) = t match { case Literal(Constant(())) => true @@ -106,58 +105,6 @@ trait PhasedTransform extends TypingTransformers { private def derivedValueClassUnbox(cls: Symbol) = (cls.info.decls.find(sym => sym.isMethod && sym.asTerm.isParamAccessor) getOrElse NoSymbol) - - def mkZero(tp: Type, pos: Position): Tree = { - val tpSym = tp.typeSymbol - if (tpSym.isClass && tpSym.asClass.isDerivedValueClass) { - val argZero = mkZero(derivedValueClassUnbox(tpSym).infoIn(tp).resultType, pos) - val baseType = tp.baseType(tpSym) // use base type here to dealias / strip phantom "tagged types" etc. - - // By explicitly attributing the types and symbols here, we subvert privacy. - // Otherwise, ticket86PrivateValueClass would fail. - - // Approximately: - // q"new ${valueClass}[$..targs](argZero)" - val target: Tree = gen.mkAttributedSelect(typecheck(atPos(pos)(New(TypeTree(baseType)))), tpSym.asClass.primaryConstructor) - - val zero = gen.mkMethodCall(target, argZero :: Nil) - // restore the original type which we might otherwise have weakened with `baseType` above - typecheck(atPos(pos)(gen.mkCast(zero, tp))) - } else { - gen.mkZero(tp) - } - } - - final def mkMutableField(tpt: Type, name: TermName, init: Tree): List[Tree] = { - if (isPastTyper) { - import scala.reflect.internal.Flags._ - // If we are running after the typer phase (ie being called from a compiler plugin) - // we have to create the trio of members manually. - val field = ValDef(Modifiers(MUTABLE | PRIVATE | LOCAL), name.localName, TypeTree(tpt), init) - val paramss = emptyParamss - val getter = DefDef(Modifiers(ACCESSOR | STABLE), name.getterName, Nil, paramss, TypeTree(tpt), Select(This(tpnme.EMPTY), field.name)) - val setter = DefDef(Modifiers(ACCESSOR), name.setterName, Nil, List(List(ValDef(NoMods, TermName("x"), TypeTree(tpt), EmptyTree))), TypeTree(definitions.UnitTpe), Assign(Select(This(tpnme.EMPTY), field.name), Ident(TermName("x")))) - field :: getter :: setter :: Nil - } else { - val result = ValDef(NoMods, name, TypeTree(tpt), init) - result :: Nil - } - } - final def mkField(tpt: Type, name: TermName, init: Tree): List[Tree] = { - if (isPastTyper) { - import scala.reflect.internal.Flags._ - // If we are running after the typer phase (ie being called from a compiler plugin) - // we have to create the trio of members manually. - val field = ValDef(Modifiers(PRIVATE | LOCAL), name.localName, TypeTree(tpt), init) - val paramss = emptyParamss - val getter = DefDef(Modifiers(ACCESSOR | STABLE), name.getterName, Nil, paramss, TypeTree(tpt), Select(This(tpnme.EMPTY), field.name)) - field :: getter :: Nil - } else { - val result = ValDef(NoMods, name, TypeTree(tpt), init) - result :: Nil - } - } - } @@ -182,79 +129,14 @@ private[async] trait TransformUtils extends PhasedTransform { def isAsync(fun: Tree) = fun.symbol == currentTransformState.ops.Async_async def isAwait(fun: Tree) = fun.symbol == currentTransformState.ops.Async_await - private lazy val Boolean_ShortCircuits: Set[Symbol] = { - import definitions.BooleanClass - def BooleanTermMember(name: String) = BooleanClass.typeSignature.member(TermName(name).encodedName) - val Boolean_&& = BooleanTermMember("&&") - val Boolean_|| = BooleanTermMember("||") - Set(Boolean_&&, Boolean_||) - } - - private def isByName(fun: Tree): ((Int, Int) => Boolean) = { - if (Boolean_ShortCircuits contains fun.symbol) (i, j) => true - else if (fun.tpe == null) (x, y) => false - else { - val paramss = fun.tpe.paramss - val byNamess = paramss.map(_.map(_.asTerm.isByNameParam)) - (i, j) => util.Try(byNamess(i)(j)).getOrElse(false) - } - } - private def argName(fun: Tree): ((Int, Int) => TermName) = { - val paramss = fun.tpe.paramss - val namess = paramss.map(_.map(_.name.toTermName)) - (i, j) => util.Try(namess(i)(j)).getOrElse(TermName(s"arg_${i}_${j}")) - } + def isBooleanShortCircuit(sym: Symbol): Boolean = + sym.owner == definitions.BooleanClass && (sym == definitions.Boolean_and || sym == definitions.Boolean_or) def isLabel(sym: Symbol): Boolean = sym.isLabel def substituteTrees(t: Tree, from: List[Symbol], to: List[Tree]): Tree = (new TreeSubstituter(from, to)).transform(t) - /** Map a list of arguments to: - * - A list of argument Trees - * - A list of auxillary results. - * - * The function unwraps and rewraps the `arg :_*` construct. - * - * @param args The original argument trees - * @param f A function from argument (with '_*' unwrapped) and argument index to argument. - * @tparam A The type of the auxillary result - */ - private def mapArguments[A](args: List[Tree])(f: (Tree, Int) => (A, Tree)): (List[A], List[Tree]) = { - args match { - case args :+ Typed(tree, Ident(tpnme.WILDCARD_STAR)) => - val (a, argExprs :+ lastArgExpr) = (args :+ tree).zipWithIndex.map(f.tupled).unzip - val exprs = argExprs :+ atPos(lastArgExpr.pos.makeTransparent)(Typed(lastArgExpr, Ident(tpnme.WILDCARD_STAR))) - (a, exprs) - case args => - args.zipWithIndex.map(f.tupled).unzip - } - } - - case class Arg(expr: Tree, isByName: Boolean, argName: TermName) - - /** - * Transform a list of argument lists, producing the transformed lists, and lists of auxillary - * results. - * - * The function `f` need not concern itself with varargs arguments e.g (`xs : _*`). It will - * receive `xs`, and it's result will be re-wrapped as `f(xs) : _*`. - * - * @param fun The function being applied - * @param argss The argument lists - * @return (auxillary results, mapped argument trees) - */ - def mapArgumentss[A](fun: Tree, argss: List[List[Tree]])(f: Arg => (A, Tree)): (List[List[A]], List[List[Tree]]) = { - val isByNamess: (Int, Int) => Boolean = isByName(fun) - val argNamess: (Int, Int) => TermName = argName(fun) - argss.zipWithIndex.map { case (args, i) => - mapArguments[A](args) { - (tree, j) => f(Arg(tree, isByNamess(i, j), argNamess(i, j))) - } - }.unzip - } - - def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { case Block(stats, expr) => (stats, expr) case _ => (List(tree), Literal(Constant(()))) @@ -269,6 +151,8 @@ private[async] trait TransformUtils extends PhasedTransform { case trees @ (init :+ last) => val pos = trees.map(_.pos).reduceLeft(_ union _) Block(init, last).setType(last.tpe).setPos(pos) + case Nil => + throw new MatchError(trees) } def emptyConstructor: DefDef = { @@ -307,15 +191,10 @@ private[async] trait TransformUtils extends PhasedTransform { case dd: DefDef => nestedMethod(dd) case fun: Function => function(fun) case m@Match(EmptyTree, _) => patMatFunction(m) // Pattern matching anonymous function under -Xoldpatmat of after `restorePatternMatchingFunctions` - case q"$fun[..$targs](...$argss)" if argss.nonEmpty => - val isInByName = isByName(fun) - for ((args, i) <- argss.zipWithIndex) { - for ((arg, j) <- args.zipWithIndex) { - if (!isInByName(i, j)) traverse(arg) - else byNameArgument(arg) - } - } + case Apply(fun, arg1 :: arg2 :: Nil) if isBooleanShortCircuit(fun.symbol) => traverse(fun) + traverse(arg1) + byNameArgument(arg2) case _ => super.traverse(tree) } } @@ -351,8 +230,10 @@ private[async] trait TransformUtils extends PhasedTransform { var containsAwait = false override def traverse(tree: Tree): Unit = if (tree.hasAttachment[NoAwait.type]) {} // safe to skip - else if (tree.hasAttachment[ContainsAwait.type]) containsAwait = true - else if (markContainsAwaitTraverser.shouldAttach(t)) super.traverse(tree) + else if (!containsAwait) { + if (tree.hasAttachment[ContainsAwait.type]) containsAwait = true + else if (markContainsAwaitTraverser.shouldAttach(t)) super.traverse(tree) + } } traverser.traverse(t) traverser.containsAwait @@ -399,63 +280,8 @@ private[async] trait TransformUtils extends PhasedTransform { t } - // First modification to translated patterns: - // - Set the type of label jumps to `Unit` - // - Propagate this change to trees known to directly enclose them: - // ``If` / `Block`) adjust types of enclosing - final def adjustTypeOfTranslatedPatternMatches(t: Tree, owner: Symbol): Tree = { - val trans = new PatmatAdjuster - trans.transformAtOwner(owner, t) - } - - private class PatmatAdjuster extends TypingTransformer(currentTransformState.unit) { - import definitions.UnitTpe - - override def transform(tree: Tree): Tree = { - tree match { - case LabelDef(name, params, rhs) => - val rhs1 = transform(rhs) - if (rhs1.tpe =:= UnitTpe) { - tree.symbol.info = internal.methodType(tree.symbol.info.paramLists.head, UnitTpe) - treeCopy.LabelDef(tree, name, params, rhs1) - } else { - treeCopy.LabelDef(tree, name, params, rhs1) - } - case Block(stats, expr) => - val stats1 = transformTrees(stats) - val expr1 = transform(expr) - if (expr1.tpe =:= UnitTpe) - treeCopy.Block(tree, stats1, expr1).setType(UnitTpe) - else - treeCopy.Block(tree, stats1, expr1) - case If(cond, thenp, elsep) => - val cond1 = transform(cond) - val thenp1 = transform(thenp) - val elsep1 = transform(elsep) - if (thenp1.tpe =:= definitions.UnitTpe && elsep.tpe =:= UnitTpe) - treeCopy.If(tree, cond1, thenp1, elsep1).setType(UnitTpe) - else - treeCopy.If(tree, cond1, thenp1, elsep1) - case Apply(fun, args) if isLabel(fun.symbol) => - treeCopy.Apply(tree, transform(fun), transformTrees(args)).setType(UnitTpe) - case t => super.transform(t) - } - } - } - - def deriveLabelDef(ld: LabelDef, applyToRhs: Tree => Tree): LabelDef = { - val rhs2 = applyToRhs(ld.rhs) - val ld2 = treeCopy.LabelDef(ld, ld.name, ld.params, rhs2) - if (ld eq ld2) ld - else { - val info2 = ld2.symbol.info match { - case MethodType(params, p) => MethodType(params, rhs2.tpe) - case t => t - } - ld2.symbol.info = info2 - ld2 - } - } + val isMatchEnd: (Tree) => Boolean = t => + MatchEnd.unapply(t).isDefined object MatchEnd { def unapply(t: Tree): Option[LabelDef] = t match { case ValDef(_, _, _, t) => unapply(t) @@ -463,6 +289,11 @@ private[async] trait TransformUtils extends PhasedTransform { case _ => None } } + + final def flattenBlock(tree: Tree)(f: Tree => Unit): Unit = tree match { + case Block(stats, expr) => stats.foreach(f); f(expr) + case _ => f(tree) + } } case object ContainsAwait diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index 26938f504c44..1f0d9459f300 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -167,7 +167,7 @@ object ScalaConcurrentFutureSystem extends FutureSystem { } def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = { - Block(gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) :: Nil, literalUnitExpr) + gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) } def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = { diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 8e6e745e6bb9..30117a8fec89 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -836,6 +836,7 @@ trait StdNames { // async val result : NameType = "result$async" + val awaitable : NameType = "awaitable$async" val completed : NameType = "completed$async" val stateMachine : NameType = "stateMachine$async" val state : NameType = "state$async" diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 71d0ff0579b4..439c3679fe82 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -1531,6 +1531,7 @@ trait Trees extends api.Trees { } class ChangeOwnerTraverser(val oldowner: Symbol, val newowner: Symbol) extends Traverser { + assert(newowner != NoSymbol, oldowner) final def change(sym: Symbol) = { if (sym != NoSymbol && sym.owner == oldowner) { sym.owner = newowner diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 76226cb664a2..c39bd5947a8e 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -192,7 +192,7 @@ class AnnotationDrivenAsync { | | "" match { | case _ if id(false) => ???; - | case _ => "okay" + | case _ => id("okay") | } | } |} @@ -247,6 +247,7 @@ class AnnotationDrivenAsync { settings.outdir.value = out.getAbsolutePath settings.embeddedDefaults(getClass.getClassLoader) // settings.debug.value = true + // settings.uniqid.value = true // settings.processArgumentString("-Xprint:all -nowarn") // sys.props("scala.async.trace") = "true" // sys.props("scala.async.debug") = "true" From b1c43ce79baedd71e299503fd9c2e8ceb0bcd47e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 09:53:00 +1000 Subject: [PATCH 19/94] Reduce allocations in name freshening. --- .../nsc/transform/async/AsyncNames.scala | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala index 0531b8b45abf..140abdbda884 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -16,12 +16,11 @@ import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import scala.reflect.api.Names /** * A per-global cache of names needed by the Async macro. */ -final class AsyncNames[U <: Names with Singleton](val u: U) { +final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { self => import u._ @@ -91,13 +90,26 @@ final class AsyncNames[U <: Names with Singleton](val u: U) { } private def freshen(name: TermName, counter: AtomicInteger): TermName = { if (freshened.contains(name)) name - else TermName(freshenString(name.toString, counter.incrementAndGet())) + else TermName(freshenString(name, counter.incrementAndGet())) } private def freshen(name: TypeName, counter: AtomicInteger): TypeName = { if (freshened.contains(name)) name - else TypeName(freshenString(name.toString, counter.incrementAndGet())) + else TypeName(freshenString(name, counter.incrementAndGet())) } } - private def freshenString(name: String, counter: Int): String = name.toString + "$async$" + counter + private def freshenString(name: CharSequence, counter: Int): String = { + val suffix = "$async$" + val length = name.length + suffix.length + decimalLength(counter) + val buffer = new java.lang.StringBuffer(length) + buffer.append(name) + buffer.append(suffix) + buffer.append(counter) + buffer.toString + } + + private def decimalLength(i: Int): Int = { + require(i >= 0, i) + if (i == 0) 1 else (Math.log10(i).floor + 1d).toInt + } } From d0ac056f491d3627593041f75ef5e4bb108dcba9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 11:22:38 +1000 Subject: [PATCH 20/94] Fix broken attempt to elide nulling in final state --- .../scala/tools/nsc/transform/async/LiveVariables.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 34722c6d6a7c..a292b2179d00 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -177,7 +177,8 @@ trait LiveVariables extends ExprBuilder { isPred0(state1, state2) } - val finalState = asyncStates.find(as => !asyncStates.exists(other => isPred(as.state, other.state))).get + val finalStates = asyncStates.filter(as => !asyncStates.exists(other => isPred(as.state, other.state))) + val finalState = finalStates.head if(AsyncUtils.verbose) { for (as <- asyncStates) @@ -298,7 +299,7 @@ trait LiveVariables extends ExprBuilder { var i = 0 while (i < succNums.length) { val num = succNums(i) - if (!isPred(num, s) && !LVentry(num).contains(fld.symbol)) + if (num != finalState.state && !isPred(num, s) && !LVentry(num).contains(fld.symbol)) result += num i += 1 } From ee663012794d2eb40a56114a1d57d79c41cbc4ae Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 11:23:32 +1000 Subject: [PATCH 21/94] Sort cases by state ID --- .../tools/nsc/transform/async/ExprBuilder.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index dad05ff2ffa3..b271495f6baf 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -578,9 +578,9 @@ trait ExprBuilder extends TransformUtils { private val compactStateTransform = new Transformer { override def transform(tree: Tree): Tree = tree match { - case as @ Apply(qual: Select, Literal(Constant(i: Integer)) :: Nil) if qual.symbol == symLookup.stateSetter => + case as @ Apply(qual: Select, (lit @ Literal(Constant(i: Integer))) :: Nil) if qual.symbol == symLookup.stateSetter => val replacement = switchIds(i) - treeCopy.Apply(tree, qual, Literal(Constant(replacement)):: Nil) + treeCopy.Apply(tree, qual, treeCopy.Literal(lit, Constant(replacement)):: Nil) case _: Match | _: CaseDef | _: Block | _: If => super.transform(tree) case _ => tree @@ -588,13 +588,15 @@ trait ExprBuilder extends TransformUtils { } private def compactStates(m: Match): Tree = { - val cases1 = m.cases.flatMap { - case cd @ CaseDef(Literal(Constant(i: Integer)), EmptyTree, rhs) => + val casesAndReplacementIds: List[(Integer, CaseDef)] = m.cases.map { + case cd @ CaseDef(lit @ Literal(Constant(i: Integer)), EmptyTree, rhs) => val replacement = switchIds(i) val rhs1 = compactStateTransform.transform(rhs) - treeCopy.CaseDef(cd, Literal(Constant(replacement)), EmptyTree, rhs1) :: Nil - case x => x :: Nil + (replacement, treeCopy.CaseDef(cd, treeCopy.Literal(lit, Constant(replacement)), EmptyTree, rhs1)) + case cd => + (Int.box(Integer.MAX_VALUE), cd) } + val cases1: List[CaseDef] = casesAndReplacementIds.sortBy(_._1).map(_._2) treeCopy.Match(m, m.selector, cases1) } From c7cb301bb25d50c8d6036e8c2f2e7de79d1ab4d9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 12:34:33 +1000 Subject: [PATCH 22/94] Fix .dot output when code contains ampersand --- src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index b271495f6baf..3dda2969f678 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -418,7 +418,8 @@ trait ExprBuilder extends TransformUtils { preText.split("\n").foreach { (line: String) => builder.append(br) - builder.append(line.replaceAllLiterally("\"", """).replaceAllLiterally("<", "<").replaceAllLiterally(">", ">").replaceAllLiterally(" ", " ")) + // TODO Wrap with CDATA instead? + builder.append(line.replaceAllLiterally("&", "&").replaceAllLiterally("\"", """).replaceAllLiterally("<", "<").replaceAllLiterally(">", ">").replaceAllLiterally(" ", " ")) } builder.append(br) builder.append("") From 210bfacd94afbd66c29fe704fb6b11d1eda6a717 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 14:06:27 +1000 Subject: [PATCH 23/94] Use reporter.error directly --- .../scala/tools/nsc/transform/async/AnfTransform.scala | 2 +- .../scala/tools/nsc/transform/async/AsyncAnalysis.scala | 4 ++-- .../scala/tools/nsc/transform/async/AsyncTransform.scala | 6 ------ .../scala/tools/nsc/transform/async/ExprBuilder.scala | 2 +- .../scala/tools/nsc/transform/async/TransformUtils.scala | 5 ----- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 96e1ac04ceb5..a8bf384c5955 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -328,7 +328,7 @@ private[async] trait AnfTransform extends TransformUtils { case r1 :: Nil => // { var matchRes = _; ....; matchRes } List(r1).iterator ++ statsExpr0.reverseIterator - case _ => error(pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr.iterator + case _ => global.reporter.error(pos, "Internal error: unexpected tree encountered during ANF transform " + statsExpr); statsExpr.iterator } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index c0447bbd6a80..57ccc78c2f3d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -61,7 +61,7 @@ trait AsyncAnalysis extends TransformUtils { reportUnsupportedAwait(tree, "try/catch") super.traverse(tree) case Return(_) => - abort(tree.pos, "return is illegal within a async block") + global.reporter.error(tree.pos, "return is illegal within a async block") case DefDef(mods, _, _, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => reportUnsupportedAwait(tree, "lazy val initializer") case ValDef(mods, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => @@ -97,7 +97,7 @@ trait AsyncAnalysis extends TransformUtils { } private def reportError(pos: Position, msg: String): Unit = { - abort(pos, msg) + global.reporter.error(pos, msg) } } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 36342566b5e7..81d79fcb22a2 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -137,14 +137,8 @@ class AsyncTransformState[U <: Global with Singleton](val symbolTable: U, val fu val labelDefStates = collection.mutable.Map[symbolTable.Symbol, Int]() } -// This was originally a macro -- TODO: complete integration with compiler universe (use global instead of scala.reflect.internal stuff) trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables with TypingTransformers { var currentTransformState: AsyncTransformState[global.type] = null - import global._ - def typecheck(tree: Tree): Tree = currentTransformState.localTyper.typed(tree) - def abort(pos: Position, msg: String): Nothing = {currentTransformState.localTyper.context.reporter.error(pos, msg); ???} - def error(pos: Position, msg: String): Unit = currentTransformState.localTyper.context.reporter.error(pos, msg) - import global._ // synthesize the state machine logic -- explode the apply method's rhs and lift local vals to field defs in the state machine diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 3dda2969f678..d5c1b534e7b8 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -250,7 +250,7 @@ trait ExprBuilder extends TransformUtils { var currState = startState def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree)) - abort(tree.pos, "await must not be used in this position") + global.reporter.error(tree.pos, "await must not be used in this position") def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index ceaea9038cc1..0cb2ee8326e0 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -24,11 +24,6 @@ import scala.tools.nsc.transform.TypingTransformers trait PhasedTransform extends TypingTransformers { import global._ - // macro context interface -- the rest is meant to be independent of our being a macro (planning to move async into the compiler) - def abort(pos: Position, msg: String): Nothing - def error(pos: Position, msg: String): Unit - def typecheck(tree: Tree): Tree - def isPastErasure: Boolean = { val erasurePhase = global.currentRun.erasurePhase erasurePhase != NoPhase && global.isPast(erasurePhase) From 90568be3aebe65f2d32adcff8bf2f256ebe568b9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 14:14:41 +1000 Subject: [PATCH 24/94] Cleanup AsyncPhase's transformer Transform the ClassDef and DefDef in separate cases. This reduces some of manual atOwner and treeCopy busywork. --- .../nsc/transform/async/AsyncPhase.scala | 71 +++++++++---------- .../nsc/transform/async/AsyncTransform.scala | 3 +- .../nsc/async/AnnotationDrivenAsync.scala | 7 +- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index dda8e0567fff..b86fe8ed5be3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -12,9 +12,9 @@ package scala.tools.nsc.transform.async +import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.tools.nsc.Mode -import scala.tools.nsc.transform.async.user.{FutureSystem, ScalaConcurrentFutureSystem} +import scala.tools.nsc.transform.async.user.FutureSystem import scala.tools.nsc.transform.{Transform, TypingTransformers} abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTransform{ @@ -48,49 +48,46 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr def newTransformer(unit: CompilationUnit): Transformer = new AsyncTransformer(unit) - // TODO: support more than the original late expansion tests // 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/...? - class AsyncTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + final class AsyncTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - lazy val uncurrier = new uncurry.UnCurryTransformer(unit) - lazy val eraser = new erasure.ErasureTransformer(unit) + private lazy val liftables = new mutable.AnyRefMap[Symbol, List[Tree]]() + // Together, these transforms below target this tree shaps + // { + // class $STATE_MACHINE extends ... { + // def $APPLY_METHOD(....) = { + // ... + // }.updateAttachment(FutureSystemAttachment(...)) + // } + // } + // + // The RHS of the method is transformed into a state machine with that transformation tailored by the + // attached `FutureSystem`. Local val/var/def/class/object trees that are referred to from multiple states + // are lifted into members of the enclosing class. override def transform(tree: Tree): Tree = super.transform(tree) match { - // { - // class stateMachine$async extends scala.runtime.AbstractFunction1 with Function0$mcV$sp { - // def apply(tr$async: scala.util.Try): Unit = { // symbol of this def is `applySym`, symbol of its param named "tr$async" is `trParamSym` - // ... - // } - // val stateMachine = ... - // ... - // } - case tree if tree.hasAttachment[FutureSystemAttachment] => - val saved = currentTransformState + case cd: ClassDef if liftables.contains(cd.symbol) => + deriveClassDef(cd)(impl => deriveTemplate(impl)(liftables.remove(cd.symbol).getOrElse(Nil) ::: _)) + + case dd: DefDef if tree.hasAttachment[FutureSystemAttachment] => val futureSystem = tree.getAndRemoveAttachment[FutureSystemAttachment].get.system - val newState = new AsyncTransformState[global.type](global, futureSystem, unit, this) - currentTransformState = newState - try tree match { - case blk@Block((temp@ValDef(_, nme.execContextTemp, _, execContext)) :: (cd@ClassDef(mods, tpnme.stateMachine, _, impl@Template(parents, self, stats))) :: (vd@ValDef(_, nme.stateMachine, tpt, _)) :: rest, expr) if tpt.tpe.typeSymbol == cd.symbol => - val ((dd: DefDef) :: Nil, others) = stats.partition { - case dd@DefDef(mods, nme.apply, _, List(tr :: Nil), _, _) => !dd.symbol.isBridge - case _ => false - } - val asyncBody = (dd.rhs: @unchecked) match { - case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last) - } - val (newRhs, liftables) = asyncTransform(asyncBody, dd.symbol, dd.vparamss.head.head.symbol) - val newApply = deriveDefDef(dd)(_ => newRhs).setType(null) /* need to retype */ - val newStats = new ListBuffer[Tree] - newStats ++= others - newStats += newApply - newStats ++= liftables - val newTempl = treeCopy.Template(impl, parents, self, newStats.toList) - treeCopy.Block(tree, temp :: localTyper.typedClassDef(treeCopy.ClassDef(cd, mods, tpnme.stateMachine, Nil, newTempl)) :: vd :: rest, expr) - } finally { - currentTransformState = saved + val asyncBody = (dd.rhs: @unchecked) match { + case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last).setType(stats.last.tpe) + } + + atOwner(dd, dd.symbol) { + val saved = currentTransformState + currentTransformState = new AsyncTransformState[global.type](global, futureSystem, unit, this) + try { + val (newRhs, liftableFields) = asyncTransform(asyncBody, dd.symbol, dd.vparamss.head.head.symbol) + liftables(dd.symbol.owner) = liftableFields + deriveDefDef(dd)(_ => localTyper.typed(newRhs)) + } finally { + currentTransformState = saved + } } case tree => tree } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 81d79fcb22a2..3a56f418e3e0 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -103,6 +103,7 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { 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(originalOwner)) } + applyFSM.updateAttachment(new FutureSystemAttachment(futureSystem)) atPos(asyncBody.pos)(ClassDef(NoMods, tpnme.stateMachine, Nil, gen.mkTemplate(parents, noSelfType, NoMods, List(Nil), @@ -124,7 +125,7 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { val promToFuture = Select(Select(Ident(nme.stateMachine), nme.result), nme.future) - Block(List(execContextTempVal, stateMachine, newStateMachine, stateMachineToFuture), promToFuture).updateAttachment(new FutureSystemAttachment(futureSystem)) + Block(List(execContextTempVal, stateMachine, newStateMachine, stateMachineToFuture), promToFuture) } } diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index c39bd5947a8e..7db827baf0e6 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -317,12 +317,12 @@ abstract class AnnotationDrivenAsyncPlugin extends Plugin { q""" { val ${nme.execContextTemp} = () - class stateMachine$$async extends _root_.scala.tools.nsc.async.StateMachineBase { + ${q"""class stateMachine$$async extends _root_.scala.tools.nsc.async.StateMachineBase { ${q"""def apply(tr: _root_.scala.util.Either[_root_.scala.Throwable, _root_.scala.AnyRef]): _root_.scala.Unit = { $rhs () - }""".updateAttachment(ChangeOwnerAttachment(dd.symbol))} - } + }""".updateAttachment(ChangeOwnerAttachment(dd.symbol)).updateAttachment(new global.async.FutureSystemAttachment(CustomFutureFutureSystem))} + }""".updateAttachment(new global.async.FutureSystemAttachment(CustomFutureFutureSystem))} 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] @@ -331,7 +331,6 @@ abstract class AnnotationDrivenAsyncPlugin extends Plugin { } """ - wrapped.updateAttachment(new global.async.FutureSystemAttachment(CustomFutureFutureSystem)) val tree = q""" val temp = ${wrapped} From 2d20aebb19ecacf61f122af7c0f6c838e77b65d7 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 16:17:25 +1000 Subject: [PATCH 25/94] Harden Context.make debug logging against null trees. TypingTransformers can easily feed null trees into here if they don't assign `curTree`. --- src/compiler/scala/tools/nsc/typechecker/Contexts.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index 09dcea30cb4b..442739e56dc9 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -513,7 +513,7 @@ trait Contexts { self: Analyzer => c(TypeConstructorAllowed) = false registerContext(c.asInstanceOf[analyzer.Context]) - debuglog("[context] ++ " + c.unit + " / " + tree.summaryString) + debuglog("[context] ++ " + c.unit + " / " + (if (tree == null) "" else tree.summaryString)) c } From e27692114194e3393a2b1a3bd56d413f6d3cc536 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 25 Feb 2020 16:35:39 +1000 Subject: [PATCH 26/94] Integrate async's logging into debuglog The ANF transform trace is demoted to the "edit a boolean and re-compile" UI. --- .../nsc/transform/async/AnfTransform.scala | 11 +++++---- .../nsc/transform/async/AsyncTransform.scala | 12 +++++----- .../nsc/transform/async/AsyncUtils.scala | 24 ------------------- .../nsc/transform/async/LiveVariables.scala | 20 ++++++++-------- .../nsc/async/AnnotationDrivenAsync.scala | 17 ++++++++++--- 5 files changed, 36 insertions(+), 48 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index a8bf384c5955..9022c357e8c2 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -52,6 +52,7 @@ private[async] trait AnfTransform extends TransformUtils { private var currentStats = ListBuffer[Tree]() override def transform(tree: Tree): Tree = trace(tree) { + curTree = tree val treeContainsAwait = containsAwait(tree) tree match { case _: ClassDef | _: ModuleDef | _: Function | _: DefDef => @@ -332,8 +333,10 @@ private[async] trait AnfTransform extends TransformUtils { } } + private final val traceAsync = false + @inline final def trace[T](args: Any)(t: => T): T = { - if (AsyncUtils.trace) { + if (traceAsync) { tracing.apply("", args)({val tree = t; ("" + currentStats.mkString(";") + " ;; " + tree, tree)}) } else t } @@ -365,11 +368,9 @@ private[async] trait AnfTransform extends TransformUtils { def oneLine(s: Any) = s.toString.replaceAll("""\n""", "\\\\n").take(300) try { - if (AsyncUtils.trace) - AsyncUtils.trace(s"$indentString$prefix(${oneLine(args)})") + println(s"$indentString$prefix(${oneLine(args)})") val result = t - if (AsyncUtils.trace) - AsyncUtils.trace(s"$indentString= ${oneLine(result._1)}") + println(s"$indentString= ${oneLine(result._1)}") result._2 } finally { indent -= 1 diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 3a56f418e3e0..3a79b929a4a0 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -261,7 +261,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) val applyRhs = UseFields.transformAtOwner(applySym, applyBody) - if (AsyncUtils.verbose) { + if (settings.debug.value && shouldLogAtThisPhase) { val location = try asyncBody.pos.source.path catch { case _: UnsupportedOperationException => asyncBody.pos.toString } @@ -273,10 +273,10 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li } def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { - AsyncUtils.vprintln(s"In file '$location':") - AsyncUtils.vprintln(s"ANF transform expands to:\n $anfTree") - states foreach (s => AsyncUtils.vprintln(s)) - AsyncUtils.vprintln("===== DOT =====") - AsyncUtils.vprintln(block.toDot) + inform(s"In file '$location':") + inform(s"ANF transform expands to:\n $anfTree") + states foreach (s => inform(s)) + inform("===== DOT =====") + inform(block.toDot) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala deleted file mode 100644 index 8035f1f62759..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncUtils.scala +++ /dev/null @@ -1,24 +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 - -object AsyncUtils { - private def enabled(level: String) = sys.props.getOrElse(s"scala.async.$level", "false").equalsIgnoreCase("true") - - private[async] val verbose = enabled("debug") - private[async] val trace = enabled("trace") - - @inline private[async] def vprintln(s: => Any): Unit = if (verbose) println(s"[async] $s") - - @inline private[async] def trace(s: => Any): Unit = if (trace) println(s"[async] $s") -} diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index a292b2179d00..0451b499f1fe 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -80,7 +80,7 @@ trait LiveVariables extends ExprBuilder { !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym) } } - AsyncUtils.vprintln(s"fields never zero-ed out: ${noNull.mkString(", ")}") + debuglog(s"fields never zero-ed out: ${noNull.mkString(", ")}") /** * Traverse statements of an `AsyncState`, collect `Ident`-s referring to lifted fields. @@ -180,9 +180,9 @@ trait LiveVariables extends ExprBuilder { val finalStates = asyncStates.filter(as => !asyncStates.exists(other => isPred(as.state, other.state))) val finalState = finalStates.head - if(AsyncUtils.verbose) { + if(settings.debug.value && shouldLogAtThisPhase) { for (as <- asyncStates) - AsyncUtils.vprintln(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") + debuglog(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") } /* Backwards data-flow analysis. Computes live variables information at entry and exit @@ -246,10 +246,10 @@ trait LiveVariables extends ExprBuilder { currStates = exitChanged } - if(AsyncUtils.verbose) { + if(settings.debug.value && shouldLogAtThisPhase) { for (as <- asyncStates) { - AsyncUtils.vprintln(s"LVentry at state #${as.state}: ${LVentry(as.state).mkString(", ")}") - AsyncUtils.vprintln(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}") + debuglog(s"LVentry at state #${as.state}: ${LVentry(as.state).mkString(", ")}") + debuglog(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}") } } @@ -282,9 +282,9 @@ trait LiveVariables extends ExprBuilder { val lastUsages: mutable.LinkedHashMap[Tree, StateSet] = mutable.LinkedHashMap(liftables.map(fld => fld -> lastUsagesOf(fld, finalState)): _*) - if(AsyncUtils.verbose) { + if(settings.debug.value && shouldLogAtThisPhase) { for ((fld, lastStates) <- lastUsages) - AsyncUtils.vprintln(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}") + debuglog(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}") } val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] = @@ -308,9 +308,9 @@ trait LiveVariables extends ExprBuilder { (fld, result) } - if(AsyncUtils.verbose) { + if(settings.debug.value && shouldLogAtThisPhase) { for ((fld, killAt) <- nullOutAt) - AsyncUtils.vprintln(s"field ${fld.symbol.name} should be nulled out in states ${killAt.iterator.mkString(", ")}") + debuglog(s"field ${fld.symbol.name} should be nulled out in states ${killAt.iterator.mkString(", ")}") } nullOutAt diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 7db827baf0e6..c5ab7cf93c53 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -10,6 +10,7 @@ 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 @@ -36,6 +37,8 @@ class AnnotationDrivenAsync { @Test def testCaseClassLifting(): Unit = { + // Note: this emits a warning under -Ydebug (which we sometimes manually set below in the compiler setup) + // 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} @@ -242,15 +245,23 @@ class AnnotationDrivenAsync { def run(code: String): Any = { val out = createTempDir() try { - val reporter = new StoreReporter + val reporter = new StoreReporter { + override protected def info0(pos: Position, msg: String, severity: Severity, force: Boolean): Unit = { + if (severity == INFO) println(msg) + else super.info0(pos, msg, severity, force) + } + } val settings = new Settings(println(_)) settings.outdir.value = out.getAbsolutePath settings.embeddedDefaults(getClass.getClassLoader) + // settings.debug.value = true // settings.uniqid.value = true // settings.processArgumentString("-Xprint:all -nowarn") - // sys.props("scala.async.trace") = "true" - // sys.props("scala.async.debug") = "true" + // settings.log.value = List("async") + + // NOTE: edit ANFTransform.traceAsync to `= true` to get additional diagnostic tracing. + val isInSBT = !settings.classpath.isSetByUser if (isInSBT) settings.usejavacp.value = true val global = new Global(settings, reporter) { From 588cab4cc14f7eb827998c7bb789100afbbd71e4 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 27 Feb 2020 16:40:27 +1000 Subject: [PATCH 27/94] Refactor and document UseFields transform Also, lifted lazy var fields need not be MUTABLE. --- .../nsc/transform/async/AsyncTransform.scala | 48 +++++++++---------- .../tools/nsc/transform/async/Lifter.scala | 6 ++- .../nsc/async/AnnotationDrivenAsync.scala | 15 ++++++ 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 3a79b929a4a0..235cd1b9f3a6 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -217,50 +217,46 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li } } - // Replace the ValDefs in the async block with Assigns to the corresponding lifted - // fields. Similarly, replace references to them with references to the field. + // Adjust the tree to: + // - lifted local variables are entered into the scope of the state machine class + // - references to them are rewritten as referencs to the fields. + // - the rhs of ValDefs that initialize such fields is turned into an assignment to the field object UseFields extends explicitOuter.OuterPathTransformer(currentTransformState.unit) { private def fieldSel(tree: Tree) = { assert(currentOwner != NoSymbol) - val outerOrThis = if (stateMachineClass == currentClass) This(stateMachineClass) else { + val outerOrThis = if (stateMachineClass == currentClass) gen.mkAttributedThis(stateMachineClass) else { + // These references need to be selected from an outer reference, because explicitouter + // has already run we must perform this transform explicitly here. tree.symbol.makeNotPrivate(tree.symbol.owner) outerPath(outerValue, currentClass.outerClass, stateMachineClass) } atPos(tree.pos)(Select(outerOrThis.setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) } override def transform(tree: Tree): Tree = tree match { - case _ if currentOwner == stateMachineClass => - super.transform(tree) - case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => - assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner((tree.symbol, currentOwner))))) - case _: DefTree if liftedSyms(tree.symbol) => + case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) && currentOwner == applySym => + // Drop the lifted definitions from the apply method + assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner(tree.symbol, currentOwner)))) + case _: DefTree if liftedSyms(tree.symbol) && currentOwner == applySym => + // Drop the lifted definitions from the apply method EmptyTree + case md: MemberDef if currentOwner == stateMachineClass && liftedSyms(tree.symbol)=> + stateMachineClass.info.decls.enter(md.symbol) + super.transform(tree) case Assign(i @ Ident(name), rhs) if liftedSyms(i.symbol) => - // Use localTyper to adapt () to BoxedUnit in `val ifRes: Object; if (cond) "" else ()` - treeCopy.Assign(tree, fieldSel(i), localTyper.typed(transform(rhs), i.symbol.tpe)) + treeCopy.Assign(tree, fieldSel(i), adapt(transform(rhs), i.symbol.tpe)) case Ident(name) if liftedSyms(tree.symbol) => fieldSel(tree).setType(tree.tpe) case _ => super.transform(tree) } - } - - val liftablesUseFields = liftedFields.map { - case vd: ValDef => vd - case x => UseFields.transformAtOwner(stateMachineClass, x) - } - liftablesUseFields.foreach { t => - if (t.symbol != null && t.symbol.owner == stateMachineClass) { - stateMachineClass.info.decls.enter(t.symbol) - // TODO AM: refine the resetting of the lazy flag -- this is so that local lazy vals that are lifted to the class - // actually get their LazyRef allocated to the var that holds the lazy val's reference - t.symbol.resetFlag(Flags.LAZY) - } + // Use localTyper to adapt () to BoxedUnit in `val ifRes: Object; if (cond) "" else ()` + private def adapt(tree: Tree, pt: Type): Tree = localTyper.typed(tree, pt) } - val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) - val applyRhs = UseFields.transformAtOwner(applySym, applyBody) + val ClassDef(_, _, _, Template(_, _, (applyDefDef: DefDef) :: liftablesUseFields)) = + UseFields.transformAtOwner(stateMachineClass.owner, ClassDef(stateMachineClass, DefDef(applySym, applyBody) :: liftedFields)) + if (settings.debug.value && shouldLogAtThisPhase) { val location = try asyncBody.pos.source.path catch { case _: UnsupportedOperationException => asyncBody.pos.toString @@ -269,7 +265,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li } futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) - (cleanupContainsAwaitAttachments(applyRhs), liftablesUseFields) + (cleanupContainsAwaitAttachments(applyDefDef.rhs), liftablesUseFields) } def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index a5f3e6d0fd3c..fffab47b318a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -145,10 +145,12 @@ trait Lifter extends ExprBuilder { val sym = t.symbol val treeLifted = t match { case vd@ValDef(_, _, tpt, rhs) => - sym.setFlag(MUTABLE | STABLE | PRIVATE | LOCAL) + val isLazy = sym.isLazy + sym.setFlag(STABLE | PRIVATE | LOCAL) + if (isLazy) sym.resetFlag(LAZY) else sym.setFlag(MUTABLE) sym.setName(name.fresh(sym.name.toTermName)) sym.setInfo(sym.info.deconst) - val rhs1 = if (sym.asTerm.isLazy) rhs else EmptyTree + val rhs1 = if (isLazy) rhs else EmptyTree treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(sym.info).setPos(t.pos), rhs1) case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) => sym.setName(this.name.freshen(sym.name.toTermName)) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index c5ab7cf93c53..498526ab793b 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -35,6 +35,21 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def testLiftedLazyVal(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.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) } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals((1, 1, -1), run(code)) + } + @Test def testCaseClassLifting(): Unit = { // Note: this emits a warning under -Ydebug (which we sometimes manually set below in the compiler setup) From 528ffd2caad4a65275ef7ae1890b46f8a8f6a81c Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 3 Mar 2020 15:34:20 +1000 Subject: [PATCH 28/94] Avoid overhead in tree attachments Deal with the underlying Set directly rather than using the ClassTag indexed lookup/removal. --- build.sbt | 5 +++++ .../nsc/transform/async/TransformUtils.scala | 14 +++++++------- .../scala/reflect/macros/Attachments.scala | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 1964588ddf4d..7e25505470e3 100644 --- a/build.sbt +++ b/build.sbt @@ -450,6 +450,7 @@ val mimaFilterSettings = Seq { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashTrieMap.getOrElse0"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashMap1.getOrElse0"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.HashMap#HashMapCollision1.getOrElse0"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.reflect.runtime.ReflectSetup.phaseWithId"), ProblemFilters.exclude[MissingClassProblem]("scala.math.Ordering$IterableOrdering"), ProblemFilters.exclude[MissingClassProblem]("scala.math.Ordering$Tuple4Ordering"), @@ -461,6 +462,10 @@ val mimaFilterSettings = Seq { ProblemFilters.exclude[MissingClassProblem]("scala.math.Ordering$Tuple6Ordering"), ProblemFilters.exclude[MissingClassProblem]("scala.math.Ordering$Tuple8Ordering"), ProblemFilters.exclude[MissingClassProblem]("scala.math.Ordering$Tuple5Ordering"), + + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.removeElement"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.addElement"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.containsElement"), ) } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 0cb2ee8326e0..63ac053d6736 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -244,11 +244,11 @@ private[async] trait TransformUtils extends PhasedTransform { case _ => false } private def attachContainsAwait(t: Tree): Unit = if (shouldAttach(t)) { - t.updateAttachment(ContainsAwait) - t.removeAttachment[NoAwait.type] + t.setAttachments(t.attachments.addElement(ContainsAwait)) + t.setAttachments(t.attachments.removeElement(NoAwait)) } private def attachNoAwait(t: Tree): Unit = if (shouldAttach(t)) { - t.updateAttachment(NoAwait) + t.setAttachments(t.attachments.addElement(NoAwait)) } var stack: List[Tree] = Nil @@ -269,8 +269,8 @@ private[async] trait TransformUtils extends PhasedTransform { t.foreach { case _: CannotHaveAttrs => case t => - t.removeAttachment[ContainsAwait.type] - t.removeAttachment[NoAwait.type] + t.setAttachments(t.attachments.removeElement(ContainsAwait)) + t.setAttachments(t.attachments.removeElement(NoAwait)) } t } @@ -291,5 +291,5 @@ private[async] trait TransformUtils extends PhasedTransform { } } -case object ContainsAwait -case object NoAwait +private case object ContainsAwait +private case object NoAwait diff --git a/src/reflect/scala/reflect/macros/Attachments.scala b/src/reflect/scala/reflect/macros/Attachments.scala index 157d142f2e22..6daffe092459 100644 --- a/src/reflect/scala/reflect/macros/Attachments.scala +++ b/src/reflect/scala/reflect/macros/Attachments.scala @@ -74,6 +74,24 @@ abstract class Attachments { self => else new NonemptyAttachments[Pos](this.pos, newAll) } } + /** Creates a copy of this attachment with the given element removed. */ + final def removeElement[T](attachment: T): Attachments { type Pos = self.Pos } = { + val newAll = all - attachment + if (newAll eq all) this + else if (newAll.isEmpty) pos.asInstanceOf[Attachments { type Pos = self.Pos }] + else new NonemptyAttachments[Pos](this.pos, newAll) + } + /** Creates a copy of this attachment with the given element added. */ + final def addElement[T](attachment: T): Attachments { type Pos = self.Pos } = { + val newAll = all + attachment + if (newAll eq all) this + else new NonemptyAttachments[Pos](this.pos, newAll) + } + + /** Tests if the given element is attached. */ + final def containsElement(element: Any): Boolean = { + all.contains(element) + } def isEmpty: Boolean = true } From b61757586d6764e109507441ba407528f4643c29 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 3 Mar 2020 14:41:36 +1000 Subject: [PATCH 29/94] Reduce number of states in async state machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We used to lift the control flow for any pattern match or if/else into fine grained states if it enclosed even a single await call. Instead, we now keep as much of the control flow as possible in the current state. The program point after the control flow statement is lifted into a new state iff it is reachable from any branch that contains an await. LabelDef-s representing cases and the matchEnd of patterns are now only lifted into states if they are the target of a label jump from a resumption handler. To make this analysis possible, we form a context chain of AsyncBlockBuilders to let the translation of the label jumps compare the current state to that current state of the enclosing block builder that holds the LabelDefs. If these differ, we can conclude that we have crossed an async boundary and mark the LabelDef for lifting into state. Here's how the simplification helps: https://gist.github.com/retronym/50839a051db9b6d1fcd0719d99ecbfc2 Refactorings along the way: - introduce a ThicketTransformer to transform `Tree => List[Tree]` - make `symLookup` more convenient to access - add some internal flags to disable state compaction and field nulling which help to debug the phase - unify onto a single `AsyncState` class. Different use cases are served by eagerly adding additional logic, rather than having this added later on in the variants of `mkCompletionHandler` - detect and remove empty states in `compactStates`. It seems easier now to do this than to avoid constructing them. - Try a bit harder to avoid redundant `()` in stats.   - Move synthesis of async block generation into     StateBuilder.build (formrly known as resultSimple). This is     now the only factory method on StateBuilder.   - Fix propagation of the enclosing subsequent state into     nested expression positions   - In combination, this lets us perform async block completion     inline in all terminal states, there is no need to write the     result to lifted field and make a state transition.   - Remove the `currTree` var in favour of delegating to     the field in the `stateBuilder` var   - Encapsulate state transition bookkeeping --- .../nsc/backend/jvm/BCodeIdiomatic.scala | 2 +- .../nsc/transform/async/AnfTransform.scala | 24 +- .../nsc/transform/async/AsyncPhase.scala | 5 +- .../nsc/transform/async/AsyncTransform.scala | 56 +- .../nsc/transform/async/ExprBuilder.scala | 878 +++++++++--------- .../tools/nsc/transform/async/Lifter.scala | 4 +- .../nsc/transform/async/LiveVariables.scala | 15 +- .../nsc/transform/async/StateAssigner.scala | 11 + .../tools/nsc/transform/async/StateSet.scala | 7 + .../nsc/transform/async/TransformUtils.scala | 139 +-- .../nsc/async/AnnotationDrivenAsync.scala | 121 ++- 11 files changed, 694 insertions(+), 568 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index ff609672cb89..35899b232160 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -456,7 +456,7 @@ abstract class BCodeIdiomatic { i = 1 while (i < keys.length) { if (keys(i-1) == keys(i)) { - abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see scala/bug#6011.") + abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see scala/bug#6011: " + keys.sorted.toList) } i += 1 } diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 9022c357e8c2..822f951c9e25 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -165,9 +165,8 @@ private[async] trait AnfTransform extends TransformUtils { treeCopy.Match(tree, scrutExpr, casesWithAssign) } - case LabelDef(name, params, rhs) => + case ld @ LabelDef(name, params, rhs) => treeCopy.LabelDef(tree, name, params, transformNewControlFlowBlock(rhs)) - case _ => super.transform(tree) } @@ -186,9 +185,8 @@ private[async] trait AnfTransform extends TransformUtils { private def transformMatchOrIf[T <: Tree](tree: Tree, needsResultVar: Boolean, nameSource: asyncNames.NameSource[TermName])(core: Symbol => T): Tree = { // if type of if/match is Unit don't introduce assignment, // but add Unit value to bring it into form expected by async transform - if (typeEqualsUnit(tree.tpe)) { - currentStats += assignUnitType(core(NoSymbol)) - atPos(tree.pos)(literalUnit) + if (isUnitType(tree.tpe)) { + assignUnitType(core(NoSymbol)) } else if (tree.tpe =:= definitions.NothingTpe) { currentStats += assignUnitType(core(NoSymbol)) localTyper.typedPos(tree.pos)(Throw(New(IllegalStateExceptionClass))) @@ -281,7 +279,7 @@ private[async] trait AnfTransform extends TransformUtils { val param = ld.params.head def unitLabelDef = { - setUnitMethodInfo(ld.symbol) + ld.symbol.setInfo(MethodType(Nil, definitions.UnitTpe)) assignUnitType(treeCopy.LabelDef(ld, ld.name, Nil, literalUnit)) } @@ -378,7 +376,7 @@ private[async] trait AnfTransform extends TransformUtils { } } - final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) extends TypingTransformer(currentTransformState.unit) { + final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) extends ThicketTransformer(currentTransformState.unit) { override def transform(tree: Tree): Tree = { tree match { case _: Function | _: MemberDef => @@ -386,19 +384,11 @@ private[async] trait AnfTransform extends TransformUtils { case Apply(fun, arg :: Nil) if isLabel(fun.symbol) && caseDefToMatchResult.contains(fun.symbol) => val temp = caseDefToMatchResult(fun.symbol) if (temp == NoSymbol) - treeCopy.Block(tree, transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil)) + Thicket(treeCopy.Block(tree, transform(arg) :: Nil, treeCopy.Apply(tree, fun, Nil))) else if (arg.tpe.typeSymbol == definitions.NothingClass) { transform(arg) } else { - treeCopy.Block(tree, typedAssign(transform(arg), temp) :: Nil, treeCopy.Apply(tree, fun, Nil)) - } - case Block(stats, expr: Apply) if isLabel(expr.symbol) => - super.transform(tree) match { - case Block(stats0, Block(stats1, expr1)) => - // flatten the block returned by `case Apply` above into the enclosing block for - // cleaner generated code. - treeCopy.Block(tree, stats0 ::: stats1, expr1) - case t => t + Thicket(treeCopy.Block(tree, typedAssign(transform(arg), temp) :: Nil, treeCopy.Apply(tree, fun, Nil))) } case _ => super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index b86fe8ed5be3..a3ac1ff9f8ce 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -13,7 +13,6 @@ package scala.tools.nsc.transform.async import scala.collection.mutable -import scala.collection.mutable.ListBuffer import scala.tools.nsc.transform.async.user.FutureSystem import scala.tools.nsc.transform.{Transform, TypingTransformers} @@ -80,9 +79,9 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr atOwner(dd, dd.symbol) { val saved = currentTransformState - currentTransformState = new AsyncTransformState[global.type](global, futureSystem, unit, this) + currentTransformState = new AsyncTransformState[global.type](global, futureSystem, unit, this, dd.symbol.owner, dd.symbol, dd.vparamss.head.head.symbol, asyncBody.tpe) try { - val (newRhs, liftableFields) = asyncTransform(asyncBody, dd.symbol, dd.vparamss.head.head.symbol) + val (newRhs, liftableFields) = asyncTransform(asyncBody) liftables(dd.symbol.owner) = liftableFields deriveDefDef(dd)(_ => localTyper.typed(newRhs)) } finally { diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 235cd1b9f3a6..6a30ee13ab53 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -131,11 +131,43 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { class AsyncTransformState[U <: Global with Singleton](val symbolTable: U, val futureSystem: FutureSystem, val unit: U#CompilationUnit, - val typingTransformer: TypingTransformers#TypingTransformer) { + val typingTransformer: TypingTransformers#TypingTransformer, + val stateMachineClass: U#Symbol, + val applyMethod: U#Symbol, + 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]() + val symLookup = new SymLookup() { + val global: symbolTable.type = symbolTable + val stateMachineClass: global.Symbol = AsyncTransformState.this.stateMachineClass.asInstanceOf[symbolTable.Symbol] + val applyTrParam: global.Symbol = AsyncTransformState.this.applyTrParam.asInstanceOf[symbolTable.Symbol] + } +} + +abstract class SymLookup { + val global: Global + import global._ + val stateMachineClass: Symbol + val applyTrParam: Symbol + + 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) + + lazy val stateGetter: Symbol = stateMachineMember(nme.state) + lazy val stateSetter: Symbol = stateGetter.setterIn(stateGetter.owner) + + def selectResult = Apply(memberRef(nme.result), Nil) + def applyMethod: Symbol = applyTrParam.owner + lazy val whileLabel: Symbol = applyMethod.newLabel(nme.WHILE_PREFIX).setInfo(MethodType(Nil, definitions.UnitTpe)) } trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables with TypingTransformers { @@ -168,7 +200,8 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li }; }; */ - def asyncTransform(asyncBody: Tree, applySym: Symbol, trParamSym: Symbol): (Tree, List[Tree]) = { + def asyncTransform(asyncBody: Tree): (Tree, List[Tree]) = { + val applySym = currentTransformState.applyMethod val futureSystem = currentTransformState.futureSystem val futureSystemOps = futureSystem.mkOps(global) @@ -190,22 +223,25 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li cleanupContainsAwaitAttachments(anfTree) markContainsAwait(anfTree) - val asyncBlock = buildAsyncBlock(anfTree, SymLookup(stateMachineClass, trParamSym)) + val asyncBlock = buildAsyncBlock(anfTree) val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) // live variables analysis // the result map indicates in which states a given field should be nulled out - val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, liftedFields) + val nullOut = true + if (nullOut) { + val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields) - for ((state, flds) <- assignsOf) { - val assigns = flds.map { fld => - val fieldSym = fld.symbol - Assign(gen.mkAttributedStableRef(thisType(fieldSym.owner), fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos)) + for ((state, flds) <- assignsOf) { + val assigns = flds.map { fld => + val fieldSym = fld.symbol + Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos)) + } + val asyncState = asyncBlock.asyncStates.find(_.state == state).get + asyncState.stats = assigns ++ asyncState.stats } - val asyncState = asyncBlock.asyncStates.find(_.state == state).get - asyncState.stats = assigns ++ asyncState.stats } val liftedSyms = liftedFields.map(_.symbol).toSet diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index d5c1b534e7b8..a3b0a24ecf7b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -20,212 +20,91 @@ import scala.collection.mutable.ListBuffer import scala.language.existentials trait ExprBuilder extends TransformUtils { - import global._ - def tryAny = transformType(currentTransformState.ops.tryType(definitions.AnyTpe)) + import global._ private def stateAssigner = currentTransformState.stateAssigner private def labelDefStates = currentTransformState.labelDefStates - trait AsyncState { - def state: Int - - def nextStates: Array[Int] - - def mkHandlerCaseForState[T]: CaseDef - - def mkOnCompleteHandler[T]: Option[CaseDef] = None - - var stats: List[Tree] + final class AsyncState(var stats: List[Tree], val state: Int, var nextStates: Array[Int], val isEmpty: Boolean) { + def mkHandlerCaseForState: CaseDef = CaseDef(Literal(Constant(state)), EmptyTree, adaptToUnit(stats)) - def treeThenStats(tree: Tree): List[Tree] = - adaptToUnitIgnoringNothing(tree :: stats) :: Nil - - final def allStats: List[Tree] = this match { - case a: AsyncStateWithAwait => treeThenStats(a.awaitable.resultValDef) - case _ => stats - } - - final def body: Tree = stats match { - case stat :: Nil => stat - case init :+ last => Block(init, last) - case Nil => literalUnit - } - } - - /** A sequence of statements that concludes with a unconditional transition to `nextState` */ - final class SimpleAsyncState(var stats: List[Tree], val state: Int, nextState: Int, symLookup: SymLookup) - extends AsyncState { - - val nextStates: Array[Int] = - Array(nextState) - - def mkHandlerCaseForState[T]: CaseDef = { - mkHandlerCase(state, treeThenStats(mkStateTree(nextState, symLookup))) - } - - override val toString: String = - s"AsyncState #$state, next = $nextState" - } - - /** A sequence of statements with a conditional transition to the next state, which will represent - * a branch of an `if` or a `match`. - */ - final class AsyncStateWithoutAwait(var stats: List[Tree], val state: Int, val nextStates: Array[Int]) extends AsyncState { - override def mkHandlerCaseForState[T]: CaseDef = - mkHandlerCase(state, stats) - - override val toString: String = - s"AsyncStateWithoutAwait #$state, nextStates = ${nextStates.toList}" - } - - /** A sequence of statements that concludes with an `await` call. The `onComplete` - * handler will unconditionally transition to `nextState`. - */ - final class AsyncStateWithAwait(var stats: List[Tree], val state: Int, val onCompleteState: Int, nextState: Int, - val awaitable: Awaitable, symLookup: SymLookup) - extends AsyncState { - - val nextStates: Array[Int] = - Array(nextState) - - override def mkHandlerCaseForState[T]: CaseDef = { - val futureSystem = currentTransformState.futureSystem - val futureSystemOps = futureSystem.mkOps(global) - val fun = This(tpnme.EMPTY) - val tryGetOrCallOnComplete: List[Tree] = - if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempAwaitableSym = symLookup.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) - val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) - val tempCompletedSym = symLookup.applyTrParam.owner.newTermSymbol(nme.completed).setInfo(tryAny) - val initTempCompleted = ValDef(tempCompletedSym, futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) - val null_ne = Select(Literal(Constant(null)), TermName("ne")) - val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) - val ifTree = - If(Apply(null_ne, Ident(tempCompletedSym) :: Nil), - adaptToUnit(ifIsFailureTree[T](Ident(tempCompletedSym)) :: Nil), - Block(toList(callOnComplete), Return(literalUnit))) - initAwaitableTemp :: initTempCompleted :: ifTree :: Nil - } else { - val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) - toList(callOnComplete) ::: Return(literalUnit) :: Nil - } - mkHandlerCase(state, stats ++ List(mkStateTree(onCompleteState, symLookup)) ++ tryGetOrCallOnComplete) - } - - /* if (tr.isFailure) - * result.complete(tr.asInstanceOf[Try[T]]) - * else { - * = tr.get.asInstanceOf[] - * - * - * } - */ - def ifIsFailureTree[T](tryReference: => Tree) = { - val futureSystem = currentTransformState.futureSystem - val futureSystemOps = futureSystem.mkOps(global) - - assert(isPastErasure) - val tryyGet = futureSystemOps.tryyGet[Any](tryReference) - - val getAndUpdateState = Block(List(Assign(Ident(awaitable.resultName), tryyGet)), mkStateTree(nextState, symLookup)) - if (futureSystem.emitTryCatch) { - If(futureSystemOps.tryyIsFailure(tryReference), - Block(toList(futureSystemOps.completeProm[T]( - symLookup.selectResult, - tryReference)), - Return(literalUnit)), - getAndUpdateState - ) - } else { - getAndUpdateState - } - } - - override def mkOnCompleteHandler[T]: Option[CaseDef] = { - Some(mkHandlerCase(onCompleteState, List(ifIsFailureTree[T](Ident(symLookup.applyTrParam))))) - } - - override val toString: String = - s"AsyncStateWithAwait #$state, next = $nextState" + override def toString: String = mkToString + " (was: " + initToString + ")" + private def mkToString = s"AsyncState #$state, next = ${nextStates.toList}" + private val initToString = mkToString } /* * Builder for a single state of an async expression. */ - final class AsyncStateBuilder(state: Int, private val symLookup: SymLookup) { + private final class AsyncStateBuilder(val state: Int, val owner: AsyncBlockBuilder) { /* Statements preceding an await call. */ - private val stats = ListBuffer[Tree]() - /** The state of the target of a LabelDef application (while loop jump) */ - private var nextJumpState: Option[Int] = None - private var nextJumpSymbol: Symbol = NoSymbol - def effectiveNextState(nextState: Int) = - nextJumpState.orElse(if (nextJumpSymbol == NoSymbol) None - else Some(stateIdForLabel(nextJumpSymbol))).getOrElse(nextState) - - def +=(stat: Tree): this.type = { - // Allow `()` (occurs in do/while) - assert(isLiteralUnit(stat) || nextJumpState.isEmpty, s"statement appeared after a label jump: $stat") - - def addStat() = stats += stat - stat match { - case Apply(fun, args) if isLabel(fun.symbol) => - // labelDefStates belongs to the current ExprBuilder - labelDefStates get fun.symbol match { - case opt@Some(nextState) => - // A backward jump - nextJumpState = opt // re-use object - nextJumpSymbol = fun.symbol - case None => - // We haven't the corresponding LabelDef, this is a forward jump - nextJumpSymbol = fun.symbol + val stats = ListBuffer[Tree]() + + val nextStates: StateSet = new StateSet + private val jumpReplacer = new JumpReplacer(nextStates, state, shouldReplace(_)) + + private def shouldReplace(target: Symbol): Boolean = labelDefStates.contains(target) || { + val patternOwnerOption = owner.outerIterator.find(_.patternSyms.contains(target)) + patternOwnerOption match { + case Some(patternOwner) => + // The target of this jump is owned by an enclosing block builder + + if (patternOwner != owner && patternOwner.currState == owner.currState) { + assert(owner.currState == owner.startState, (owner.currState, owner.startState)) + // .. but we are still in our first state which is also the current state of the + // enclosing, then the current section of code will be `incorporate`d + // into a branch of normal control flow in the enclosing builder, at which + // point we need can reach the jump target without a state transition. + false + } else { + // ... otherwise, indicate that this case symbol needs to be addressable as a state + // and that this label jump should be replaced by a state transition. + true } - case _ => addStat() + case _ => false } - this } - def resultWithAwait(awaitable: Awaitable, - onCompleteState: Int, - nextState: Int): AsyncState = { - new AsyncStateWithAwait(stats.toList, state, onCompleteState, effectiveNextState(nextState), awaitable, symLookup) - } - - def resultSimple(nextState: Int): AsyncState = { - new SimpleAsyncState(stats.toList, state, effectiveNextState(nextState), symLookup) - } - - def resultWithIf(condTree: Tree, thenState: Int, elseState: Int): AsyncState = { - def mkBranch(state: Int) = mkStateTree(state, symLookup) - this += If(condTree, mkBranch(thenState), mkBranch(elseState)) - new AsyncStateWithoutAwait(stats.toList, state, Array(thenState, elseState)) + def ++=(stats: List[Tree]): this.type = {stats.foreach(+=(_)); this} + def +=(stat: Tree): this.type = { + stats ++= jumpReplacer.atOwner(currentTransformState.localTyper.context.owner) { + jumpReplacer.apply(stat) + } + this } - /** - * Build `AsyncState` ending with a match expression. - * - * The cases of the match simply resume at the state of their corresponding right-hand side. - * - * @param scrutTree tree of the scrutinee - * @param cases list of case definitions - * @param caseStates starting state of the right-hand side of the each case - * @return an `AsyncState` representing the match expression - */ - def resultWithMatch(scrutTree: Tree, cases: List[CaseDef], caseStates: Array[Int], symLookup: SymLookup): AsyncState = { - // 1. build list of changed cases - val newCases = for ((cas, num) <- cases.zipWithIndex) yield cas match { - case CaseDef(pat, guard, rhs) => - val bindAssigns = rhs.children.takeWhile(isSyntheticBindVal) - CaseDef(pat, guard, Block(bindAssigns, mkStateTree(caseStates(num), symLookup))) + /** Build the state using the accumulated `stats` followed by a state transition. */ + def build(nextState: Int, style: StateTransitionStyle): AsyncState = { + // Record whether this state was free of meaningful stats (exclkuding unit literals which creep in after + // the ANF and state maching transforms and the state transition code added bekow. + // + // Empty stats that have a single successor state will be eliminated in `filterStates`. + val isEmpty = !style.isInstanceOf[StateTransitionStyle.UpdateAndAwait] && stats.forall(isLiteralUnit) + val allNextStates = new StateSet + val concludesWithJump = stats.lastOption match { + case Some(Apply(fun, args)) if isLabel(fun.symbol) => true + case _ => false } - // 2. insert changed match tree at the end of the current state - this += Match(scrutTree, newCases) - new AsyncStateWithoutAwait(stats.toList, state, caseStates) - } + def completeAsyncBlock(): Unit = { + if (isUnitType(currentTransformState.asyncType)) { + stats += completeSuccess(literalBoxedUnit) + } else if (currentTransformState.asyncType.typeSymbol == definitions.NothingClass) { + // An exception should bubble out to the enclosing handler, don't insert a complete call. + } else { + val expr = stats.remove(stats.size - 1) + stats += completeSuccess(expr) + } + stats += Return(literalUnit).setSymbol(currentTransformState.applyMethod) + allNextStates -= nextState + } + if (nextState == StateAssigner.Terminal) + completeAsyncBlock() + else if (!concludesWithJump) + stats ++= style.trees(nextState, allNextStates) - def resultWithLabel(startLabelState: Int, symLookup: SymLookup): AsyncState = { - this += mkStateTree(startLabelState, symLookup) - new AsyncStateWithoutAwait(stats.toList, state, Array(startLabelState)) + nextStates.foreach(allNextStates += _) + new AsyncState(stats.toList, state, allNextStates.toArray, isEmpty) } override def toString: String = { @@ -241,151 +120,206 @@ trait ExprBuilder extends TransformUtils { * @param expr the last expression of the block * @param startState the start state * @param endState the state to continue with + * @param outer the enclosing block buildemr or `None` if this is the root builder. */ - final private class AsyncBlockBuilder(stats: List[Tree], expr: Tree, startState: Int, endState: Int, - private val symLookup: SymLookup) { - val asyncStates = ListBuffer[AsyncState]() + final private class AsyncBlockBuilder(stats: List[Tree], expr: Tree, val startState: Int, val endState: Int, + startToEndUpdateStyle: StateTransitionStyle, + val outer: Option[AsyncBlockBuilder] = None) { + val patternSyms: Set[Symbol] = (expr :: stats).collect { + case ld: LabelDef if isMatchEndLabel(ld.symbol) || isCaseLabel(ld.symbol) => ld.symbol + }.toSet + private var stateBuilder = new AsyncStateBuilder(startState, this) + private val statesMap = mutable.LinkedHashMap[Int, AsyncState]() + private var building = true + + def build: List[AsyncState] = { + try statesMap.values.toList + finally building = false + } - var stateBuilder = new AsyncStateBuilder(startState, symLookup) - var currState = startState + def outerIterator: Iterator[AsyncBlockBuilder] = Iterator.iterate(this)(_.outer.orNull).takeWhile(_ ne null) + def currState: Int = stateBuilder.state - def checkForUnsupportedAwait(tree: Tree) = if (containsAwait(tree)) - global.reporter.error(tree.pos, "await must not be used in this position") + addStats() - def nestedBlockBuilder(nestedTree: Tree, startState: Int, endState: Int) = { - val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) - new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, symLookup) + private def addState(state: AsyncState): AsyncState = { + assert(building) + assert(!statesMap.contains(state.state), "Duplicate state: " + state) + statesMap(state.state) = state + state } - - def nextState() = stateAssigner.nextState() - def directlyAdjacentLabelDefs(t: Tree): List[Tree] = { - def isPatternCaseLabelDef(t: Tree) = t match { - case LabelDef(name, _, _) => name.toString.startsWith("case") - case _ => false - } - val span = (stats :+ expr).filterNot(isLiteralUnit).span(_ ne t) - span match { - case (before, _ :: after) => - before.reverse.takeWhile(isPatternCaseLabelDef) ::: after.takeWhile(isPatternCaseLabelDef) - case _ => - stats :+ expr + private def addStats(): Unit = { + stats.foreach(stat => add(stat, isExpr = false)) + add(expr, isExpr = true) + def isRoot = outer.isEmpty + if (!stateBuilder.stats.isEmpty || isRoot) { + val style = if (currState == startState) startToEndUpdateStyle else StateTransitionStyle.Update + addState(stateBuilder.build(endState, style = style)) } } - // `while(await(x))` ... or `do { await(x); ... } while(...)` contain an `If` that loops; - // we must break that `If` into states so that it convert the label jump into a state machine - // transition - private def containsForeignLabelJump(t: Tree): Boolean = { - val labelDefs = t.collect { case ld: LabelDef => ld.symbol }.toSet - t.exists { - case rt: RefTree => rt.symbol != null && isLabel(rt.symbol) && !(labelDefs contains rt.symbol) - case _ => false - } - } + private def add(stat: Tree, isExpr: Boolean): Unit = { + def afterState() = if (isExpr) endState else stateAssigner.nextState() + stat match { + case vd @ ValDef(mods, name, tpt, UnwrapBoxedUnit(Apply(fun, arg :: Nil))) if isAwait(fun) => + // The val await$0 = await(someFuture) pattern. The ANF tranform makes sure this is + // always in statement position. + // + // Spawn a new state and transition to asynchronously it with `UpdateAndAwait` (ie an onComplete call) + val awaitable = Awaitable(arg.changeOwner(vd.symbol, vd.symbol.owner), stat.symbol, tpt.tpe, vd) + val afterAwaitState = stateAssigner.nextState() + buildStateAndOpenNextState(afterAwaitState, style = StateTransitionStyle.UpdateAndAwait(awaitable)) + + stateBuilder.stats ++= resumeTree(awaitable) + + case If(cond, thenp, elsep) if containsAwait(stat) => + // Emit a modified `If` in this state with each branch incorprating the + // first state from the nested block builder. + // + // The pragram point after the if will be the start of a new state if either of the branches + // contains an async boundary that falls through to that point. + checkForUnsupportedAwait(cond) + + val afterIfState = afterState() + var needAfterIfState = false + def mkBranch(tree: Tree): Tree = { + val (inlinedState, nestedStates) = buildNestedStatesFirstForInlining(tree, afterIfState) + val branchNeedsAfterIfState = incorporate(nestedStates, afterIfState) + needAfterIfState ||= branchNeedsAfterIfState + adaptToUnit(inlinedState.stats) + } + stateBuilder.stats += treeCopy.If(stat, cond, mkBranch(thenp), mkBranch(elsep)).clearType() + if (needAfterIfState) { + stateBuilder.nextStates += afterIfState + buildStateAndOpenNextState(afterIfState, style = StateTransitionStyle.Update) + } - // unwrap Block(t :: Nil, scala.runtime.BoxedUnit.UNIT) -- erasure will add the expr when await had type Unit - object UnwrapBoxedUnit { - def unapply(tree: Tree): Some[Tree] = tree match { - case Block(t :: Nil, unit) if isLiteralUnit(unit) => Some(t) // is really only going to be BoxedUnit, but hey - case t => Some(t) - } - } - // populate asyncStates - def add(stat: Tree, afterState: Option[Int] = None): Unit = stat match { - // the val name = await(..) pattern - case vd @ ValDef(mods, name, tpt, UnwrapBoxedUnit(Apply(fun, arg :: Nil))) if isAwait(fun) => - val onCompleteState = nextState() - val afterAwaitState = afterState.getOrElse(nextState()) - val awaitable = Awaitable(arg.changeOwner(vd.symbol, vd.symbol.owner), stat.symbol, tpt.tpe, vd) - asyncStates += stateBuilder.resultWithAwait(awaitable, onCompleteState, afterAwaitState) // complete with await - currState = afterAwaitState - stateBuilder = new AsyncStateBuilder(currState, symLookup) - - case If(cond, thenp, elsep) if containsAwait(stat) || containsForeignLabelJump(stat) => - checkForUnsupportedAwait(cond) - - val thenStartState = nextState() - val elseStartState = nextState() - val afterIfState = afterState.getOrElse(nextState()) - - // the two Int arguments are the start state of the then branch and the else branch, respectively - asyncStates += stateBuilder.resultWithIf(cond, thenStartState, elseStartState) - - List((thenp, thenStartState), (elsep, elseStartState)) foreach { - case (branchTree, state) => - val builder = nestedBlockBuilder(branchTree, state, afterIfState) - asyncStates ++= builder.asyncStates - } + case Match(scrutinee, cases) if containsAwait(stat) => + // This code path is now only used for patterns which a `@switch`-able scrutinee type. + // Translation is the same as `If`, just with more branches. - currState = afterIfState - stateBuilder = new AsyncStateBuilder(currState, symLookup) + checkForUnsupportedAwait(scrutinee) - case Match(scrutinee, cases) if containsAwait(stat) => - checkForUnsupportedAwait(scrutinee) + val afterMatchState = afterState() + var needAfterMatchState = false + def mkBranch(tree: Tree): Tree = { + val (inlinedState, nestedStates) = buildNestedStatesFirstForInlining(tree, afterMatchState) + val branchNeedsAfterMatchState = incorporate(nestedStates, afterMatchState) + needAfterMatchState ||= branchNeedsAfterMatchState + adaptToUnit(inlinedState.stats) + } - val caseStates = new Array[Int](cases.length) - java.util.Arrays.setAll(caseStates, new IntUnaryOperator { - override def applyAsInt(operand: Int): Int = nextState() - }) - val afterMatchState = afterState.getOrElse(nextState()) + val newCases = cases.map { + case cd @ CaseDef(pat, guard, rhs) => + checkForUnsupportedAwait(guard) + treeCopy.CaseDef(cd, pat, guard, mkBranch(rhs)) + } + stateBuilder.stats += treeCopy.Match(stat, scrutinee, newCases).clearType() - asyncStates += stateBuilder.resultWithMatch(scrutinee, cases, caseStates, symLookup) + if (needAfterMatchState) { + stateBuilder.nextStates += afterMatchState + buildStateAndOpenNextState(afterMatchState, StateTransitionStyle.Update) + } - for ((cas, num) <- cases.zipWithIndex) { - val (stats, expr) = statsAndExpr(cas.body) - val stats1 = stats.dropWhile(isSyntheticBindVal) - val builder = nestedBlockBuilder(Block(stats1, expr), caseStates(num), afterMatchState) - asyncStates ++= builder.asyncStates - } + case ld @ LabelDef(name, params, rhs) => + if (isCaseLabel(ld.symbol) || isMatchEndLabel(ld.symbol)) { + // LabelDefs from patterns are a bit trickier as they can (forward) branch to each other. + + labelDefStates.get(ld.symbol).foreach { startLabelState => + // While processing a prior `stat`, `JumpReplacer` detected that that this label was the target + // of a jump from some code that followed an async boundary. That's common for matchEnd but + // could also be true for a "case" label when the preceding pattern had an + // async guard or extractor. + // + // We rely on the fact that the patterm matcher only emits forward branches. + // This allows analysis and transformation to occur in one pass. + stateBuilder.nextStates += startLabelState + buildStateAndOpenNextState(startLabelState, StateTransitionStyle.Update) + } - currState = afterMatchState - stateBuilder = new AsyncStateBuilder(currState, symLookup) - case ld @ LabelDef(name, params, rhs) - if containsAwait(rhs) || directlyAdjacentLabelDefs(ld).exists(containsAwait) => - - val startLabelState = stateIdForLabel(ld.symbol) - val afterLabelState = afterState.getOrElse(nextState()) - asyncStates += stateBuilder.resultWithLabel(startLabelState, symLookup) - labelDefStates(ld.symbol) = startLabelState - val builder = nestedBlockBuilder(rhs, startLabelState, afterLabelState) - asyncStates ++= builder.asyncStates - currState = afterLabelState - stateBuilder = new AsyncStateBuilder(currState, symLookup) - case b @ Block(stats, expr) => - for (stat <- stats) add(stat) - add(expr, afterState = Some(endState)) - case _ => - checkForUnsupportedAwait(stat) - stateBuilder += stat - } - for (stat <- (stats :+ expr)) add(stat) - val lastState = stateBuilder.resultSimple(endState) - asyncStates += lastState - } + val afterLabelState = afterState() + val (inlinedState, nestedStates) = buildNestedStatesFirstForInlining(rhs, afterLabelState) - trait AsyncBlock { - def asyncStates: List[AsyncState] + // Leave this label here for synchronous jumps from previous cases. This is + // allowed even if this case has its own state (ie if there is an asynchrounous path + // from the start of the pattern to this case/matchEnd) + ld.symbol.setInfo(MethodType(Nil, definitions.UnitTpe)) + stateBuilder.stats += treeCopy.LabelDef(ld, ld.name, ld.params, adaptToUnit(inlinedState.stats)).clearType() - def onCompleteHandler[T]: Tree + val needsAfterLabelState = incorporate(nestedStates, afterLabelState) + if (needsAfterLabelState) { + buildStateAndOpenNextState(afterLabelState, style = StateTransitionStyle.None) + } + } else if (containsAwait(rhs)) { + // A while loop containg an await. We assuming that the the backward branch is reachable across the async + // code path and create a state for the `while` label. + // + // In theory we could avoid creating this state in code like: + // + // while (cond) { if (z) { await(f); return }; i += 1 } + // + val startLabelState = addLabelState(ld.symbol) + val afterLabelState = stateAssigner.nextState() + val nestedStates = buildNestedStates(rhs, startLabelState, afterLabelState) + nestedStates.foreach(addState) + buildStateAndOpenNextState(startLabelState, afterLabelState, StateTransitionStyle.UpdateAndContinue) + } else { + checkForUnsupportedAwait(stat) + stateBuilder += stat + } - def toDot: String - } + case _ => + checkForUnsupportedAwait(stat) + stateBuilder += stat + } + } + + private def buildNestedStatesFirstForInlining(nestedTree: Tree, endState: Int): (AsyncState, List[AsyncState]) = { + val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) + val nestedBuilder = new AsyncBlockBuilder(nestedStats, nestedExpr, currState, endState, StateTransitionStyle.None, Some(this)) + val ((inlinedState :: Nil), nestedStates) = nestedBuilder.build.partition(_.state == currState) + (inlinedState, nestedStates) + } + private def buildNestedStates(nestedTree: Tree, startState: Int, endState: Int): List[AsyncState] = { + val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) + val nestedBuilder = new AsyncBlockBuilder(nestedStats, nestedExpr, startState, endState, StateTransitionStyle.Update, Some(this)) + nestedBuilder.build + } + + private def buildStateAndOpenNextState(nextState: Int, style: StateTransitionStyle): Unit = + buildStateAndOpenNextState(nextState, nextState, style) + private def buildStateAndOpenNextState(toState: Int, nextState: Int, style: StateTransitionStyle): Unit = { + addState(stateBuilder.build(toState, style)) + 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") - case class SymLookup(stateMachineClass: Symbol, applyTrParam: Symbol) { - def stateMachineMember(name: TermName): Symbol = { - stateMachineClass.info.member(name) + /** Copy these states into the current block builder's async stats updating the open state builder's + * next states + * + * @return true if any of the nested states includes `afterState` as a next state. + */ + private def incorporate(nestedStates: List[AsyncState], afterState: Int): Boolean = { + def loop(states: List[AsyncState], needsAfterState: Boolean): Boolean = states match { + case Nil => needsAfterState + case state :: rest => + addState(state) + stateBuilder.nextStates += state.state + loop(rest, needsAfterState || state.nextStates.contains(afterState)) + } + loop(nestedStates, false) } - def memberRef(name: TermName): Tree = - gen.mkAttributedRef(stateMachineClass.typeConstructor, stateMachineMember(name)) - def memberRef(sym: Symbol): Tree = - gen.mkAttributedRef(stateMachineClass.typeConstructor, sym) + } - lazy val stateGetter: Symbol = stateMachineMember(nme.state) - lazy val stateSetter: Symbol = stateGetter.setterIn(stateGetter.owner) + trait AsyncBlock { + def asyncStates: List[AsyncState] + + def onCompleteHandler: Tree - def selectResult = applyNilAfterUncurry(memberRef(nme.result)) + def toDot: String } private lazy val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal") @@ -395,18 +329,19 @@ trait ExprBuilder extends TransformUtils { * Uses `AsyncBlockBuilder` to create an instance of `AsyncBlock`. * * @param block a `Block` tree in ANF - * @param symLookup helper for looking up members of the state machine class * @return an `AsyncBlock` */ - def buildAsyncBlock(block: Block, symLookup: SymLookup): AsyncBlock = { + def buildAsyncBlock(block: Block): AsyncBlock = { val Block(stats, expr) = block val startState = stateAssigner.nextState() - val endState = Int.MaxValue + val endState = StateAssigner.Terminal - val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, symLookup) + val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, startToEndUpdateStyle = StateTransitionStyle.Update) new AsyncBlock { val switchIds = mutable.AnyRefMap[Integer, Integer]() + val emptyReplacements = mutable.AnyRefMap[Integer, Integer]() + def switchIdOf(state: Integer) = switchIds(emptyReplacements.getOrElse(state, state)) // render with http://graphviz.it/#/new def toDot: String = { @@ -427,7 +362,7 @@ trait ExprBuilder extends TransformUtils { val dotBuilder = new StringBuilder() dotBuilder.append("digraph {\n") def stateLabel(s: Int) = { - if (s == 0) "INITIAL" else if (s == Int.MaxValue) "TERMINAL" else switchIds.getOrElse[Integer](s, s).toString + if (s == 0) "INITIAL" else if (s == StateAssigner.Terminal) "TERMINAL" else (if (compactStates) switchIdOf(s) else s).toString } val length = states.size for ((state, i) <- asyncStates.zipWithIndex) { @@ -438,129 +373,39 @@ trait ExprBuilder extends TransformUtils { case t => t :: Nil }).iterator.map(t => global.show(t)).mkString("\n") } - if (i != length - 1) { - val CaseDef(_, _, body) = state.mkHandlerCaseForState - toHtmlLabel(stateLabel(state.state), show(compactStateTransform.transform(body)), dotBuilder) - } else { - toHtmlLabel(stateLabel(state.state), state.allStats.map(show(_)).mkString("\n"), dotBuilder) - } + val CaseDef(_, _, body) = state.mkHandlerCaseForState + toHtmlLabel(stateLabel(state.state), show(compactStateTransform.transform(body)), dotBuilder) dotBuilder.append("> ]\n") - state match { - case s: AsyncStateWithAwait => - val CaseDef(_, _, body) = s.mkOnCompleteHandler.get - dotBuilder.append(s"""${stateLabel(s.onCompleteState)} [label=""").append("<") - toHtmlLabel(stateLabel(s.onCompleteState), show(compactStateTransform.transform(body)), dotBuilder) - dotBuilder.append("> ]\n") - case _ => - } } for (state <- states) { - state match { - case s: AsyncStateWithAwait => - dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(s.onCompleteState)} [style=dashed color=red]""") - dotBuilder.append("\n") - for (succ <- state.nextStates) { - dotBuilder.append(s"""${stateLabel(s.onCompleteState)} -> ${stateLabel(succ)}""") - dotBuilder.append("\n") - } - case _ => - for (succ <- state.nextStates) { - dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(succ)}""") - dotBuilder.append("\n") - } + for (succ <- state.nextStates) { + val style = "" + dotBuilder.append(s"""${stateLabel(state.state)} -> ${stateLabel(succ)} $style""") + dotBuilder.append("\n") } } dotBuilder.append("}\n") dotBuilder.toString } - lazy val asyncStates: List[AsyncState] = filterStates - - def filterStates = { - val all = blockBuilder.asyncStates.toList - val (initial :: rest) = all - val map = all.iterator.map(x => (x.state, x)).toMap - val seen = mutable.HashSet[Int]() - def loop(state: AsyncState): Unit = { - seen.add(state.state) - for (i <- state.nextStates) { - if (i != Int.MaxValue && !seen.contains(i)) { - loop(map(i)) - } - } - } - loop(initial) - val live = rest.filter(state => seen(state.state)) - var nextSwitchId = 0 - (initial :: live).foreach { state => - val switchId = nextSwitchId - switchIds(state.state) = switchId - nextSwitchId += 1 - state match { - case state: AsyncStateWithAwait => - val switchId = nextSwitchId - switchIds(state.onCompleteState) = switchId - nextSwitchId += 1 - case _ => - } - } - initial :: live - - } - - def mkCombinedHandlerCases[T]: List[CaseDef] = { - val futureSystem = currentTransformState.futureSystem - val futureSystemOps = futureSystem.mkOps(global) - - val caseForLastState: CaseDef = { - val lastState = asyncStates.last - val lastStateBody = lastState.body - val rhs = futureSystemOps.completeWithSuccess( - symLookup.selectResult, lastStateBody) - mkHandlerCase(lastState.state, Block(rhs, Return(literalUnit))) - } - asyncStates match { - case s :: Nil => - List(caseForLastState) - case _ => - val initCases = for (state <- asyncStates.init) yield state.mkHandlerCaseForState[T] - initCases :+ caseForLastState - } - } - - val initStates = asyncStates.init + lazy val asyncStates: List[AsyncState] = filterStates(blockBuilder.build) /** - * Builds the definition of the `resume` method. - * - * The resulting tree has the following shape: - * - * try { - * state match { - * case 0 => { - * f11 = exprReturningFuture - * f11.onComplete(onCompleteHandler)(context) - * } - * ... - * } - * } catch { - * case NonFatal(t) => result.failure(t) - * } + * Builds the definition of the `apply(tr: Try)` method. */ - private def resumeFunTree[T]: Tree = { - val futureSystem = currentTransformState.futureSystem - val futureSystemOps = futureSystem.mkOps(global) + def onCompleteHandler: Tree = { + val futureSystemOps = currentTransformState.ops - val stateMemberRef = gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)) + val symLookup = currentTransformState.symLookup + def stateMemberRef = gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)) val body = Match(stateMemberRef, - mkCombinedHandlerCases[T] ++ - initStates.flatMap(_.mkOnCompleteHandler[T]) ++ - List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List()))))) + asyncStates.map(_.mkHandlerCaseForState) ++ + List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List(gen.mkMethodCall(definitions.StringModule.info.member(nme.valueOf), stateMemberRef :: Nil))))))) val body1 = compactStates(body) - maybeTry(currentTransformState.futureSystem.emitTryCatch)( + val stateMatch = maybeTry(currentTransformState.futureSystem.emitTryCatch)( body1, List( CaseDef( @@ -568,78 +413,179 @@ trait ExprBuilder extends TransformUtils { EmptyTree, { val branchTrue = { val t = Ident(nme.t) - val complete = futureSystemOps.completeProm[T]( - symLookup.selectResult, futureSystemOps.tryyFailure[T](t)) + val complete = futureSystemOps.completeProm[AnyRef]( + symLookup.selectResult, futureSystemOps.tryyFailure[AnyRef](t)) Block(toList(complete), Return(literalUnit)) } If(Apply(Ident(NonFatalClass), List(Ident(nme.t))), branchTrue, Throw(Ident(nme.t))) branchTrue })), EmptyTree) + LabelDef(symLookup.whileLabel, Nil, Block(stateMatch :: Nil, Apply(Ident(symLookup.whileLabel), Nil))) } + private def compactStates = true + + // Filter out dead or trivial states. + private def filterStates(all: List[AsyncState]): List[AsyncState] = if (compactStates) { + val ((initial :: Nil), rest) = all.partition(_.state == blockBuilder.startState) + val map = all.iterator.map(x => (x.state, x)).toMap + val seen = mutable.HashSet[Int]() + def followEmptyState(state: AsyncState): AsyncState = if (state.isEmpty && state.nextStates.size == 1) { + val next = state.nextStates(0) + if (next == blockBuilder.endState) state + else followEmptyState(map(next)) + } else state + all.foreach {state => + val state1 = followEmptyState(state) + if (state1 ne state) + emptyReplacements(state.state) = state1.state + } + all.foreach { + state => state.nextStates = state.nextStates.map(s => emptyReplacements.getOrElse[Integer](s, s).toInt).distinct + } + def loop(state: AsyncState): Unit = { + if (!emptyReplacements.contains(state.state)) { + seen.add(state.state) + for (i <- state.nextStates if !seen.contains(i) && i != StateAssigner.Terminal) { + loop(map(i)) + } + } + } + loop(initial) + val live = initial :: rest.filter(state => seen(state.state)) + var nextSwitchId = 0 + live.foreach { state => + val switchId = nextSwitchId + switchIds(state.state) = switchId + nextSwitchId += 1 + } + live + } else all + private val compactStateTransform = new Transformer { + val symLookup = currentTransformState.symLookup override def transform(tree: Tree): Tree = tree match { - case as @ Apply(qual: Select, (lit @ Literal(Constant(i: Integer))) :: Nil) if qual.symbol == symLookup.stateSetter => - val replacement = switchIds(i) + case as @ Apply(qual: Select, (lit @ Literal(Constant(i: Integer))) :: Nil) if qual.symbol == symLookup.stateSetter && compactStates => + val replacement = switchIdOf(i) treeCopy.Apply(tree, qual, treeCopy.Literal(lit, Constant(replacement)):: Nil) - case _: Match | _: CaseDef | _: Block | _: If => + case _: Match | _: CaseDef | _: Block | _: If | _: LabelDef => super.transform(tree) case _ => tree } } - private def compactStates(m: Match): Tree = { + private def compactStates(m: Match): Tree = if (!compactStates) m else { val casesAndReplacementIds: List[(Integer, CaseDef)] = m.cases.map { case cd @ CaseDef(lit @ Literal(Constant(i: Integer)), EmptyTree, rhs) => - val replacement = switchIds(i) + val replacement = switchIdOf(i) val rhs1 = compactStateTransform.transform(rhs) (replacement, treeCopy.CaseDef(cd, treeCopy.Literal(lit, Constant(replacement)), EmptyTree, rhs1)) case cd => - (Int.box(Integer.MAX_VALUE), cd) + (Int.box(Int.MaxValue), cd) // sort the default case to the end. } val cases1: List[CaseDef] = casesAndReplacementIds.sortBy(_._1).map(_._2) treeCopy.Match(m, m.selector, cases1) } - - def forever(t: Tree): Tree = { - val labelName = TermName(name.fresh("while$")) - LabelDef(labelName, Nil, Block(toList(t), Apply(Ident(labelName), Nil))) - } - - /** - * Builds a `match` expression used as an onComplete handler, wrapped in a while(true) loop. - */ - def onCompleteHandler[T]: Tree = { - forever { - adaptToUnit(toList(resumeFunTree)) - } - } } } - private def isSyntheticBindVal(tree: Tree) = tree match { - case vd@ValDef(_, lname, _, Ident(rname)) => vd.symbol.attachments.contains[SyntheticBindVal.type] - case _ => false + private case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef) + + // Resume execution by extracting the successful value and assigining it to the `awaitable.resultValDef` + private def resumeTree(awaitable: Awaitable): List[Tree] = { + val futureSystemOps = currentTransformState.ops + def tryyReference = Ident(currentTransformState.symLookup.applyTrParam) + val assignTryGet = Assign(Ident(awaitable.resultName), futureSystemOps.tryyGet[Any](tryyReference)) + + val vd = deriveValDef(awaitable.resultValDef)(_ => gen.mkZero(awaitable.resultValDef.symbol.info)) + vd.symbol.setFlag(Flag.MUTABLE) + val assignOrReturn = if (currentTransformState.futureSystem.emitTryCatch) { + If(futureSystemOps.tryyIsFailure(tryyReference), + Block(toList(futureSystemOps.completeProm[AnyRef](currentTransformState.symLookup.selectResult, tryyReference)), + Return(literalBoxedUnit).setSymbol(currentTransformState.applyMethod)), + assignTryGet + ) + } else { + assignTryGet + } + vd :: assignOrReturn :: Nil } - case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef) - - private def mkStateTree(nextState: Int, symLookup: SymLookup): Tree = - Apply(symLookup.memberRef(symLookup.stateSetter), Literal(Constant(nextState)) :: Nil) + // 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. + private def awaitTree(awaitable: Awaitable): List[Tree] = { + val futureSystemOps = currentTransformState.ops + val fun = This(tpnme.EMPTY) + val symLookup = currentTransformState.symLookup + if (futureSystemOps.continueCompletedFutureOnSameThread) { + val tempAwaitableSym = symLookup.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) + val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) + val initTempCompleted = Assign(Ident(symLookup.applyTrParam), futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) + val null_ne = Select(Literal(Constant(null)), TermName("ne")) + val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) + val ifTree = + If(Apply(null_ne, Ident(symLookup.applyTrParam) :: Nil), + Apply(Ident(currentTransformState.symLookup.whileLabel), Nil), + Block(toList(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod))) + initAwaitableTemp :: initTempCompleted :: ifTree :: Nil + } else { + val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) + (toList(callOnComplete)) ::: Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod) :: Nil + } + } - private def mkHandlerCase(num: Int, rhs: List[Tree]): CaseDef = - mkHandlerCase(num, adaptToUnit(rhs)) + // Comlete the Promise in the `result` field with the final sucessful result of this async block. + private def completeSuccess(expr: Tree): Tree = { + val futureSystemOps = currentTransformState.ops + futureSystemOps.completeWithSuccess(currentTransformState.symLookup.selectResult, expr) + } - // We use the convention that the state machine's ID for a state corresponding to - // a labeldef will a negative number be based on the symbol ID. This allows us - // to translate a forward jump to the label as a state transition to a known state - // ID, even though the state machine transform hasn't yet processed the target label - // def. Negative numbers are used so as as not to clash with regular state IDs, which - // are allocated in ascending order from 0. - private def stateIdForLabel(sym: Symbol): Int = -sym.id + /** What trailing statements should be added to the code for this state to transition to the nest state? */ + private sealed abstract class StateTransitionStyle { + def trees(next: Int, stateSet: StateSet): List[Tree] + protected def mkStateTree(nextState: Int): Tree = { + val symLookup = currentTransformState.symLookup + val callSetter = Apply(symLookup.memberRef(symLookup.stateSetter), Literal(Constant(nextState)) :: Nil) + val printStateUpdates = false + if (printStateUpdates) { + Block( + callSetter :: Nil, + gen.mkMethodCall(definitions.PredefModule.info.member(TermName("println")), currentTransformState.localTyper.typed(gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)), definitions.ObjectTpe) :: Nil) + ) + } + else callSetter + } + } - private def mkHandlerCase(num: Int, rhs: Tree): CaseDef = - CaseDef(Literal(Constant(num)), EmptyTree, rhs) + private object StateTransitionStyle { + /** Do not update the state variable */ + case object None extends StateTransitionStyle { + def trees(nextState: Int, stateSet: StateSet): List[Tree] = Nil + } + /** Update the state variable, */ + case object Update extends StateTransitionStyle { + def trees(nextState: Int, stateSet: StateSet): List[Tree] = { + stateSet += nextState + List(mkStateTree(nextState)) + } + } + /** Update the state variable and the await completion of `awaitble.expr`. */ + case class UpdateAndAwait(awaitable: Awaitable) extends StateTransitionStyle { + def trees(nextState: Int, stateSet: StateSet): List[Tree] = { + stateSet += nextState + mkStateTree(nextState) :: awaitTree(awaitable) + } + } + /** Update the state variable and jump to the the while loop that encloses the state machine. */ + case object UpdateAndContinue extends StateTransitionStyle { + def trees(nextState: Int, stateSet: StateSet): List[Tree] = { + stateSet += nextState + List(mkStateTree(nextState), Apply(Ident(currentTransformState.symLookup.whileLabel), Nil)) + } + } + } // TODO AM: should this explode blocks even when expr is not ()? private def toList(tree: Tree): List[Tree] = tree match { @@ -647,4 +593,22 @@ trait ExprBuilder extends TransformUtils { case _ => tree :: Nil } + private def addLabelState(label: Symbol): Int = + labelDefStates.getOrElseUpdate(label, StateAssigner.stateIdForLabel(label)) + + // Replace jumps to qualifying labels as a state transition. + private class JumpReplacer(states: StateSet, state: Int, shouldReplace: Symbol => Boolean) extends ThicketTransformer(currentTransformState.unit) { + override def transform(tree: Tree): Tree = tree match { + case Apply(fun, args) if isLabel(fun.symbol) => + if (shouldReplace(fun.symbol)) { + val nextState = addLabelState(fun.symbol) + val trees = StateTransitionStyle.UpdateAndContinue.trees(nextState, states) + localTyper.typed(Thicket(listToBlock(trees))) + } else { + super.transform(tree) + } + case _ => + super.transform(tree) + } + } } diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index fffab47b318a..e05cbb6b2404 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -51,7 +51,7 @@ trait Lifter extends ExprBuilder { val childDefs = mutable.ArrayBuffer[Tree]() private val classesBuffer, moduleClassesBuffer = mutable.ArrayBuffer[Symbol]() override def traverse(tree: Tree): Unit = tree match { - case _: LabelDef => + case _: LabelDef => super.traverse(tree) case _: DefTree => childDefs += tree case _: Function => Nil case Block(stats, expr) => @@ -78,7 +78,7 @@ trait Lifter extends ExprBuilder { for (asyncState <- asyncStates) { traverser.childDefs.clear() - traverser.traverse(Block(asyncState.allStats, EmptyTree)) + traverser.traverse(Block(asyncState.stats, EmptyTree)) for (defTree <-traverser.childDefs) { result(defTree) = asyncState.state } diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 0451b499f1fe..ba7b62908b39 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -31,10 +31,10 @@ trait LiveVariables extends ExprBuilder { * @return a map mapping a state to the fields that should be nulled out * upon resuming that state */ - def fieldsToNullOut(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Int, List[Tree]] = { + def fieldsToNullOut(asyncStates: List[AsyncState], finalState: AsyncState, liftables: List[Tree]): mutable.LinkedHashMap[Int, List[Tree]] = { // live variables analysis: // the result map indicates in which states a given field should be nulled out - val liveVarsMap: mutable.LinkedHashMap[Tree, StateSet] = liveVars(asyncStates, liftables) + val liveVarsMap: mutable.LinkedHashMap[Tree, StateSet] = liveVars(asyncStates, finalState, liftables) val assignsOf = mutable.LinkedHashMap[Int, List[Tree]]() @@ -66,7 +66,7 @@ trait LiveVariables extends ExprBuilder { * @param liftables the lifted fields * @return a map which indicates for a given field (the key) the states in which it should be nulled out */ - def liveVars(asyncStates: List[AsyncState], liftables: List[Tree]): mutable.LinkedHashMap[Tree, StateSet] = { + def liveVars(asyncStates: List[AsyncState], finalState: AsyncState, liftables: List[Tree]): mutable.LinkedHashMap[Tree, StateSet] = { val liftedSyms: Set[Symbol] = // include only vars liftables.iterator.filter { case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE) @@ -76,7 +76,7 @@ trait LiveVariables extends ExprBuilder { // determine which fields should be live also at the end (will not be nulled out) val noNull: Set[Symbol] = liftedSyms.filter { sym => val tpSym = sym.info.typeSymbol - (tpSym.isClass && (tpSym.asClass.isPrimitive || isNothingClass(tpSym))) || liftables.exists { tree => + (tpSym.isClass && (tpSym.asClass.isPrimitive || tpSym == definitions.NothingClass)) || liftables.exists { tree => !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym) } } @@ -131,10 +131,6 @@ trait LiveVariables extends ExprBuilder { val findUses = new FindUseTraverser findUses.traverse(Block(as.stats: _*)) - as match { - case aswa: AsyncStateWithAwait => findUses.traverse(aswa.awaitable.expr) - case _ => - } ReferencedFields(findUses.usedFields, findUses.capturedFields) } case class ReferencedFields(used: Set[Symbol], captured: Set[Symbol]) { @@ -177,9 +173,6 @@ trait LiveVariables extends ExprBuilder { isPred0(state1, state2) } - val finalStates = asyncStates.filter(as => !asyncStates.exists(other => isPred(as.state, other.state))) - val finalState = finalStates.head - if(settings.debug.value && shouldLogAtThisPhase) { for (as <- asyncStates) debuglog(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") diff --git a/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala b/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala index 84759576a0cf..f5971c5a3671 100644 --- a/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala +++ b/src/compiler/scala/tools/nsc/transform/async/StateAssigner.scala @@ -12,6 +12,8 @@ package scala.tools.nsc.transform.async +import scala.tools.nsc.symtab.SymbolTable + private[async] final class StateAssigner { private var current = StateAssigner.Initial @@ -20,4 +22,13 @@ private[async] final class StateAssigner { object StateAssigner { final val Initial = 0 + final val Terminal = -1 + + // We use the convention that the state machine's ID for a state corresponding to + // a labeldef will a negative number be based on the symbol ID. This allows us + // to translate a forward jump to the label as a state transition to a known state + // ID, even though the state machine transform hasn't yet processed the target label + // def. Negative numbers are used so as as not to clash with regular state IDs, which + // are allocated in ascending order from 0. + def stateIdForLabel(sym: SymbolTable#Symbol): Int = -sym.id } diff --git a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala index 487787a8128b..ea1bbc903628 100644 --- a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala +++ b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala @@ -25,9 +25,16 @@ final class StateSet { def +=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.set(stateId) else caseSet.add(stateId) def -=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.clear(stateId) else caseSet.remove(stateId) def contains(stateId: Int): Boolean = if (useBitSet(stateId)) bitSet.get(stateId) else caseSet.contains(stateId) + def isEmpty = bitSet.isEmpty && caseSet.isEmpty def iterator: Iterator[Integer] = { bitSet.stream().iterator().asScala ++ caseSet.asScala.iterator } + def toArray: Array[Int] = { + val result = new Array[Int](bitSet.cardinality() + caseSet.size()) + var i = 0 + foreach(value => {result(i) = value; i += 1 }) + result + } def foreach(f: IntConsumer): Unit = { bitSet.stream().forEach(f) caseSet.stream().forEach(new Consumer[Integer] { diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 63ac053d6736..39107fcb4280 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -24,44 +24,19 @@ import scala.tools.nsc.transform.TypingTransformers trait PhasedTransform extends TypingTransformers { import global._ - def isPastErasure: Boolean = { - val erasurePhase = global.currentRun.erasurePhase - erasurePhase != NoPhase && global.isPast(erasurePhase) - } - - // We're not that granular, but keeping separate flag for semantics - private def isPastUncurry = isPastErasure - private def emptyParamss: List[List[ValDef]] = if (isPastUncurry) ListOfNil else Nil - protected def applyNilAfterUncurry(t: Tree) = if (isPastUncurry) Apply(t, Nil) else t - - def literalNull = Literal(Constant(null)) - - def typeEqualsUnit(tp: Type) = tp =:= definitions.UnitTpe || (isPastErasure && tp =:= definitions.BoxedUnitTpe) + def assignUnitType(t: Tree): t.type = t.setType(definitions.UnitTpe) - def assignUnitType(t: Tree): t.type = - t.setType(definitions.UnitTpe) + def isUnitType(tp: Type): Boolean = tp.typeSymbol == definitions.UnitClass || tp =:= definitions.BoxedUnitTpe - def setUnitMethodInfo(sym: Symbol): sym.type = sym.setInfo(MethodType(Nil, if (isPastErasure) definitions.BoxedUnitTpe else definitions.UnitTpe)) + def literalUnit: Tree = Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees + def literalBoxedUnit: Tree = gen.mkAttributedRef(definitions.BoxedUnit_UNIT) - def isUnitType(tp: Type) = tp.typeSymbol == definitions.UnitClass || (isPastErasure && tp =:= definitions.BoxedUnitTpe) - def isNothingClass(sym: Symbol) = sym == definitions.NothingClass - - def literalUnit = - Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees - def literalBoxedUnit = - gen.mkAttributedRef(definitions.BoxedUnit_UNIT) - - def isLiteralUnit(t: Tree) = t match { + def isLiteralUnit(t: Tree): Boolean = t match { case Literal(Constant(())) => true case t if t.symbol == definitions.BoxedUnit_UNIT => true // important to find match labels (which are potential states) case _ => false } - - def transformType(tp: Type) = if (isPastErasure) transformedType(tp) else tp - - def mkAsInstanceOf(qual: Tree, tp: Type) = gen.mkCast(qual, tp) - private def tpeOf(t: Tree): Type = t match { case _ if t.tpe != null => t.tpe case Try(body, Nil, _) => tpeOf(body) @@ -71,35 +46,30 @@ trait PhasedTransform extends TypingTransformers { case _ => NoType } - def adaptToUnit(rhs: List[Tree]): Block = + def adaptToUnit(rhs: List[Tree]): Block = { + def filterUnit(ts: List[Tree]): List[Tree] = { + val result = ts.filterNot(isLiteralUnit(_)) + // if (result != ts) + // getClass + result + } rhs match { case (rhs: Block) :: Nil if { val tp = tpeOf(rhs); tp <:< definitions.UnitTpe || tp <:< definitions.BoxedUnitTpe } => rhs case init :+ last if { val tp = tpeOf(last); tp <:< definitions.UnitTpe || tp <:< definitions.BoxedUnitTpe } => - Block(init, last) + Block(filterUnit(init), last) case init :+ (last@Literal(Constant(()))) => - Block(init, last) + Block(filterUnit(init), last) case init :+ (last@Block(_, Return(_) | Literal(Constant(())))) => - Block(init, last) + Block(filterUnit(init), last) case init :+ (last@Block(_, expr)) if expr.symbol == definitions.BoxedUnit_UNIT => - Block(init, last) + Block(filterUnit(init), last) case init :+ Block(stats, expr) => - Block(init, Block(stats :+ expr, literalUnit)) + Block(filterUnit(init), Block(filterUnit(stats :+ expr), literalUnit)) case _ => - Block(rhs, literalUnit) - } - - // TODO AM: why add the :Any type ascription to hide a tree of type Nothing? adaptToUnit doesn't seem to care - def adaptToUnitIgnoringNothing(stats: List[Tree]): Block = - stats match { - case init :+ last if tpeOf(last) =:= definitions.NothingTpe => - adaptToUnit(init :+ Typed(last, TypeTree(definitions.AnyTpe))) - case _ => - adaptToUnit(stats) + Block(filterUnit(rhs), literalUnit) } - - private def derivedValueClassUnbox(cls: Symbol) = - (cls.info.decls.find(sym => sym.isMethod && sym.asTerm.isParamAccessor) getOrElse NoSymbol) + } } @@ -116,7 +86,7 @@ private[async] trait TransformUtils extends PhasedTransform { def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName } - def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree) = + def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree): Tree = if (emitTryCatch) Try(block, catches, finalizer) else block lazy val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException") @@ -127,19 +97,18 @@ private[async] trait TransformUtils extends PhasedTransform { def isBooleanShortCircuit(sym: Symbol): Boolean = sym.owner == definitions.BooleanClass && (sym == definitions.Boolean_and || sym == definitions.Boolean_or) - def isLabel(sym: Symbol): Boolean = sym.isLabel + def isLabel(sym: Symbol): Boolean = sym != null && sym.isLabel + def isCaseLabel(sym: Symbol): Boolean = sym != null && sym.isLabel && sym.name.startsWith("case") + def isMatchEndLabel(sym: Symbol): Boolean = sym != null && sym.isLabel && sym.name.startsWith("matchEnd") def substituteTrees(t: Tree, from: List[Symbol], to: List[Tree]): Tree = (new TreeSubstituter(from, to)).transform(t) def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { case Block(stats, expr) => (stats, expr) - case _ => (List(tree), Literal(Constant(()))) - } - - def blockToList(tree: Tree): List[Tree] = tree match { - case Block(stats, expr) => stats :+ expr - case t => t :: Nil + case _ => + if (tree.tpe <:< definitions.UnitTpe) (Nil, tree) + else (List(tree), literalUnit) } def listToBlock(trees: List[Tree]): Block = trees match { @@ -150,9 +119,44 @@ private[async] trait TransformUtils extends PhasedTransform { throw new MatchError(trees) } - def emptyConstructor: DefDef = { - val emptySuperCall = Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), Nil) - DefDef(NoMods, nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(emptySuperCall), Literal(Constant(())))) + class ThicketTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + private object Thicket + private def expandThicket(t: Tree): List[Tree] = t match { + case Block(stats, expr) if t.attachments.contains[Thicket.type] => + stats :+ expr + case _ => t :: Nil + } + + def apply(tree: Tree): List[Tree] = expandThicket(transform(tree)) + + protected def Thicket(stats: List[Tree], expr: Tree): Tree = { + Block(stats, expr).updateAttachment(Thicket) + } + protected def Thicket(block: Block): Tree = { + block.updateAttachment(Thicket) + } + + override def transform(tree: Tree): Tree = tree match { + case Block(stats, expr) => + val stats1 = mutable.ListBuffer[Tree]() + transformTrees(stats).foreach { + case blk @ Block(stats, expr) if blk.hasAttachment[Thicket.type] => + stats1 ++= stats + stats1 += expr + case t => + stats1 += t + } + val expr1 = transform(expr) match { + case blk @ Block(stats, expr) if blk.hasAttachment[Thicket.type] => + stats1 ++= stats + expr + case expr => + expr + } + treeCopy.Block(tree, stats1.toList, expr1) + case _ => + super.transform(tree) + } } /** Descends into the regions of the tree that are subject to the @@ -205,11 +209,6 @@ private[async] trait TransformUtils extends PhasedTransform { result.map { case (a, b) => (a, b.toList) } } - def thisType(sym: Symbol): Type = { - if (sym.isClass) sym.asClass.thisPrefix - else NoPrefix - } - /** * Efficiently decorate each subtree within `t` with the result of `t exists isAwait`, * and return a function that can be used on derived trees to efficiently test the @@ -280,7 +279,7 @@ private[async] trait TransformUtils extends PhasedTransform { object MatchEnd { def unapply(t: Tree): Option[LabelDef] = t match { case ValDef(_, _, _, t) => unapply(t) - case ld: LabelDef if ld.name.toString.startsWith("matchEnd") => Some(ld) + case ld: LabelDef if isMatchEndLabel(ld.symbol) => Some(ld) case _ => None } } @@ -289,6 +288,14 @@ private[async] trait TransformUtils extends PhasedTransform { case Block(stats, expr) => stats.foreach(f); f(expr) case _ => f(tree) } + + // unwrap Block(t :: Nil, scala.runtime.BoxedUnit.UNIT) -- erasure will add the expr when await had type Unit + object UnwrapBoxedUnit { + def unapply(tree: Tree): Some[Tree] = tree match { + case Block(t :: Nil, unit) if isLiteralUnit(unit) => Some(t) // is really only going to be BoxedUnit, but hey + case t => Some(t) + } + } } private case object ContainsAwait diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 498526ab793b..dbbad4bbb10a 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -35,6 +35,71 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def testMatchBig(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.async.Async.{async, await} + | + | + |object Test { + | def test: Future[Int] = async { + | val x: Option[Either[Object, (String, String)]] = Some(Right(("a", "b"))) + | x match { + | case Some(Left(_)) => 1 + | case Some(Right(("a", "c"))) => 2 + | case Some(Right(("a", "e"))) => 3 + | case Some(Right(("a", x))) if "ab".isEmpty => 4 + | case Some(Right(("a", "b"))) => await(f(5)) + | case Some(Right((y, x))) if x == y => 6 + | case Some(Right((_, _))) => await(f(7)) + | case None => 8 + | } + | } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals(5, run(code)) + } + @Test + def testMatchSmall(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.async.Async.{async, await} + | + | + |object Test { + | def test: Future[Int] = async { + | val x: Option[Either[Object, (String, String)]] = Some(Right(("a", "b"))) + | (x: @unchecked) match { + | case Some(Right(("a", "b"))) => await(f(5)) + | case None => 8 + | } + | } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals(5, run(code)) + } + + + @Test + def testBasicScalaConcurrentCapture(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.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))) } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals(("init_updated", 1, 2), run(code)) + } + @Test def testLiftedLazyVal(): Unit = { val code = @@ -50,6 +115,58 @@ class AnnotationDrivenAsync { assertEquals((1, 1, -1), run(code)) } + @Test + def testWhile1(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.async.Async.{async, await} + | + |object Test { + | def p[T](t: T): T = {println(t); t } + | def test: Future[Int] = async { + | var sum = 0 + | var i = 0 + | while (i < 5) { + | var j = 0 + | while (j < 5) { + | sum += await(f(i)) * await(f(j)) + | j += 1 + | } + | i += 1 + | } + | sum + | } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals(100, run(code)) + } + + @Test + def testWhile2(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.async.Async.{async, await} + | + |object Test { + | def p[T](t: T): T = {println(t); t } + | def test: Future[Int] = async { + | var sum = 0 + | var i = 0 + | while (i < 5) { + | sum += await(f(i)) + | i += 1 + | } + | sum + | } + | def f(x: Int): Future[Int] = Future.successful(x) + |} + |""".stripMargin + assertEquals(10, run(code)) + } + @Test def testCaseClassLifting(): Unit = { // Note: this emits a warning under -Ydebug (which we sometimes manually set below in the compiler setup) @@ -243,6 +360,8 @@ class AnnotationDrivenAsync { assertEquals(true, run(code)) } + + // Handy to debug the compiler @Test @Ignore def testManualRunPartestUnderJUnit(): Unit = { @@ -451,7 +570,7 @@ object CustomFutureFutureSystem extends FutureSystem { } def completeProm[A](prom: Expr[Prom[A]], value: Expr[scala.util.Try[A]]): Expr[Unit] = { - Block(gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) :: Nil, literalUnitExpr) + gen.mkMethodCall(prom, Promise_complete, Nil, value :: Nil) } def tryyIsFailure[A](tryy: Expr[scala.util.Try[A]]): Expr[Boolean] = { From 7fdeed382c34f9a441a950faa4609b7dd7768c1d Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 11 Mar 2020 10:25:23 +1000 Subject: [PATCH 30/94] Use faster attachment lookups in ThicketTransformer --- .../nsc/transform/async/TransformUtils.scala | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 39107fcb4280..985d06990fc1 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -14,9 +14,7 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.tools.nsc.NoPhase import scala.language.existentials -import scala.reflect.internal.util.ListOfNil import scala.tools.nsc.transform.TypingTransformers // Logic sensitive to where we are in the pipeline @@ -119,10 +117,10 @@ private[async] trait TransformUtils extends PhasedTransform { throw new MatchError(trees) } + private object ThicketAttachment class ThicketTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - private object Thicket private def expandThicket(t: Tree): List[Tree] = t match { - case Block(stats, expr) if t.attachments.contains[Thicket.type] => + case Block(stats, expr) if t.attachments.containsElement(ThicketAttachment) => stats :+ expr case _ => t :: Nil } @@ -130,24 +128,24 @@ private[async] trait TransformUtils extends PhasedTransform { def apply(tree: Tree): List[Tree] = expandThicket(transform(tree)) protected def Thicket(stats: List[Tree], expr: Tree): Tree = { - Block(stats, expr).updateAttachment(Thicket) + Block(stats, expr).updateAttachment(ThicketAttachment) } protected def Thicket(block: Block): Tree = { - block.updateAttachment(Thicket) + block.updateAttachment(ThicketAttachment) } override def transform(tree: Tree): Tree = tree match { case Block(stats, expr) => val stats1 = mutable.ListBuffer[Tree]() transformTrees(stats).foreach { - case blk @ Block(stats, expr) if blk.hasAttachment[Thicket.type] => + case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => stats1 ++= stats stats1 += expr case t => stats1 += t } val expr1 = transform(expr) match { - case blk @ Block(stats, expr) if blk.hasAttachment[Thicket.type] => + case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => stats1 ++= stats expr case expr => @@ -223,9 +221,9 @@ private[async] trait TransformUtils extends PhasedTransform { object traverser extends Traverser { var containsAwait = false override def traverse(tree: Tree): Unit = - if (tree.hasAttachment[NoAwait.type]) {} // safe to skip + if (tree.attachments.containsElement(NoAwait)) {} // safe to skip else if (!containsAwait) { - if (tree.hasAttachment[ContainsAwait.type]) containsAwait = true + if (tree.attachments.containsElement(ContainsAwait)) containsAwait = true else if (markContainsAwaitTraverser.shouldAttach(t)) super.traverse(tree) } } From 1db7f9da37935c5b0b230f4f990eb9daec18aa44 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2020 11:29:02 +1000 Subject: [PATCH 31/94] Reinstate live variables test and fix null assignment While looking more closely at the implementation, I realised our approach of nulling dead variables at the start of the subsequent state was wrong, we retained the references for longer than needed. I've moved the nulling into the conclusion of the state of last usages. Remove the warnings test which isn't relevant with post-typer ANF. --- .../nsc/transform/async/AsyncTransform.scala | 30 +- .../nsc/transform/async/ExprBuilder.scala | 53 ++- .../nsc/transform/async/LiveVariables.scala | 47 +- .../nsc/transform/async/TransformUtils.scala | 1 + .../scala/tools/partest/JUnitTest.scala | 22 +- test/async/run/live.scala | 418 +++++++----------- test/async/run/warning.scala | 105 ----- 7 files changed, 218 insertions(+), 458 deletions(-) delete mode 100644 test/async/run/warning.scala diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 6a30ee13ab53..8e2f7ce43337 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -12,6 +12,7 @@ package scala.tools.nsc.transform.async +import scala.collection.mutable import user.{FutureSystem, ScalaConcurrentFutureSystem} import scala.reflect.internal.Flags import scala.tools.nsc.Global @@ -234,13 +235,30 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields) for ((state, flds) <- assignsOf) { - val assigns = flds.map { fld => - val fieldSym = fld.symbol - Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos)) - - } val asyncState = asyncBlock.asyncStates.find(_.state == state).get - asyncState.stats = assigns ++ asyncState.stats + val stats1 = mutable.ListBuffer[Tree]() + def addNullAssigments(): Unit = { + // Insert the null assignments immediately after the state transition + for (fld <- flds) { + val fieldSym = fld.symbol + stats1 += Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos)) + } + } + var foundStateTransition = false + asyncState.stats.foreach { + stat => + stats1 += stat + if (stat.attachments.containsElement(StateTransitionTree)) { + assert(!foundStateTransition) + foundStateTransition = true + // Insert the null assignments immediately after the state transition + addNullAssigments() + } + } + if (!foundStateTransition) { + addNullAssigments() + } + asyncState.stats = stats1.toList } } diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index a3b0a24ecf7b..291cbb023e61 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -511,31 +511,6 @@ trait ExprBuilder extends TransformUtils { vd :: assignOrReturn :: Nil } - // 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. - private def awaitTree(awaitable: Awaitable): List[Tree] = { - val futureSystemOps = currentTransformState.ops - val fun = This(tpnme.EMPTY) - val symLookup = currentTransformState.symLookup - if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempAwaitableSym = symLookup.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) - val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) - val initTempCompleted = Assign(Ident(symLookup.applyTrParam), futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) - val null_ne = Select(Literal(Constant(null)), TermName("ne")) - val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) - val ifTree = - If(Apply(null_ne, Ident(symLookup.applyTrParam) :: Nil), - Apply(Ident(currentTransformState.symLookup.whileLabel), Nil), - Block(toList(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod))) - initAwaitableTemp :: initTempCompleted :: ifTree :: Nil - } else { - val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) - (toList(callOnComplete)) ::: Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod) :: Nil - } - } - // Comlete the Promise in the `result` field with the final sucessful result of this async block. private def completeSuccess(expr: Tree): Tree = { val futureSystemOps = currentTransformState.ops @@ -549,13 +524,13 @@ trait ExprBuilder extends TransformUtils { val symLookup = currentTransformState.symLookup val callSetter = Apply(symLookup.memberRef(symLookup.stateSetter), Literal(Constant(nextState)) :: Nil) val printStateUpdates = false - if (printStateUpdates) { + (if (printStateUpdates) { Block( callSetter :: Nil, gen.mkMethodCall(definitions.PredefModule.info.member(TermName("println")), currentTransformState.localTyper.typed(gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)), definitions.ObjectTpe) :: Nil) ) } - else callSetter + else callSetter).updateAttachment(StateTransitionTree) } } @@ -575,7 +550,29 @@ trait ExprBuilder extends TransformUtils { case class UpdateAndAwait(awaitable: Awaitable) extends StateTransitionStyle { def trees(nextState: Int, stateSet: StateSet): List[Tree] = { stateSet += nextState - mkStateTree(nextState) :: awaitTree(awaitable) + + // 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 symLookup = currentTransformState.symLookup + if (futureSystemOps.continueCompletedFutureOnSameThread) { + val tempAwaitableSym = symLookup.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) + val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) + val initTempCompleted = Assign(Ident(symLookup.applyTrParam), futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) + val null_ne = Select(Literal(Constant(null)), TermName("ne")) + val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) + val ifTree = + If(Apply(null_ne, Ident(symLookup.applyTrParam) :: Nil), + Apply(Ident(currentTransformState.symLookup.whileLabel), Nil), + Block(toList(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod))) + initAwaitableTemp :: initTempCompleted :: mkStateTree(nextState) :: ifTree :: Nil + } else { + val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) + mkStateTree(nextState) :: toList(callOnComplete) ::: Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod) :: Nil + } } } /** Update the state variable and jump to the the while loop that encloses the state machine. */ diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index ba7b62908b39..e56f46b8589e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -149,30 +149,6 @@ trait LiveVariables extends ExprBuilder { res } - /** Tests if `state1` is a predecessor of `state2`. - */ - def isPred(state1: Int, state2: Int): Boolean = { - val seen = new StateSet() - - def isPred0(state1: Int, state2: Int): Boolean = - if(state1 == state2) false - else if (seen.contains(state1)) false // breaks cycles in the CFG - else cfg get state1 match { - case Some(nextStates) => - seen += state1 - var i = 0 - while (i < nextStates.length) { - if (nextStates(i) == state2 || isPred0(nextStates(i), state2)) return true - i += 1 - } - false - case None => - false - } - - isPred0(state1, state2) - } - if(settings.debug.value && shouldLogAtThisPhase) { for (as <- asyncStates) debuglog(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") @@ -280,30 +256,11 @@ trait LiveVariables extends ExprBuilder { debuglog(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}") } - val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] = - for ((fld, lastStates) <- lastUsages) yield { - val result = new StateSet - lastStates.foreach(new IntConsumer { def accept(s: Int): Unit = { - if (s != finalState.state) { - val lastAsyncState = asyncStates.find(_.state == s).get - val succNums = lastAsyncState.nextStates - // all successor states that are not indirect predecessors - // filter out successor states where the field is live at the entry - var i = 0 - while (i < succNums.length) { - val num = succNums(i) - if (num != finalState.state && !isPred(num, s) && !LVentry(num).contains(fld.symbol)) - result += num - i += 1 - } - } - }}) - (fld, result) - } + val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] = lastUsages if(settings.debug.value && shouldLogAtThisPhase) { for ((fld, killAt) <- nullOutAt) - debuglog(s"field ${fld.symbol.name} should be nulled out in states ${killAt.iterator.mkString(", ")}") + debuglog(s"field ${fld.symbol.name} should be nulled out at the conclusion of states ${killAt.iterator.mkString(", ")}") } nullOutAt diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 985d06990fc1..03cdafc2171d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -298,3 +298,4 @@ private[async] trait TransformUtils extends PhasedTransform { private case object ContainsAwait private case object NoAwait +case object StateTransitionTree diff --git a/src/partest-extras/scala/tools/partest/JUnitTest.scala b/src/partest-extras/scala/tools/partest/JUnitTest.scala index dba97a023e18..f37708aac808 100644 --- a/src/partest-extras/scala/tools/partest/JUnitTest.scala +++ b/src/partest-extras/scala/tools/partest/JUnitTest.scala @@ -12,13 +12,10 @@ package scala.tools.partest -import java.io.{PrintWriter, StringWriter} - -import org.junit.internal.TextListener import org.junit.runner.JUnitCore import org.junit.runner.notification.{Failure, RunListener} -import scala.collection.JavaConverters._ +import scala.tools.nsc.util.Exceptional abstract class JUnitTest(classes: Class[_]*) extends App { @@ -28,12 +25,17 @@ abstract class JUnitTest(classes: Class[_]*) extends App { println(failure) val ex = failure.getException if(ex != null) { - val sw = new StringWriter() - val out = new PrintWriter(sw) - ex.printStackTrace(out) - out.flush() - val lines = sw.getBuffer.toString.split('\n') - lines.iterator.takeWhile(s => !s.contains("at org.junit.runners.")).foreach(println) + val seen = new java.util.IdentityHashMap[Throwable, Object] + @scala.annotation.tailrec + def trimStack(ex: Throwable): Unit = if (ex != null && !seen.containsKey(ex)) { + seen.put(ex, null) + ex.setStackTrace(ex.getStackTrace.takeWhile(!_.getClassName.startsWith("org.junit.runners."))) + trimStack(ex.getCause) + } + + val unwrapped = Exceptional.unwrap(ex) + trimStack(unwrapped) + unwrapped.printStackTrace() } } }) diff --git a/test/async/run/live.scala b/test/async/run/live.scala index f4268a731720..7d3da2e35267 100644 --- a/test/async/run/live.scala +++ b/test/async/run/live.scala @@ -1,299 +1,189 @@ -/* - * 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 -package run -package live - -import org.junit.Test - -import internal.AsyncTestLV -import AsyncTestLV._ - -case class Cell[T](v: T) - -class Meter(val len: Long) extends AnyVal - -case class MCell[T](var v: T) - - -class LiveVariablesSpec { - AsyncTestLV.clear() - - @Test - def `zero out fields of reference type`(): Unit = { - val f = async { Cell(1) } - - def m1(x: Cell[Int]): Cell[Int] = - async { Cell(x.v + 1) } - - def m2(x: Cell[Int]): String = - async { x.v.toString } - - def m3() = async { - val a: Cell[Int] = await(f) // await$1$1 - // a == Cell(1) - val b: Cell[Int] = await(m1(a)) // await$2$1 - // b == Cell(2) - assert(AsyncTestLV.log.exists(_._2 == Cell(1)), AsyncTestLV.log) - val res = await(m2(b)) // await$3$1 - assert(AsyncTestLV.log.exists(_._2 == Cell(2))) - res - } - - assert(m3() == "2") +object Test extends scala.tools.partest.JUnitTest(classOf[scala.async.run.live.LiveVariablesSpec]) + +package scala.async.run.live { + import org.junit.Test + import org.junit.Assert._ + + import scala.concurrent._ + import duration.Duration + import scala.async.Async.{async, await} + import scala.collection.immutable + object TestUtil { + import language.implicitConversions + implicit def lift[T](t: T): Future[T] = Future.successful(t) + def block[T](f: Future[T]): T = Await.result(f, Duration.Inf) } + import TestUtil._ + + case class Cell[T](v: T) + + class Meter(val len: Long) extends AnyVal + + case class MCell[T](var v: T) + + class LiveVariablesSpec { + implicit object testingEc extends ExecutionContext { + var lastStateMachine: Any = _ + var lastFailure: Throwable = _ + def live[T: reflect.ClassTag]: List[T] = { + val instance = lastStateMachine + val flds = instance.getClass.getDeclaredFields + val filterClass = reflect.classTag[T].runtimeClass + flds.toList.flatMap { fld => + fld.setAccessible(true) + val value = fld.get(instance) + if (filterClass.isInstance(value)) { + value.asInstanceOf[T] :: Nil + } else Nil + } + } + override def execute(runnable: Runnable): Unit = try { + lastStateMachine = reflectivelyExtractStateMachine(runnable) + runnable.run() + } catch { + case t: Throwable => throw new RuntimeException(t) + } + override def reportFailure(cause: Throwable): Unit = { + lastFailure = cause + } - @Test - def `zero out fields of type Any`(): Unit = { - val f = async { Cell(1) } - - def m1(x: Cell[Int]): Cell[Int] = - async { Cell(x.v + 1) } - - def m2(x: Any): String = - async { x.toString } - - def m3() = async { - val a: Cell[Int] = await(f) // await$4$1 - // a == Cell(1) - val b: Any = await(m1(a)) // await$5$1 - // b == Cell(2) - assert(AsyncTestLV.log.exists(_._2 == Cell(1))) - val res = await(m2(b)) // await$6$1 - assert(AsyncTestLV.log.exists(_._2 == Cell(2))) - res - } - - assert(m3() == "Cell(2)") - } - - @Test - def `do not zero out fields of primitive type`(): Unit = { - val f = async { 1 } - - def m1(x: Int): Cell[Int] = - async { Cell(x + 1) } - - def m2(x: Any): String = - async { x.toString } - - def m3() = async { - val a: Int = await(f) // await$7$1 - // a == 1 - val b: Any = await(m1(a)) // await$8$1 - // b == Cell(2) - // assert(!AsyncTestLV.log.exists(p => p._1 == "await$7$1")) - val res = await(m2(b)) // await$9$1 - assert(AsyncTestLV.log.exists(_._2 == Cell(2))) - res + private def reflectivelyExtractStateMachine(runnable: Runnable) = { + assert(runnable.getClass == Class.forName("scala.concurrent.impl.CallbackRunnable"), runnable.getClass) + val fld = runnable.getClass.getDeclaredField("onComplete") + fld.setAccessible(true) + val stateMachine = fld.get(runnable) + assert(stateMachine.getClass.getName.contains("stateMachine"), stateMachine.getClass) + stateMachine + } } - assert(m3() == "Cell(2)") - } + @Test + def `zero out fields of reference type`(): Unit = { + def live: Set[Any] = { + testingEc.live[Cell[_]].map(_.v).toSet + } - @Test - def `zero out fields of value class type`(): Unit = { - val f = async { Cell(1) } + def m3() = async { + val _0: Any = await(Cell(0)) + val _1: Cell[Int] = await(Cell(1)) + identity(_1) + val _2 = await(Cell(2)) - def m1(x: Cell[Int]): Meter = - async { new Meter(x.v + 1) } + identity(_1) + assertEquals(Set(0, 1), live) + val res = await(_2.toString) - def m2(x: Any): String = - async { x.toString } + assertEquals(Set(0), live) + identity(_0) + res + } - def m3() = async { - val a: Cell[Int] = await(f) // await$10$1 - // a == Cell(1) - val b: Meter = await(m1(a)) // await$11$1 - // b == Meter(2) - assert(AsyncTestLV.log.exists(_._2 == Cell(1))) - val res = await(m2(b.len)) // await$12$1 - assert(AsyncTestLV.log.exists(_._2.asInstanceOf[Meter].len == 2L)) - res + assert(block(m3()) == "Cell(2)") } - assert(m3() == "2") - } - - @Test - def `zero out fields after use in loop`(): Unit = { - val f = async { MCell(1) } - - def m1(x: MCell[Int], y: Int): Int = - async { x.v + y } + @Test + def `zero out fields after use in loop`(): Unit = { + val f = Cell(1) - def m3() = async { - // state #1 - val a: MCell[Int] = await(f) // await$13$1 - // state #2 - var y = MCell(0) - - while (a.v < 10) { - // state #4 - a.v = a.v + 1 - y = MCell(await(a).v + 1) // await$14$1 - // state #7 + def live: Set[Any] = { + testingEc.live[Cell[_]].map(_.v).toSet } - // state #3 - // assert(AsyncTestLV.log.exists(entry => entry._1 == "await$14$1")) - - val b = await(m1(a, y.v)) // await$15$1 - // state #8 - assert(AsyncTestLV.log.exists(_._2 == MCell(10)), AsyncTestLV.log) - assert(AsyncTestLV.log.exists(_._2 == MCell(11))) - b - } - - assert(m3() == 21, m3()) - } + def m3() = async { + val _0: Any = await(Cell(0)) + val _1: Cell[Int] = await(Cell(1)) + var i = 0 + while (i < 5) { + identity(await(_1)) + assertEquals("in loop", Set(0, 1), live) + i += 1 + } + assertEquals("after loop", Set(0), live) + identity(_0) + await(()) + assertEquals("end of block", Set(), live) + () + } - @Test - def `don't zero captured fields captured lambda`(): Unit = { - val f = async { - val x = "x" - val y = "y" - await(0) - y.reverse - val f = () => assert(x != null) - await(0) - f + block(m3()) } - AsyncTestLV.assertNotNulledOut("x") - AsyncTestLV.assertNulledOut("y") - f() - } - @Test - def `don't zero captured fields captured by-name`(): Unit = { - def func0[A](a: => A): () => A = () => a - val f = async { - val x = "x" - val y = "y" - await(0) - y.reverse - val f = func0(assert(x != null)) - await(0) - f - } - AsyncTestLV.assertNotNulledOut("x") - AsyncTestLV.assertNulledOut("y") - f() - } + @Test + def `don't zero captured fields captured lambda`(): Unit = { + def live: Set[Any] = { + testingEc.live[Cell[_]].map(_.v).toSet + } - @Test - def `don't zero captured fields nested class`(): Unit = { - def func0[A](a: => A): () => A = () => a - val f = async { - val x = "x" - val y = "y" - await(0) - y.reverse - val f = new Function0[Unit] { - def apply = assert(x != null) - } - await(0) - f - } - AsyncTestLV.assertNotNulledOut("x") - AsyncTestLV.assertNulledOut("y") - f() - } + def m3() = async { + val _1 = Cell(1) + val _2 = Cell(2) + val _3 = Cell(3) + val _4 = Cell(4) + val _5 = Cell(5) + val _6 = Cell(6) + await(0) + _1.toString.reverse + val fun = () => assert(_2 != null) + class LocalClass { assert(_3 != null) } + object localObject { assert(_4 != null) } + def localDef = { assert(_5 != null) } + lazy val localLazy = { assert(_6 != null) } + await(0) + assertEquals("after capture", Set(2, 3, 4, 5, 6), live) + fun() + new LocalClass() + localObject + } - @Test - def `don't zero captured fields nested object`(): Unit = { - def func0[A](a: => A): () => A = () => a - val f = async { - val x = "x" - val y = "y" - await(0) - y.reverse - object f extends Function0[Unit] { - def apply = assert(x != null) + block(m3()) } - await(0) - f - } - AsyncTestLV.assertNotNulledOut("x") - AsyncTestLV.assertNulledOut("y") - f() - } - @Test - def `don't zero captured fields nested def`(): Unit = { - val f = async { - val x = "x" - val y = "y" - await(0) - y.reverse - def xx = x - val f = xx _ - await(0) - f - } - AsyncTestLV.assertNotNulledOut("x") - AsyncTestLV.assertNulledOut("y") - f() - } - @Test - def `capture bug`(): Unit = { - sealed trait Base - case class B1() extends Base - case class B2() extends Base - val outer = List[(Base, Int)]((B1(), 8)) + @Test + def `capture bug`(): Unit = { + sealed trait Base + case class B1() extends Base + case class B2() extends Base + val outer = List[(Base, Int)]((B1(), 8)) - def getMore(b: Base) = 4 + def getMore(b: Base) = 4 - def baz = async { - outer.head match { - case (a @ B1(), r) => { - val ents = await(getMore(a)) + def baz = async { + outer.head match { + case (a @ B1(), r) => { + val ents = await(getMore(a)) - { () => - println(a) - assert(a ne null) + { () => + // println(a) + assert(a ne null) + } } + case (b @ B2(), x) => + () => ??? } - case (b @ B2(), x) => - () => ??? } + block(baz).apply() } - baz() - } - // https://github.com/scala/async/issues/104 - @Test def dontNullOutVarsOfTypeNothing_t104(): Unit = { - import scala.async.Async._ - import scala.concurrent.duration.Duration - import scala.concurrent.{Await, Future} - import scala.concurrent.ExecutionContext.Implicits.global - def errorGenerator(randomNum: Double) = { - Future { - if (randomNum < 0) { - throw new IllegalStateException("Random number was too low!") - } else { - throw new IllegalStateException("Random number was too high!") + // https://github.com/scala/async/issues/104 + @Test def dontNullOutVarsOfTypeNothing_t104(): Unit = { + def errorGenerator(randomNum: Double) = { + Future { + if (randomNum < 0) { + throw new IllegalStateException("Random number was too low!") + } else { + throw new IllegalStateException("Random number was too high!") + } } } - } - def randomTimesTwo = async { - val num = _root_.scala.math.random - if (num < 0 || num > 1) { - await(errorGenerator(num)) + def randomTimesTwo = async { + val num = _root_.scala.math.random + if (num < 0 || num > 1) { + await(errorGenerator(num)) + } + num * 2 } - num * 2 + block(randomTimesTwo) // was: NotImplementedError } - Await.result(randomTimesTwo, TestLatch.DefaultTimeout) // was: NotImplementedError } } diff --git a/test/async/run/warning.scala b/test/async/run/warning.scala deleted file mode 100644 index d9584e21ab5d..000000000000 --- a/test/async/run/warning.scala +++ /dev/null @@ -1,105 +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 -package run - -import org.junit.Test - -import scala.language.{postfixOps, reflectiveCalls} -import scala.tools.nsc.reporters.StoreReporter - - -class WarningsSpec { - - @Test - // https://github.com/scala/async/issues/74 - def noPureExpressionInStatementPositionWarning_t74(): Unit = { - val tb = mkToolbox(s"-cp ${toolboxClasspath} -Xfatal-warnings") - // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses" - tb.eval(tb.parse { - """ - | import scala.tools.nsc.transform.async.user.AsyncId._ - | async { - | if ("".isEmpty) { - | await(println("hello")) - | () - | } else 42 - | } - """.stripMargin - }) - } - - @Test - // https://github.com/scala/async/issues/74 - def noDeadCodeWarningForAsyncThrow(): Unit = { - val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks") - // was: "a pure expression does nothing in statement position; you may be omitting necessary parentheses" - val source = - """ - | class Test { - | import scala.async.Async._ - | import scala.concurrent.ExecutionContext.Implicits.global - | async { throw new Error() } - | } - """.stripMargin - val run = new global.Run - val sourceFile = global.newSourceFile(source) - run.compileSources(sourceFile :: Nil) - assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos) - } - - @Test - def noDeadCodeWarningInMacroExpansion(): Unit = { - val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ywarn-dead-code -Xfatal-warnings -Ystop-after:refchecks") - val source = """ - | class Test { - | def test = { - | import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global - | async { - | val opt = await(async(Option.empty[String => Future[Unit]])) - | opt match { - | case None => - | throw new RuntimeException("case a") - | case Some(f) => - | await(f("case b")) - | } - | } - | } - |} - """.stripMargin - val run = new global.Run - val sourceFile = global.newSourceFile(source) - run.compileSources(sourceFile :: Nil) - assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos) - } - - @Test - def ignoreNestedAwaitsInIDE_t1002561(): Unit = { - // https://www.assembla.com/spaces/scala-ide/tickets/1002561 - val global = mkGlobal("-cp ${toolboxClasspath} -Yrangepos -Ystop-after:typer ") - val source = """ - | class Test { - | def test = { - | import scala.async.Async._, scala.concurrent._, ExecutionContext.Implicits.global - | async { - | 1 + await({def foo = (async(await(async(2)))); foo}) - | } - | } - |} - """.stripMargin - val run = new global.Run - val sourceFile = global.newSourceFile(source) - run.compileSources(sourceFile :: Nil) - assert(!global.reporter.hasErrors, global.reporter.asInstanceOf[StoreReporter].infos) - } -} From 32e15bbe416f892e9c51a13e9c75e397d41610d8 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2020 18:07:38 +1000 Subject: [PATCH 32/94] Use compiler APIs. --- .../scala/tools/nsc/transform/async/AsyncAnalysis.scala | 2 +- .../scala/tools/nsc/transform/async/AsyncTransform.scala | 2 +- .../scala/tools/nsc/transform/async/LiveVariables.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index 57ccc78c2f3d..4cfbe4958069 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -31,7 +31,7 @@ trait AsyncAnalysis extends TransformUtils { private class UnsupportedAwaitAnalyzer extends AsyncTraverser { override def nestedClass(classDef: ClassDef): Unit = { - val kind = if (classDef.symbol.asClass.isTrait) "trait" else "class" + val kind = if (classDef.symbol.isTrait) "trait" else "class" reportUnsupportedAwait(classDef, s"nested $kind") } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 8e2f7ce43337..120a61bbccba 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -78,7 +78,7 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { 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.asClass.isTrait) + val useClass = customParents.forall(_.typeSymbol.isTrait) val fun1Tpe = if (useClass) definitions.abstractFunctionType(tryResult :: Nil, definitions.UnitTpe) diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index e56f46b8589e..526f51b9916b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -76,7 +76,7 @@ trait LiveVariables extends ExprBuilder { // determine which fields should be live also at the end (will not be nulled out) val noNull: Set[Symbol] = liftedSyms.filter { sym => val tpSym = sym.info.typeSymbol - (tpSym.isClass && (tpSym.asClass.isPrimitive || tpSym == definitions.NothingClass)) || liftables.exists { tree => + (tpSym.isPrimitiveValueClass || tpSym == definitions.NothingClass) || liftables.exists { tree => !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym) } } From c62fb75d824ce88c256efca17754f8d272ad5d29 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2020 23:15:40 +1000 Subject: [PATCH 33/94] Deal in typed trees in ExprBuilder. --- .../nsc/transform/async/AsyncPhase.scala | 2 +- .../nsc/transform/async/AsyncTransform.scala | 5 ++- .../nsc/transform/async/ExprBuilder.scala | 44 ++++++++++--------- .../nsc/transform/async/TransformUtils.scala | 37 ++++++---------- 4 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index a3ac1ff9f8ce..72072fe40b64 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -83,7 +83,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr try { val (newRhs, liftableFields) = asyncTransform(asyncBody) liftables(dd.symbol.owner) = liftableFields - deriveDefDef(dd)(_ => localTyper.typed(newRhs)) + deriveDefDef(dd)(_ => newRhs) } finally { currentTransformState = saved } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 120a61bbccba..68a527ab49f6 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -14,9 +14,10 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import user.{FutureSystem, ScalaConcurrentFutureSystem} -import scala.reflect.internal.Flags +import scala.reflect.internal.{Flags, Mode} import scala.tools.nsc.Global import scala.tools.nsc.transform.TypingTransformers +import scala.tools.nsc.typechecker.Analyzer // TODO: check there's no await outside of an async block @@ -241,7 +242,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li // Insert the null assignments immediately after the state transition for (fld <- flds) { val fieldSym = fld.symbol - stats1 += Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos)) + stats1 += typed(Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos))) } } var foundStateTransition = false diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 291cbb023e61..29b5ddca88a0 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -27,7 +27,8 @@ trait ExprBuilder extends TransformUtils { private def labelDefStates = currentTransformState.labelDefStates final class AsyncState(var stats: List[Tree], val state: Int, var nextStates: Array[Int], val isEmpty: Boolean) { - def mkHandlerCaseForState: CaseDef = CaseDef(Literal(Constant(state)), EmptyTree, adaptToUnit(stats)) + def mkHandlerCaseForState: CaseDef = + CaseDef(Literal(Constant(state)), EmptyTree, adaptToUnit(stats)) override def toString: String = mkToString + " (was: " + initToString + ")" private def mkToString = s"AsyncState #$state, next = ${nextStates.toList}" @@ -95,7 +96,7 @@ trait ExprBuilder extends TransformUtils { val expr = stats.remove(stats.size - 1) stats += completeSuccess(expr) } - stats += Return(literalUnit).setSymbol(currentTransformState.applyMethod) + stats += typed(Return(literalUnit).setSymbol(currentTransformState.applyMethod)) allNextStates -= nextState } if (nextState == StateAssigner.Terminal) @@ -189,7 +190,7 @@ trait ExprBuilder extends TransformUtils { needAfterIfState ||= branchNeedsAfterIfState adaptToUnit(inlinedState.stats) } - stateBuilder.stats += treeCopy.If(stat, cond, mkBranch(thenp), mkBranch(elsep)).clearType() + stateBuilder.stats += treeCopy.If(stat, cond, mkBranch(thenp), mkBranch(elsep)) if (needAfterIfState) { stateBuilder.nextStates += afterIfState buildStateAndOpenNextState(afterIfState, style = StateTransitionStyle.Update) @@ -215,7 +216,7 @@ trait ExprBuilder extends TransformUtils { checkForUnsupportedAwait(guard) treeCopy.CaseDef(cd, pat, guard, mkBranch(rhs)) } - stateBuilder.stats += treeCopy.Match(stat, scrutinee, newCases).clearType() + stateBuilder.stats += treeCopy.Match(stat, scrutinee, newCases) if (needAfterMatchState) { stateBuilder.nextStates += afterMatchState @@ -245,7 +246,7 @@ trait ExprBuilder extends TransformUtils { // allowed even if this case has its own state (ie if there is an asynchrounous path // from the start of the pattern to this case/matchEnd) ld.symbol.setInfo(MethodType(Nil, definitions.UnitTpe)) - stateBuilder.stats += treeCopy.LabelDef(ld, ld.name, ld.params, adaptToUnit(inlinedState.stats)).clearType() + stateBuilder.stats += treeCopy.LabelDef(ld, ld.name, ld.params, adaptToUnit(inlinedState.stats)) val needsAfterLabelState = incorporate(nestedStates, afterLabelState) if (needsAfterLabelState) { @@ -399,11 +400,11 @@ trait ExprBuilder extends TransformUtils { val symLookup = currentTransformState.symLookup def stateMemberRef = gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)) val body = - Match(stateMemberRef, + typed(Match(stateMemberRef, asyncStates.map(_.mkHandlerCaseForState) ++ - List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List(gen.mkMethodCall(definitions.StringModule.info.member(nme.valueOf), stateMemberRef :: Nil))))))) + List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List(gen.mkMethodCall(definitions.StringModule.info.member(nme.valueOf), stateMemberRef :: Nil)))))))) - val body1 = compactStates(body) + val body1 = compactStates(body.asInstanceOf[Match]) val stateMatch = maybeTry(currentTransformState.futureSystem.emitTryCatch)( body1, @@ -415,12 +416,12 @@ trait ExprBuilder extends TransformUtils { val t = Ident(nme.t) val complete = futureSystemOps.completeProm[AnyRef]( symLookup.selectResult, futureSystemOps.tryyFailure[AnyRef](t)) - Block(toList(complete), Return(literalUnit)) + Block(toStats(complete), Return(literalUnit)) } If(Apply(Ident(NonFatalClass), List(Ident(nme.t))), branchTrue, Throw(Ident(nme.t))) branchTrue })), EmptyTree) - LabelDef(symLookup.whileLabel, Nil, Block(stateMatch :: Nil, Apply(Ident(symLookup.whileLabel), Nil))) + typed(LabelDef(symLookup.whileLabel, Nil, Block(stateMatch :: Nil, Apply(Ident(symLookup.whileLabel), Nil)))) } private def compactStates = true @@ -501,20 +502,20 @@ trait ExprBuilder extends TransformUtils { vd.symbol.setFlag(Flag.MUTABLE) val assignOrReturn = if (currentTransformState.futureSystem.emitTryCatch) { If(futureSystemOps.tryyIsFailure(tryyReference), - Block(toList(futureSystemOps.completeProm[AnyRef](currentTransformState.symLookup.selectResult, tryyReference)), + Block(toStats(futureSystemOps.completeProm[AnyRef](currentTransformState.symLookup.selectResult, tryyReference)), Return(literalBoxedUnit).setSymbol(currentTransformState.applyMethod)), assignTryGet ) } else { assignTryGet } - vd :: assignOrReturn :: Nil + vd :: typed(assignOrReturn) :: Nil } // Comlete the Promise in the `result` field with the final sucessful result of this async block. private def completeSuccess(expr: Tree): Tree = { val futureSystemOps = currentTransformState.ops - futureSystemOps.completeWithSuccess(currentTransformState.symLookup.selectResult, expr) + typed(futureSystemOps.completeWithSuccess(currentTransformState.symLookup.selectResult, expr)) } /** What trailing statements should be added to the code for this state to transition to the nest state? */ @@ -524,13 +525,14 @@ trait ExprBuilder extends TransformUtils { val symLookup = currentTransformState.symLookup val callSetter = Apply(symLookup.memberRef(symLookup.stateSetter), Literal(Constant(nextState)) :: Nil) val printStateUpdates = false - (if (printStateUpdates) { + val tree = if (printStateUpdates) { Block( callSetter :: Nil, gen.mkMethodCall(definitions.PredefModule.info.member(TermName("println")), currentTransformState.localTyper.typed(gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)), definitions.ObjectTpe) :: Nil) ) } - else callSetter).updateAttachment(StateTransitionTree) + else callSetter + typed(tree.updateAttachment(StateTransitionTree)) } } @@ -567,11 +569,11 @@ trait ExprBuilder extends TransformUtils { val ifTree = If(Apply(null_ne, Ident(symLookup.applyTrParam) :: Nil), Apply(Ident(currentTransformState.symLookup.whileLabel), Nil), - Block(toList(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod))) - initAwaitableTemp :: initTempCompleted :: mkStateTree(nextState) :: ifTree :: Nil + Block(toStats(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod))) + typed(initAwaitableTemp) :: typed(initTempCompleted) :: mkStateTree(nextState) :: typed(ifTree) :: Nil } else { val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) - mkStateTree(nextState) :: toList(callOnComplete) ::: Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod) :: Nil + mkStateTree(nextState) :: toStats(typed(callOnComplete)) ::: typed(Return(literalUnit)) :: Nil } } } @@ -579,14 +581,14 @@ trait ExprBuilder extends TransformUtils { case object UpdateAndContinue extends StateTransitionStyle { def trees(nextState: Int, stateSet: StateSet): List[Tree] = { stateSet += nextState - List(mkStateTree(nextState), Apply(Ident(currentTransformState.symLookup.whileLabel), Nil)) + List(mkStateTree(nextState), typed(Apply(Ident(currentTransformState.symLookup.whileLabel), Nil))) } } } - // TODO AM: should this explode blocks even when expr is not ()? - private def toList(tree: Tree): List[Tree] = tree match { + private def toStats(tree: Tree): List[Tree] = tree match { case Block(stats, expr) if isLiteralUnit(expr) => stats + case Block(stats, expr) => stats ::: (expr :: Nil) case _ => tree :: Nil } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 03cdafc2171d..367e8244f3bb 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -15,6 +15,7 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.language.existentials +import scala.reflect.internal.Mode import scala.tools.nsc.transform.TypingTransformers // Logic sensitive to where we are in the pipeline @@ -35,37 +36,23 @@ trait PhasedTransform extends TypingTransformers { case _ => false } - private def tpeOf(t: Tree): Type = t match { - case _ if t.tpe != null => t.tpe - case Try(body, Nil, _) => tpeOf(body) - case Block(_, expr) => tpeOf(expr) - case Literal(Constant(())) => definitions.UnitTpe - case Return(_) => definitions.NothingTpe - case _ => NoType - } - def adaptToUnit(rhs: List[Tree]): Block = { - def filterUnit(ts: List[Tree]): List[Tree] = { - val result = ts.filterNot(isLiteralUnit(_)) - // if (result != ts) - // getClass - result - } + def filterUnit(ts: List[Tree]): List[Tree] = ts.filterNot(isLiteralUnit(_)) rhs match { - case (rhs: Block) :: Nil if { val tp = tpeOf(rhs); tp <:< definitions.UnitTpe || tp <:< definitions.BoxedUnitTpe } => + case (rhs: Block) :: Nil if rhs.tpe <:< definitions.UnitTpe || rhs.tpe <:< definitions.BoxedUnitTpe => rhs - case init :+ last if { val tp = tpeOf(last); tp <:< definitions.UnitTpe || tp <:< definitions.BoxedUnitTpe } => - Block(filterUnit(init), last) + case init :+ last if last.tpe <:< definitions.UnitTpe || last.tpe <:< definitions.BoxedUnitTpe=> + Block(filterUnit(init), last).setType(definitions.UnitTpe) case init :+ (last@Literal(Constant(()))) => - Block(filterUnit(init), last) + Block(filterUnit(init), last).setType(definitions.UnitTpe) case init :+ (last@Block(_, Return(_) | Literal(Constant(())))) => - Block(filterUnit(init), last) + Block(filterUnit(init), last).setType(definitions.UnitTpe) case init :+ (last@Block(_, expr)) if expr.symbol == definitions.BoxedUnit_UNIT => - Block(filterUnit(init), last) + Block(filterUnit(init), last).setType(definitions.UnitTpe) case init :+ Block(stats, expr) => - Block(filterUnit(init), Block(filterUnit(stats :+ expr), literalUnit)) + Block(filterUnit(init), Block(filterUnit(stats :+ expr), literalUnit)).setType(definitions.UnitTpe) case _ => - Block(filterUnit(rhs), literalUnit) + Block(filterUnit(rhs), literalUnit).setType(definitions.UnitTpe) } } } @@ -84,6 +71,10 @@ private[async] trait TransformUtils extends PhasedTransform { def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName } + def typedPos(pos: Position)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos)(tree: Tree) + def typedPos(pos: Position, mode: Mode, pt: Type)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos, mode, pt)(tree) + def typed(tree: Tree): Tree = typedPos(currentTransformState.applyMethod.pos)(tree) + def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree): Tree = if (emitTryCatch) Try(block, catches, finalizer) else block From ffc613b1010cfe29209c1ae25ea49494ceb56ecb Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2020 23:32:24 +1000 Subject: [PATCH 34/94] Remove PhasedTransform We don't have any phase-dependent logic left. --- .../nsc/transform/async/TransformUtils.scala | 81 +++++++++---------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 367e8244f3bb..0aa88f0b7b35 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -18,50 +18,10 @@ import scala.language.existentials import scala.reflect.internal.Mode import scala.tools.nsc.transform.TypingTransformers -// Logic sensitive to where we are in the pipeline -// (intend to move the transformation as late as possible, to avoid lugging all these trees around) -trait PhasedTransform extends TypingTransformers { - import global._ - - def assignUnitType(t: Tree): t.type = t.setType(definitions.UnitTpe) - - def isUnitType(tp: Type): Boolean = tp.typeSymbol == definitions.UnitClass || tp =:= definitions.BoxedUnitTpe - - def literalUnit: Tree = Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees - def literalBoxedUnit: Tree = gen.mkAttributedRef(definitions.BoxedUnit_UNIT) - - def isLiteralUnit(t: Tree): Boolean = t match { - case Literal(Constant(())) => true - case t if t.symbol == definitions.BoxedUnit_UNIT => true // important to find match labels (which are potential states) - case _ => false - } - - def adaptToUnit(rhs: List[Tree]): Block = { - def filterUnit(ts: List[Tree]): List[Tree] = ts.filterNot(isLiteralUnit(_)) - rhs match { - case (rhs: Block) :: Nil if rhs.tpe <:< definitions.UnitTpe || rhs.tpe <:< definitions.BoxedUnitTpe => - rhs - case init :+ last if last.tpe <:< definitions.UnitTpe || last.tpe <:< definitions.BoxedUnitTpe=> - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ (last@Literal(Constant(()))) => - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ (last@Block(_, Return(_) | Literal(Constant(())))) => - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ (last@Block(_, expr)) if expr.symbol == definitions.BoxedUnit_UNIT => - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ Block(stats, expr) => - Block(filterUnit(init), Block(filterUnit(stats :+ expr), literalUnit)).setType(definitions.UnitTpe) - case _ => - Block(filterUnit(rhs), literalUnit).setType(definitions.UnitTpe) - } - } -} - - /** * Utilities used in both `ExprBuilder` and `AnfTransform`. */ -private[async] trait TransformUtils extends PhasedTransform { +private[async] trait TransformUtils extends TypingTransformers { import global._ def currentTransformState: AsyncTransformState[global.type] @@ -78,10 +38,10 @@ private[async] trait TransformUtils extends PhasedTransform { def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree): Tree = if (emitTryCatch) Try(block, catches, finalizer) else block - lazy val IllegalStateExceptionClass = rootMirror.staticClass("java.lang.IllegalStateException") + lazy val IllegalStateExceptionClass: Symbol = rootMirror.staticClass("java.lang.IllegalStateException") - def isAsync(fun: Tree) = fun.symbol == currentTransformState.ops.Async_async - def isAwait(fun: Tree) = fun.symbol == currentTransformState.ops.Async_await + def isAsync(fun: Tree): Boolean = fun.symbol == currentTransformState.ops.Async_async + def isAwait(fun: Tree): Boolean = fun.symbol == currentTransformState.ops.Async_await def isBooleanShortCircuit(sym: Symbol): Boolean = sym.owner == definitions.BooleanClass && (sym == definitions.Boolean_and || sym == definitions.Boolean_or) @@ -108,6 +68,39 @@ private[async] trait TransformUtils extends PhasedTransform { throw new MatchError(trees) } + def assignUnitType(t: Tree): t.type = t.setType(definitions.UnitTpe) + + def isUnitType(tp: Type): Boolean = tp.typeSymbol == definitions.UnitClass || tp =:= definitions.BoxedUnitTpe + + def literalUnit: Tree = Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees + def literalBoxedUnit: Tree = gen.mkAttributedRef(definitions.BoxedUnit_UNIT) + + def isLiteralUnit(t: Tree): Boolean = t match { + case Literal(Constant(())) => true + case t if t.symbol == definitions.BoxedUnit_UNIT => true // important to find match labels (which are potential states) + case _ => false + } + + def adaptToUnit(rhs: List[Tree]): Block = { + def filterUnit(ts: List[Tree]): List[Tree] = ts.filterNot(isLiteralUnit(_)) + rhs match { + case (rhs: Block) :: Nil if rhs.tpe <:< definitions.UnitTpe || rhs.tpe <:< definitions.BoxedUnitTpe => + rhs + case init :+ last if last.tpe <:< definitions.UnitTpe || last.tpe <:< definitions.BoxedUnitTpe=> + Block(filterUnit(init), last).setType(definitions.UnitTpe) + case init :+ (last@Literal(Constant(()))) => + Block(filterUnit(init), last).setType(definitions.UnitTpe) + case init :+ (last@Block(_, Return(_) | Literal(Constant(())))) => + Block(filterUnit(init), last).setType(definitions.UnitTpe) + case init :+ (last@Block(_, expr)) if expr.symbol == definitions.BoxedUnit_UNIT => + Block(filterUnit(init), last).setType(definitions.UnitTpe) + case init :+ Block(stats, expr) => + Block(filterUnit(init), Block(filterUnit(stats :+ expr), literalUnit)).setType(definitions.UnitTpe) + case _ => + Block(filterUnit(rhs), literalUnit).setType(definitions.UnitTpe) + } + } + private object ThicketAttachment class ThicketTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { private def expandThicket(t: Tree): List[Tree] = t match { From 789811a016c6cb6c10dba9ca8ebd9830228f67da Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 12 Mar 2020 23:34:00 +1000 Subject: [PATCH 35/94] Remove legacy future system methods. --- .../nsc/transform/async/user/FutureSystem.scala | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index 1f0d9459f300..c71b48a173d6 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -39,7 +39,7 @@ trait FutureSystem { abstract class Ops[Universe <: SymbolTable](val u: Universe) { import u._ - final def isPastErasure = { + final def isPastErasure: Boolean = { val global = u.asInstanceOf[Global] val erasurePhase = global.currentRun.erasurePhase erasurePhase != NoPhase && global.isPast(erasurePhase) @@ -47,9 +47,7 @@ trait FutureSystem { def Async_async: Symbol def Async_await: Symbol - def literalUnitExpr = if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) else Literal(Constant(())) - - def phasedAppliedType(tycon: Type, tp: Type) = if (isPastErasure) tycon else appliedType(tycon, tp) + def literalUnitExpr: Tree = if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) else Literal(Constant(())) def tryType(tp: Type): Type def tryTypeToResult(tp: Type): Type @@ -65,7 +63,7 @@ trait FutureSystem { def onComplete[A, B](future: Expr[Fut[A]], fun: Expr[Tryy[A] => B], execContext: Expr[ExecContext]): Expr[Unit] - def continueCompletedFutureOnSameThread = false + def continueCompletedFutureOnSameThread: Boolean = false /** Return `null` if this future is not yet completed, or `Tryy[A]` with the completed result * otherwise @@ -90,16 +88,11 @@ trait FutureSystem { /** 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] - @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7") - def freshenAllNames: Boolean = false def emitTryCatch: Boolean = true - @deprecated("No longer honoured by the macro, all generated names now contain $async to avoid accidental clashes with lambda lifted names", "0.9.7") - def resultFieldName: String = "result" } // TODO AM: test the erased version by running the remainder of the test suite post-posterasure (i.e., not LateExpansion, which tests AsyncId) From b86f2683ddea6acc2d8d17bbb03a63b9b9d58da7 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 13 Mar 2020 09:46:46 +1000 Subject: [PATCH 36/94] Remove unused imports --- .../scala/tools/nsc/transform/async/AsyncTransform.scala | 3 +-- .../scala/tools/nsc/transform/async/ExprBuilder.scala | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 68a527ab49f6..852efce1511c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -14,10 +14,9 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import user.{FutureSystem, ScalaConcurrentFutureSystem} -import scala.reflect.internal.{Flags, Mode} +import scala.reflect.internal.Flags import scala.tools.nsc.Global import scala.tools.nsc.transform.TypingTransformers -import scala.tools.nsc.typechecker.Analyzer // TODO: check there's no await outside of an async block diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 29b5ddca88a0..0877d8646d86 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -12,12 +12,8 @@ package scala.tools.nsc.transform.async -import java.util.function.IntUnaryOperator - -import user.FutureSystem import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.language.existentials trait ExprBuilder extends TransformUtils { From 2f75120373602e57457b1f1b6040beb12e9210f1 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 13 Mar 2020 10:40:14 +1000 Subject: [PATCH 37/94] Allow a nested TypingTransformer to use an existing local typer. This saves allocations for root imports etc. --- .../tools/nsc/transform/ExplicitOuter.scala | 3 +- .../nsc/transform/TypingTransformers.scala | 15 +++--- .../nsc/transform/async/AnfTransform.scala | 5 +- .../nsc/transform/async/AsyncTransform.scala | 2 +- .../nsc/transform/async/ExprBuilder.scala | 3 +- .../nsc/transform/async/TransformUtils.scala | 51 ++++++++++++++----- 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index cf50d843c2c9..0114897f56ec 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -209,7 +209,8 @@ abstract class ExplicitOuter extends InfoTransform * values for outer parameters of constructors. * The class provides methods for referencing via outer. */ - abstract class OuterPathTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with UnderConstructionTransformer { + abstract class OuterPathTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) with UnderConstructionTransformer { + def this(unit: CompilationUnit) { this(newRootLocalTyper(unit)) } /** The directly enclosing outer parameter, if we are in a constructor */ protected var outerParam: Symbol = NoSymbol diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala index d328697f1bd8..795df159c2ae 100644 --- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala +++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala @@ -21,12 +21,15 @@ trait TypingTransformers { val global: Global import global._ - abstract class TypingTransformer(unit: CompilationUnit) extends Transformer { - var localTyper: analyzer.Typer = - if (phase.erasedTypes) - erasure.newTyper(erasure.rootContextPostTyper(unit, EmptyTree)).asInstanceOf[analyzer.Typer] - else // TODO: AM: should some phases use a regular rootContext instead of a post-typer one?? - analyzer.newTyper(analyzer.rootContextPostTyper(unit, EmptyTree)) + protected def newRootLocalTyper(unit: CompilationUnit): analyzer.Typer = if (phase.erasedTypes) + erasure.newTyper(erasure.rootContextPostTyper(unit, EmptyTree)).asInstanceOf[analyzer.Typer] + else // TODO: AM: should some phases use a regular rootContext instead of a post-typer one?? + analyzer.newTyper(analyzer.rootContextPostTyper(unit, EmptyTree)) + + abstract class TypingTransformer(initLocalTyper: global.analyzer.Typer) extends Transformer { + def this(unit: CompilationUnit) = this(newRootLocalTyper(unit)) + var localTyper: analyzer.Typer = initLocalTyper + currentOwner = localTyper.context.owner protected var curTree: Tree = _ override final def atOwner[A](owner: Symbol)(trans: => A): A = atOwner(curTree, owner)(trans) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 822f951c9e25..35b3ea2dff72 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -31,7 +31,7 @@ private[async] trait AnfTransform extends TransformUtils { trans.atOwner(tree, owner) { trans.apply(tree) } } - private final class AnfTransformer() extends TypingTransformer(currentTransformState.unit) { + private final class AnfTransformer() extends TypingTransformer(currentTransformState.localTyper) { /** Main entry point to the ANF transform. */ def apply(tree: Tree): Block = { transformNewControlFlowBlock(tree) match { @@ -376,7 +376,8 @@ private[async] trait AnfTransform extends TransformUtils { } } - final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) extends ThicketTransformer(currentTransformState.unit) { + final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) + extends ThicketTransformer(currentTransformState.unit, currentTransformState.localTyper) { override def transform(tree: Tree): Tree = { tree match { case _: Function | _: MemberDef => diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 852efce1511c..da2efc1b942a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -275,7 +275,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li // - lifted local variables are entered into the scope of the state machine class // - references to them are rewritten as referencs to the fields. // - the rhs of ValDefs that initialize such fields is turned into an assignment to the field - object UseFields extends explicitOuter.OuterPathTransformer(currentTransformState.unit) { + object UseFields extends explicitOuter.OuterPathTransformer(currentTransformState.localTyper) { private def fieldSel(tree: Tree) = { assert(currentOwner != NoSymbol) val outerOrThis = if (stateMachineClass == currentClass) gen.mkAttributedThis(stateMachineClass) else { diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 0877d8646d86..ff71cd42758b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -592,7 +592,8 @@ trait ExprBuilder extends TransformUtils { labelDefStates.getOrElseUpdate(label, StateAssigner.stateIdForLabel(label)) // Replace jumps to qualifying labels as a state transition. - private class JumpReplacer(states: StateSet, state: Int, shouldReplace: Symbol => Boolean) extends ThicketTransformer(currentTransformState.unit) { + private class JumpReplacer(states: StateSet, state: Int, shouldReplace: Symbol => Boolean) + extends ThicketTransformer(currentTransformState.unit, currentTransformState.localTyper) { override def transform(tree: Tree): Tree = tree match { case Apply(fun, args) if isLabel(fun.symbol) => if (shouldReplace(fun.symbol)) { diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 0aa88f0b7b35..acf895e8850b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -102,7 +102,7 @@ private[async] trait TransformUtils extends TypingTransformers { } private object ThicketAttachment - class ThicketTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + class ThicketTransformer(unit: CompilationUnit, initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { private def expandThicket(t: Tree): List[Tree] = t match { case Block(stats, expr) if t.attachments.containsElement(ThicketAttachment) => stats :+ expr @@ -120,22 +120,45 @@ private[async] trait TransformUtils extends TypingTransformers { override def transform(tree: Tree): Tree = tree match { case Block(stats, expr) => - val stats1 = mutable.ListBuffer[Tree]() - transformTrees(stats).foreach { + val transformedStats = transformTrees(stats) + val transformedExpr = transform(expr) + def expandStats(expanded: mutable.ListBuffer[Tree]) = transformedStats.foreach { case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => - stats1 ++= stats - stats1 += expr + expanded ++= stats + expanded += expr case t => - stats1 += t + expanded += t } - val expr1 = transform(expr) match { + def expandExpr(expanded: mutable.ListBuffer[Tree]): Tree = transformedExpr match { case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => - stats1 ++= stats - expr - case expr => + expanded ++= stats expr + case t => + t + } + + if (stats eq transformedStats) { + if (expr eq transformedExpr) tree + else { + val expanded = new mutable.ListBuffer[Tree] + expanded ++= transformedStats + val expr1 = expandExpr(expanded) + if (expanded.isEmpty) + treeCopy.Block(tree, transformedStats, expr1) + else + treeCopy.Block(tree, expanded.toList, expr1) + } + } else { + val expanded = new mutable.ListBuffer[Tree] + if (expr eq transformedExpr) { + expandStats(expanded) + treeCopy.Block(tree, expanded.toList, expr) + } else { + expandStats(expanded) + val expr1 = expandExpr(expanded) + treeCopy.Block(tree, expanded.toList, expr1) + } } - treeCopy.Block(tree, stats1.toList, expr1) case _ => super.transform(tree) } @@ -232,17 +255,17 @@ private[async] trait TransformUtils extends TypingTransformers { t.setAttachments(t.attachments.addElement(NoAwait)) } - var stack: List[Tree] = Nil + var stack = mutable.ArrayStack[Tree]() override def traverse(tree: Tree): Unit = { - stack ::= tree + stack.push(tree) try { if (isAwait(tree)) stack.foreach(attachContainsAwait) else attachNoAwait(tree) super.traverse(tree) - } finally stack = stack.tail + } finally stack.pop() } } From 4753b3992f3551d92fdadbd631d14b5fb70e2cf8 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Mar 2020 21:52:20 +1000 Subject: [PATCH 38/94] Merge AsyncTransformState with SymLookup Also: - remove some redundant or simply unused parameters. - tighten access on some members --- .../nsc/transform/async/AnfTransform.scala | 4 +- .../nsc/transform/async/AsyncPhase.scala | 13 ++--- .../nsc/transform/async/AsyncTransform.scala | 43 +++++++---------- .../nsc/transform/async/ExprBuilder.scala | 48 +++++++++---------- .../nsc/transform/async/TransformUtils.scala | 4 +- 5 files changed, 51 insertions(+), 61 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 35b3ea2dff72..38a7c55999ab 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -353,7 +353,7 @@ private[async] trait AnfTransform extends TransformUtils { private def typedAssign(lhs: Tree, varSym: Symbol) = Assign(gen.mkAttributedRef(varSym), lhs).setType(definitions.UnitTpe).setPos(lhs.pos) - val tracing: Tracing + protected val tracing: Tracing class Tracing { private var indent = -1 @@ -377,7 +377,7 @@ private[async] trait AnfTransform extends TransformUtils { } final class MatchResultTransformer(caseDefToMatchResult: collection.Map[Symbol, Symbol]) - extends ThicketTransformer(currentTransformState.unit, currentTransformState.localTyper) { + extends ThicketTransformer(currentTransformState.localTyper) { override def transform(tree: Tree): Tree = { tree match { case _: Function | _: MemberDef => diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 72072fe40b64..25be11943e76 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -16,12 +16,12 @@ import scala.collection.mutable import scala.tools.nsc.transform.async.user.FutureSystem import scala.tools.nsc.transform.{Transform, TypingTransformers} -abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTransform{ +abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTransform { self => import global._ val asyncNames = new AsyncNames[global.type](global) - val tracing = new Tracing + protected val tracing = new Tracing val phaseName: String = "async" override def enabled = true // TODO: should be off by default, enabled by flag @@ -41,8 +41,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr 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(_, resultTp :: Nil, List(asyncBody :: Nil, execContext :: Nil)) => - c => c.global.async.macroExpansion.apply(c.callsiteTyper, asyncBody, execContext, resultTp.tpe, c.internal.enclosingOwner) + 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) @@ -72,14 +72,15 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr case dd: DefDef if tree.hasAttachment[FutureSystemAttachment] => val futureSystem = tree.getAndRemoveAttachment[FutureSystemAttachment].get.system - val asyncBody = (dd.rhs: @unchecked) match { case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last).setType(stats.last.tpe) } + val saved = currentTransformState atOwner(dd, dd.symbol) { + val trSym = dd.vparamss.head.head.symbol val saved = currentTransformState - currentTransformState = new AsyncTransformState[global.type](global, futureSystem, unit, this, dd.symbol.owner, dd.symbol, dd.vparamss.head.head.symbol, asyncBody.tpe) + currentTransformState = new AsyncTransformState[global.type](global, futureSystem, this, trSym, asyncBody.tpe) try { val (newRhs, liftableFields) = asyncTransform(asyncBody) liftables(dd.symbol.owner) = liftableFields diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index da2efc1b942a..4dc0cb0b346a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -67,7 +67,7 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { stateMachine$async.result$async.future } */ - def apply(callsiteTyper: analyzer.Typer, asyncBody: Tree, execContext: Tree, resultType: Type, originalOwner: Symbol) = { + def apply(callsiteTyper: analyzer.Typer, asyncBody: Tree, execContext: Tree, resultType: Type) = { val tryResult = futureSystemOps.tryType(resultType) val execContextTempVal = @@ -102,7 +102,9 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { 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(originalOwner)) + DefDef(NoMods, nme.apply, Nil, applyVParamss, TypeTree(definitions.UnitTpe), Block( + asyncBody.updateAttachment(SuppressPureExpressionWarning), Literal(Constant(()))) + ).updateAttachment(ChangeOwnerAttachment(callsiteTyper.context.owner)) } applyFSM.updateAttachment(new FutureSystemAttachment(futureSystem)) @@ -131,48 +133,35 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { } class AsyncTransformState[U <: Global with Singleton](val symbolTable: U, val futureSystem: FutureSystem, - val unit: U#CompilationUnit, val typingTransformer: TypingTransformers#TypingTransformer, - val stateMachineClass: U#Symbol, - val applyMethod: U#Symbol, 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]() - val symLookup = new SymLookup() { - val global: symbolTable.type = symbolTable - val stateMachineClass: global.Symbol = AsyncTransformState.this.stateMachineClass.asInstanceOf[symbolTable.Symbol] - val applyTrParam: global.Symbol = AsyncTransformState.this.applyTrParam.asInstanceOf[symbolTable.Symbol] - } -} -abstract class SymLookup { - val global: Global - import global._ - val stateMachineClass: Symbol - val applyTrParam: Symbol + 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 = { + 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) - lazy val stateGetter: Symbol = stateMachineMember(nme.state) - lazy val stateSetter: Symbol = stateGetter.setterIn(stateGetter.owner) - - def selectResult = Apply(memberRef(nme.result), Nil) - def applyMethod: Symbol = applyTrParam.owner - lazy val whileLabel: Symbol = applyMethod.newLabel(nme.WHILE_PREFIX).setInfo(MethodType(Nil, definitions.UnitTpe)) } trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables with TypingTransformers { - var currentTransformState: AsyncTransformState[global.type] = null + var currentTransformState: AsyncTransformState[global.type] = _ import global._ // synthesize the state machine logic -- explode the apply method's rhs and lift local vals to field defs in the state machine @@ -202,12 +191,12 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li }; */ def asyncTransform(asyncBody: Tree): (Tree, List[Tree]) = { - val applySym = currentTransformState.applyMethod + val transformState = currentTransformState + import transformState.{applySym, stateMachineClass} val futureSystem = currentTransformState.futureSystem val futureSystemOps = futureSystem.mkOps(global) val asyncPos = asyncBody.pos - val stateMachineClass = applySym.owner markContainsAwait(asyncBody) // ANF transform also relies on whether something contains await reportUnsupportedAwaits(asyncBody) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index ff71cd42758b..7de861228c6e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -39,7 +39,7 @@ trait ExprBuilder extends TransformUtils { val stats = ListBuffer[Tree]() val nextStates: StateSet = new StateSet - private val jumpReplacer = new JumpReplacer(nextStates, state, shouldReplace(_)) + private val jumpReplacer = new JumpReplacer(nextStates, shouldReplace(_)) private def shouldReplace(target: Symbol): Boolean = labelDefStates.contains(target) || { val patternOwnerOption = owner.outerIterator.find(_.patternSyms.contains(target)) @@ -92,7 +92,7 @@ trait ExprBuilder extends TransformUtils { val expr = stats.remove(stats.size - 1) stats += completeSuccess(expr) } - stats += typed(Return(literalUnit).setSymbol(currentTransformState.applyMethod)) + stats += typed(Return(literalUnit).setSymbol(currentTransformState.applySym)) allNextStates -= nextState } if (nextState == StateAssigner.Terminal) @@ -393,8 +393,8 @@ trait ExprBuilder extends TransformUtils { def onCompleteHandler: Tree = { val futureSystemOps = currentTransformState.ops - val symLookup = currentTransformState.symLookup - def stateMemberRef = gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.stateGetter)) + val transformState = currentTransformState + def stateMemberRef = gen.mkApplyIfNeeded(transformState.memberRef(transformState.stateGetter)) val body = typed(Match(stateMemberRef, asyncStates.map(_.mkHandlerCaseForState) ++ @@ -411,13 +411,13 @@ trait ExprBuilder extends TransformUtils { val branchTrue = { val t = Ident(nme.t) val complete = futureSystemOps.completeProm[AnyRef]( - symLookup.selectResult, futureSystemOps.tryyFailure[AnyRef](t)) + 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) - typed(LabelDef(symLookup.whileLabel, Nil, Block(stateMatch :: Nil, Apply(Ident(symLookup.whileLabel), Nil)))) + typed(LabelDef(transformState.whileLabel, Nil, Block(stateMatch :: Nil, Apply(Ident(transformState.whileLabel), Nil)))) } private def compactStates = true @@ -460,9 +460,9 @@ trait ExprBuilder extends TransformUtils { } else all private val compactStateTransform = new Transformer { - val symLookup = currentTransformState.symLookup + val transformState = currentTransformState override def transform(tree: Tree): Tree = tree match { - case as @ Apply(qual: Select, (lit @ Literal(Constant(i: Integer))) :: Nil) if qual.symbol == symLookup.stateSetter && compactStates => + case as @ Apply(qual: Select, (lit @ Literal(Constant(i: Integer))) :: Nil) if qual.symbol == transformState.stateSetter && compactStates => val replacement = switchIdOf(i) treeCopy.Apply(tree, qual, treeCopy.Literal(lit, Constant(replacement)):: Nil) case _: Match | _: CaseDef | _: Block | _: If | _: LabelDef => @@ -491,15 +491,15 @@ trait ExprBuilder extends TransformUtils { // Resume execution by extracting the successful value and assigining it to the `awaitable.resultValDef` private def resumeTree(awaitable: Awaitable): List[Tree] = { val futureSystemOps = currentTransformState.ops - def tryyReference = Ident(currentTransformState.symLookup.applyTrParam) + def tryyReference = Ident(currentTransformState.applyTrParam) val assignTryGet = Assign(Ident(awaitable.resultName), futureSystemOps.tryyGet[Any](tryyReference)) val vd = deriveValDef(awaitable.resultValDef)(_ => gen.mkZero(awaitable.resultValDef.symbol.info)) vd.symbol.setFlag(Flag.MUTABLE) val assignOrReturn = if (currentTransformState.futureSystem.emitTryCatch) { If(futureSystemOps.tryyIsFailure(tryyReference), - Block(toStats(futureSystemOps.completeProm[AnyRef](currentTransformState.symLookup.selectResult, tryyReference)), - Return(literalBoxedUnit).setSymbol(currentTransformState.applyMethod)), + Block(toStats(futureSystemOps.completeProm[AnyRef](currentTransformState.selectResult, tryyReference)), + Return(literalBoxedUnit).setSymbol(currentTransformState.applySym)), assignTryGet ) } else { @@ -511,20 +511,20 @@ trait ExprBuilder extends TransformUtils { // Comlete the Promise in the `result` field with the final sucessful result of this async block. private def completeSuccess(expr: Tree): Tree = { val futureSystemOps = currentTransformState.ops - typed(futureSystemOps.completeWithSuccess(currentTransformState.symLookup.selectResult, expr)) + typed(futureSystemOps.completeWithSuccess(currentTransformState.selectResult, expr)) } /** What trailing statements should be added to the code for this state to transition to the nest state? */ private sealed abstract class StateTransitionStyle { def trees(next: Int, stateSet: StateSet): List[Tree] protected def mkStateTree(nextState: Int): Tree = { - val symLookup = currentTransformState.symLookup - val callSetter = Apply(symLookup.memberRef(symLookup.stateSetter), Literal(Constant(nextState)) :: Nil) + val transformState = currentTransformState + val callSetter = Apply(transformState.memberRef(transformState.stateSetter), Literal(Constant(nextState)) :: Nil) val printStateUpdates = false val tree = if (printStateUpdates) { Block( callSetter :: Nil, - gen.mkMethodCall(definitions.PredefModule.info.member(TermName("println")), currentTransformState.localTyper.typed(gen.mkApplyIfNeeded(symLookup.memberRef(symLookup.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 @@ -555,17 +555,17 @@ trait ExprBuilder extends TransformUtils { // synchronously. val futureSystemOps = currentTransformState.ops val fun = This(tpnme.EMPTY) - val symLookup = currentTransformState.symLookup + val transformState = currentTransformState if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempAwaitableSym = symLookup.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) + val tempAwaitableSym = transformState.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) - val initTempCompleted = Assign(Ident(symLookup.applyTrParam), futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) + val initTempCompleted = Assign(Ident(transformState.applyTrParam), futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) val null_ne = Select(Literal(Constant(null)), TermName("ne")) val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) val ifTree = - If(Apply(null_ne, Ident(symLookup.applyTrParam) :: Nil), - Apply(Ident(currentTransformState.symLookup.whileLabel), Nil), - Block(toStats(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.symLookup.applyMethod))) + If(Apply(null_ne, Ident(transformState.applyTrParam) :: Nil), + Apply(Ident(currentTransformState.whileLabel), Nil), + Block(toStats(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.applySym))) typed(initAwaitableTemp) :: typed(initTempCompleted) :: mkStateTree(nextState) :: typed(ifTree) :: Nil } else { val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) @@ -577,7 +577,7 @@ trait ExprBuilder extends TransformUtils { case object UpdateAndContinue extends StateTransitionStyle { def trees(nextState: Int, stateSet: StateSet): List[Tree] = { stateSet += nextState - List(mkStateTree(nextState), typed(Apply(Ident(currentTransformState.symLookup.whileLabel), Nil))) + List(mkStateTree(nextState), typed(Apply(Ident(currentTransformState.whileLabel), Nil))) } } } @@ -592,8 +592,8 @@ trait ExprBuilder extends TransformUtils { labelDefStates.getOrElseUpdate(label, StateAssigner.stateIdForLabel(label)) // Replace jumps to qualifying labels as a state transition. - private class JumpReplacer(states: StateSet, state: Int, shouldReplace: Symbol => Boolean) - extends ThicketTransformer(currentTransformState.unit, currentTransformState.localTyper) { + private class JumpReplacer(states: StateSet, shouldReplace: global.Symbol => Boolean) + extends ThicketTransformer(currentTransformState.localTyper) { override def transform(tree: Tree): Tree = tree match { case Apply(fun, args) if isLabel(fun.symbol) => if (shouldReplace(fun.symbol)) { diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index acf895e8850b..b3cd76fff09c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -33,7 +33,7 @@ private[async] trait TransformUtils extends TypingTransformers { def typedPos(pos: Position)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos)(tree: Tree) def typedPos(pos: Position, mode: Mode, pt: Type)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos, mode, pt)(tree) - def typed(tree: Tree): Tree = typedPos(currentTransformState.applyMethod.pos)(tree) + def typed(tree: Tree): Tree = typedPos(currentTransformState.applySym.pos)(tree) def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree): Tree = if (emitTryCatch) Try(block, catches, finalizer) else block @@ -102,7 +102,7 @@ private[async] trait TransformUtils extends TypingTransformers { } private object ThicketAttachment - class ThicketTransformer(unit: CompilationUnit, initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { + class ThicketTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { private def expandThicket(t: Tree): List[Tree] = t match { case Block(stats, expr) if t.attachments.containsElement(ThicketAttachment) => stats :+ expr From bcf25d77fe009a0aa86f35427e870db22ea9dc1e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Mar 2020 22:10:19 +1000 Subject: [PATCH 39/94] Add a fast path to the async phase If no methods have been marked for tranform, disable the phase for the entire unit. --- .../nsc/transform/async/AsyncPhase.scala | 24 +++++++++++-------- .../nsc/transform/async/AsyncTransform.scala | 3 +-- .../nsc/async/AnnotationDrivenAsync.scala | 14 ++++++----- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 25be11943e76..5d097640fd16 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -24,16 +24,18 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr protected val tracing = new Tracing val phaseName: String = "async" - override def enabled = true // TODO: should be off by default, enabled by flag -// { -// (currentRun.runDefinitions match { -// case null => new definitions.RunDefinitions -// case rd => rd -// }).Async_async.exists -// } - final class FutureSystemAttachment(val system: FutureSystem) extends PlainAttachment - object macroExpansion extends AsyncEarlyExpansion { + private final class FutureSystemAttachment(val system: FutureSystem) 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): Tree = { + units += unit + method.updateAttachment(new FutureSystemAttachment(system)) + method + } + + protected object macroExpansion extends AsyncEarlyExpansion { val global: self.global.type = self.global } @@ -50,9 +52,11 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr // 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 liftables = new mutable.AnyRefMap[Symbol, List[Tree]]() + override def transformUnit(unit: CompilationUnit): Unit = + if (units.contains(unit)) super.transformUnit(unit) + // Together, these transforms below target this tree shaps // { // class $STATE_MACHINE extends ... { diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 4dc0cb0b346a..3f72294c4415 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -22,7 +22,6 @@ import scala.tools.nsc.transform.TypingTransformers abstract class AsyncEarlyExpansion extends TypingTransformers { import global._ - import global.async.FutureSystemAttachment // NOTE: this part runs during typer lazy val futureSystem: FutureSystem = ScalaConcurrentFutureSystem @@ -106,7 +105,7 @@ abstract class AsyncEarlyExpansion extends TypingTransformers { asyncBody.updateAttachment(SuppressPureExpressionWarning), Literal(Constant(()))) ).updateAttachment(ChangeOwnerAttachment(callsiteTyper.context.owner)) } - applyFSM.updateAttachment(new FutureSystemAttachment(futureSystem)) + async.addFutureSystemAttachment(callsiteTyper.context.unit, applyFSM, futureSystem) atPos(asyncBody.pos)(ClassDef(NoMods, tpnme.stateMachine, Nil, gen.mkTemplate(parents, noSelfType, NoMods, List(Nil), diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index dbbad4bbb10a..b4fd1ce89e80 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -458,16 +458,18 @@ 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) val wrapped = q""" { val ${nme.execContextTemp} = () - ${q"""class stateMachine$$async extends _root_.scala.tools.nsc.async.StateMachineBase { - ${q"""def apply(tr: _root_.scala.util.Either[_root_.scala.Throwable, _root_.scala.AnyRef]): _root_.scala.Unit = { - $rhs - () - }""".updateAttachment(ChangeOwnerAttachment(dd.symbol)).updateAttachment(new global.async.FutureSystemAttachment(CustomFutureFutureSystem))} - }""".updateAttachment(new global.async.FutureSystemAttachment(CustomFutureFutureSystem))} + 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] From f512e2f396cbb38b3031961f151a5c58d6cbe003 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Mar 2020 22:28:06 +1000 Subject: [PATCH 40/94] Split some async implementation classes into files --- .../transform/async/AsyncEarlyExpansion.scala | 133 ++++++++++++++++ .../nsc/transform/async/AsyncTransform.scala | 143 ------------------ .../transform/async/AsyncTransformState.scala | 46 ++++++ 3 files changed, 179 insertions(+), 143 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala new file mode 100644 index 000000000000..8ff49066a3ab --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala @@ -0,0 +1,133 @@ +/* + * 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.collection.mutable +import user.{FutureSystem, ScalaConcurrentFutureSystem} +import scala.reflect.internal.Flags +import scala.tools.nsc.Global +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/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 3f72294c4415..a791e2c62ebe 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -13,152 +13,9 @@ package scala.tools.nsc.transform.async import scala.collection.mutable -import user.{FutureSystem, ScalaConcurrentFutureSystem} -import scala.reflect.internal.Flags -import scala.tools.nsc.Global import scala.tools.nsc.transform.TypingTransformers // TODO: check there's no await outside of an async block - -abstract class AsyncEarlyExpansion extends TypingTransformers { - import global._ - - // NOTE: this part runs during typer - lazy val futureSystem: FutureSystem = ScalaConcurrentFutureSystem - lazy val futureSystemOps: futureSystem.Ops[global.type] = futureSystem.mkOps(global) - - private lazy val Promise_class = rootMirror.requiredClass[scala.concurrent.Promise[_]] - private def promType(tp: Type): Type = appliedType(Promise_class, tp) - - /** 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 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(promType(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) - } -} - -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) - -} - trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables with TypingTransformers { var currentTransformState: AsyncTransformState[global.type] = _ import global._ diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala new file mode 100644 index 000000000000..2436e7925d4c --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala @@ -0,0 +1,46 @@ +/* + * 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.collection.mutable +import user.{FutureSystem, ScalaConcurrentFutureSystem} +import scala.reflect.internal.Flags +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) +} From dd0001b38347e53d9e18f2daff228ee4e6240537 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Mar 2020 22:40:59 +1000 Subject: [PATCH 41/94] Refactor lifting to to change owner sooner And reuse existing utility method to change module class owner. --- .../tools/nsc/transform/async/AsyncTransform.scala | 10 +--------- .../scala/tools/nsc/transform/async/Lifter.scala | 4 +++- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index a791e2c62ebe..1ada1f7347f1 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -49,8 +49,7 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li def asyncTransform(asyncBody: Tree): (Tree, List[Tree]) = { val transformState = currentTransformState import transformState.{applySym, stateMachineClass} - val futureSystem = currentTransformState.futureSystem - val futureSystemOps = futureSystem.mkOps(global) + val futureSystemOps = transformState.ops val asyncPos = asyncBody.pos @@ -108,13 +107,6 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li } val liftedSyms = liftedFields.map(_.symbol).toSet - liftedSyms.foreach { sym => - if (sym != null) { - sym.owner = stateMachineClass - if (sym.isModule) - sym.asModule.moduleClass.owner = stateMachineClass - } - } // Adjust the tree to: // - lifted local variables are entered into the scope of the state machine class diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index e05cbb6b2404..558d024aac6d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -139,10 +139,12 @@ trait Lifter extends ExprBuilder { (liftableStatementRefs ++ liftableRefsOfDefTrees).foreach(markForLift) liftableMutableSet } - + val changer = new ChangeOwnerTraverser(currentTransformState.applySym, currentTransformState.stateMachineClass) liftableSyms.iterator.map(symToTree).map { t => val sym = t.symbol + changer.change(sym) + val treeLifted = t match { case vd@ValDef(_, _, tpt, rhs) => val isLazy = sym.isLazy From ad00a919ba26dbe3630563dfc1b0f0495cafcdd5 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 14 Mar 2020 23:23:07 +1000 Subject: [PATCH 42/94] Refactor field nulling. --- .../nsc/transform/async/AsyncTransform.scala | 30 +-------- .../nsc/transform/async/ExprBuilder.scala | 24 +++++++ .../nsc/transform/async/LiveVariables.scala | 63 +++++-------------- 3 files changed, 42 insertions(+), 75 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala index 1ada1f7347f1..41f661ab9949 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala @@ -12,7 +12,6 @@ package scala.tools.nsc.transform.async -import scala.collection.mutable import scala.tools.nsc.transform.TypingTransformers // TODO: check there's no await outside of an async block @@ -73,36 +72,11 @@ trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with Li val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) // live variables analysis - // the result map indicates in which states a given field should be nulled out val nullOut = true if (nullOut) { - val assignsOf = fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields) - - for ((state, flds) <- assignsOf) { + for ((state, flds) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { val asyncState = asyncBlock.asyncStates.find(_.state == state).get - val stats1 = mutable.ListBuffer[Tree]() - def addNullAssigments(): Unit = { - // Insert the null assignments immediately after the state transition - for (fld <- flds) { - val fieldSym = fld.symbol - stats1 += typed(Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info).setPos(asyncPos))) - } - } - var foundStateTransition = false - asyncState.stats.foreach { - stat => - stats1 += stat - if (stat.attachments.containsElement(StateTransitionTree)) { - assert(!foundStateTransition) - foundStateTransition = true - // Insert the null assignments immediately after the state transition - addNullAssigments() - } - } - if (!foundStateTransition) { - addNullAssigments() - } - asyncState.stats = stats1.toList + asyncState.insertNullAssignments(flds.iterator) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 7de861228c6e..832ef4680b3f 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -29,6 +29,30 @@ trait ExprBuilder extends TransformUtils { override def toString: String = mkToString + " (was: " + initToString + ")" private def mkToString = s"AsyncState #$state, next = ${nextStates.toList}" private val initToString = mkToString + def insertNullAssignments(flds: Iterator[Symbol]): Unit = { + val stats1 = mutable.ListBuffer[Tree]() + def addNullAssigments(): Unit = { + // Insert the null assignments immediately after the state transition + for (fieldSym <- flds) { + stats1 += typed(Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info))) + } + } + var foundStateTransition = false + stats.foreach { + stat => + stats1 += stat + if (stat.attachments.containsElement(StateTransitionTree)) { + assert(!foundStateTransition) + foundStateTransition = true + // Insert the null assignments immediately after the state transition + addNullAssigments() + } + } + if (!foundStateTransition) { + addNullAssigments() + } + stats = stats1.toList + } } /* diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 526f51b9916b..8d0d973f4249 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -16,57 +16,21 @@ import java.util.function.IntConsumer import scala.collection.immutable.IntMap import scala.collection.mutable - import scala.reflect.internal.Flags._ trait LiveVariables extends ExprBuilder { import global._ - /** - * Returns for a given state a list of fields (as trees) that should be nulled out - * upon resuming that state (at the beginning of `resume`). - * - * @param asyncStates the states of an `async` block - * @param liftables the lifted fields - * @return a map mapping a state to the fields that should be nulled out - * upon resuming that state - */ - def fieldsToNullOut(asyncStates: List[AsyncState], finalState: AsyncState, liftables: List[Tree]): mutable.LinkedHashMap[Int, List[Tree]] = { - // live variables analysis: - // the result map indicates in which states a given field should be nulled out - val liveVarsMap: mutable.LinkedHashMap[Tree, StateSet] = liveVars(asyncStates, finalState, liftables) - - val assignsOf = mutable.LinkedHashMap[Int, List[Tree]]() - - for ((fld, where) <- liveVarsMap) { - where.foreach { new IntConsumer { def accept(state: Int): Unit = { - assignsOf get state match { - case None => - assignsOf += (state -> List(fld)) - case Some(trees) if !trees.exists(_.symbol == fld.symbol) => - assignsOf += (state -> (fld +: trees)) - case _ => - // do nothing - } - }}} - } - - assignsOf - } - /** * Live variables data-flow analysis. * - * The goal is to find, for each lifted field, the last state where the field is used. - * In all direct successor states which are not (indirect) predecessors of that last state - * (possible through loops), the corresponding field should be nulled out (at the beginning of - * `resume`). + * Find, for each lifted field, the last state where the field is used. * * @param asyncStates the states of an `async` block * @param liftables the lifted fields - * @return a map which indicates for a given field (the key) the states in which it should be nulled out + * @return a map which indicates fields which are used for the final time in each state. */ - def liveVars(asyncStates: List[AsyncState], finalState: AsyncState, liftables: List[Tree]): mutable.LinkedHashMap[Tree, StateSet] = { + def fieldsToNullOut(asyncStates: List[AsyncState], finalState: AsyncState, liftables: List[Tree]): mutable.LinkedHashMap[Int, mutable.LinkedHashSet[Symbol]] = { val liftedSyms: Set[Symbol] = // include only vars liftables.iterator.filter { case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE) @@ -248,21 +212,26 @@ trait LiveVariables extends ExprBuilder { result } - val lastUsages: mutable.LinkedHashMap[Tree, StateSet] = - mutable.LinkedHashMap(liftables.map(fld => fld -> lastUsagesOf(fld, finalState)): _*) + val lastUsages: mutable.LinkedHashMap[Symbol, StateSet] = + mutable.LinkedHashMap(liftables.map(fld => fld.symbol -> lastUsagesOf(fld, finalState)): _*) if(settings.debug.value && shouldLogAtThisPhase) { for ((fld, lastStates) <- lastUsages) - debuglog(s"field ${fld.symbol.name} is last used in states ${lastStates.iterator.mkString(", ")}") + debuglog(s"field ${fld.name} is last used in states ${lastStates.iterator.mkString(", ")}") } - val nullOutAt: mutable.LinkedHashMap[Tree, StateSet] = lastUsages - if(settings.debug.value && shouldLogAtThisPhase) { - for ((fld, killAt) <- nullOutAt) - debuglog(s"field ${fld.symbol.name} should be nulled out at the conclusion of states ${killAt.iterator.mkString(", ")}") + for ((fld, killAt) <- lastUsages) + debuglog(s"field ${fld.name} should be nulled out at the conclusion of states ${killAt.iterator.mkString(", ")}") } - nullOutAt + val assignsOf = mutable.LinkedHashMap[Int, mutable.LinkedHashSet[Symbol]]() + + for ((fld, where) <- lastUsages) { + where.foreach { new IntConsumer { def accept(state: Int): Unit = { + assignsOf.getOrElseUpdate(state, new mutable.LinkedHashSet()) += fld + }}} + } + assignsOf } } From 60db8f9e2b51148f9b285856cce6a58ea89a6fc8 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 12:12:07 +1000 Subject: [PATCH 43/94] Remove unused imports. --- .../scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala | 2 -- .../scala/tools/nsc/transform/async/AsyncTransformState.scala | 4 +--- .../scala/tools/nsc/transform/async/TransformUtils.scala | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala index 8ff49066a3ab..d61639b61d5a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala @@ -12,10 +12,8 @@ package scala.tools.nsc.transform.async -import scala.collection.mutable import user.{FutureSystem, ScalaConcurrentFutureSystem} import scala.reflect.internal.Flags -import scala.tools.nsc.Global import scala.tools.nsc.transform.TypingTransformers // NOTE: this part runs during typer to wrap argument to the `async` macro diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala index 2436e7925d4c..ee9cdcbb2b21 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala @@ -12,9 +12,7 @@ package scala.tools.nsc.transform.async -import scala.collection.mutable -import user.{FutureSystem, ScalaConcurrentFutureSystem} -import scala.reflect.internal.Flags +import user.FutureSystem import scala.tools.nsc.Global import scala.tools.nsc.transform.TypingTransformers diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index b3cd76fff09c..16c0e9cfd281 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -14,7 +14,6 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.language.existentials import scala.reflect.internal.Mode import scala.tools.nsc.transform.TypingTransformers From 5755c7468f45a9222d1c87eeaec6a9f8229330d1 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 13:30:31 +1000 Subject: [PATCH 44/94] Remove dead cases from adaptToUnit The input trees are always typed now which means the early cases subsume the ones being deleted here. --- .../scala/tools/nsc/transform/async/TransformUtils.scala | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 16c0e9cfd281..2928dbcca61e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -87,14 +87,6 @@ private[async] trait TransformUtils extends TypingTransformers { rhs case init :+ last if last.tpe <:< definitions.UnitTpe || last.tpe <:< definitions.BoxedUnitTpe=> Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ (last@Literal(Constant(()))) => - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ (last@Block(_, Return(_) | Literal(Constant(())))) => - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ (last@Block(_, expr)) if expr.symbol == definitions.BoxedUnit_UNIT => - Block(filterUnit(init), last).setType(definitions.UnitTpe) - case init :+ Block(stats, expr) => - Block(filterUnit(init), Block(filterUnit(stats :+ expr), literalUnit)).setType(definitions.UnitTpe) case _ => Block(filterUnit(rhs), literalUnit).setType(definitions.UnitTpe) } From ef04e596bd02cd7d5ad9dc7140262c27cbfcca32 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 14:04:40 +1000 Subject: [PATCH 45/94] Move AsyncTransform code into AsyncPhase.scala --- .../nsc/transform/async/AsyncPhase.scala | 119 ++++++++++++++- .../nsc/transform/async/AsyncTransform.scala | 143 ------------------ .../nsc/transform/async/TransformUtils.scala | 4 +- .../transform/async/user/FutureSystem.scala | 2 +- 4 files changed, 115 insertions(+), 153 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 5d097640fd16..bb40a679bc1c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -16,12 +16,13 @@ import scala.collection.mutable import scala.tools.nsc.transform.async.user.FutureSystem import scala.tools.nsc.transform.{Transform, TypingTransformers} -abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTransform { +abstract class AsyncPhase extends Transform with TypingTransformers with AnfTransform with AsyncAnalysis with Lifter with LiveVariables { self => import global._ - val asyncNames = new AsyncNames[global.type](global) - protected val tracing = new Tracing + private[async] var currentTransformState: AsyncTransformState[global.type] = _ + private[async] val asyncNames = new AsyncNames[global.type](global) + protected[async] val tracing = new Tracing val phaseName: String = "async" @@ -52,7 +53,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr // 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 liftables = new mutable.AnyRefMap[Symbol, List[Tree]]() + private lazy val liftableMap = new mutable.AnyRefMap[Symbol, (Symbol, List[Tree])]() override def transformUnit(unit: CompilationUnit): Unit = if (units.contains(unit)) super.transformUnit(unit) @@ -71,8 +72,16 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr // are lifted into members of the enclosing class. override def transform(tree: Tree): Tree = super.transform(tree) match { - case cd: ClassDef if liftables.contains(cd.symbol) => - deriveClassDef(cd)(impl => deriveTemplate(impl)(liftables.remove(cd.symbol).getOrElse(Nil) ::: _)) + 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) { + deriveClassDef(cd)(impl => { + deriveTemplate(impl)(liftedTrees ::: _) + }) + } + 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 @@ -87,7 +96,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr currentTransformState = new AsyncTransformState[global.type](global, futureSystem, this, trSym, asyncBody.tpe) try { val (newRhs, liftableFields) = asyncTransform(asyncBody) - liftables(dd.symbol.owner) = liftableFields + liftableMap(dd.symbol.owner) = (dd.symbol, liftableFields) deriveDefDef(dd)(_ => newRhs) } finally { currentTransformState = saved @@ -95,5 +104,101 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AsyncTr } 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 + + markContainsAwait(asyncBody) // ANF transform also relies on whether something contains await + reportUnsupportedAwaits(asyncBody) + + // Transform to A-normal form: + // - no await calls in qualifiers or arguments, + // - if/match only used in statement position. + val anfTree0: Block = anfTransform(asyncBody, applySym) + + val anfTree = futureSystemOps.postAnfTransform(anfTree0) + + // The ANF transform re-parents some trees, so the previous traversal to mark ancestors of + // await is no longer reliable. + cleanupContainsAwaitAttachments(anfTree) + markContainsAwait(anfTree) + + val asyncBlock = buildAsyncBlock(anfTree) + + val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) + + // live variables analysis + val nullOut = true + if (nullOut) { + for ((state, flds) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { + val asyncState = asyncBlock.asyncStates.find(_.state == state).get + asyncState.insertNullAssignments(flds.iterator) + } + } + + val liftedSyms = liftedFields.map(_.symbol).toSet + + val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) + + if (settings.debug.value && shouldLogAtThisPhase) { + val location = try asyncBody.pos.source.path catch { + case _: UnsupportedOperationException => asyncBody.pos.toString + } + logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) + } + futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) + + (cleanupContainsAwaitAttachments(applyBody), liftedFields) + } + + // Adjust the tree to: + // - lifted local variables are entered into the scope of the state machine class + // - references to them are rewritten as referencs to the fields. + // - the rhs of ValDefs that initialize such fields is turned into an assignment to the field + private class UseFields(initLocalTyper: analyzer.Typer, stateMachineClass: Symbol, + applySym: Symbol, liftedSyms: Set[Symbol]) extends explicitOuter.OuterPathTransformer(initLocalTyper) { + private def fieldSel(tree: Tree) = { + assert(currentOwner != NoSymbol) + val outerOrThis = if (stateMachineClass == currentClass) gen.mkAttributedThis(stateMachineClass) else { + // These references need to be selected from an outer reference, because explicitouter + // has already run we must perform this transform explicitly here. + tree.symbol.makeNotPrivate(tree.symbol.owner) + outerPath(outerValue, currentClass.outerClass, stateMachineClass) + } + atPos(tree.pos)(Select(outerOrThis.setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) + } + override def transform(tree: Tree): Tree = tree match { + case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) && currentOwner == applySym => + // Drop the lifted definitions from the apply method + assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner(tree.symbol, currentOwner)))) + case _: DefTree if liftedSyms(tree.symbol) && currentOwner == applySym => + // Drop the lifted definitions from the apply method + EmptyTree + case md: MemberDef if currentOwner == stateMachineClass && liftedSyms(tree.symbol) => + stateMachineClass.info.decls.enter(md.symbol) + super.transform(tree) + case Assign(i @ Ident(name), rhs) if liftedSyms(i.symbol) => + treeCopy.Assign(tree, fieldSel(i), adapt(transform(rhs), i.symbol.tpe)) + case Ident(name) if liftedSyms(tree.symbol) => + fieldSel(tree).setType(tree.tpe) + case _ => + super.transform(tree) + } + + // Use localTyper to adapt () to BoxedUnit in `val ifRes: Object; if (cond) "" else ()` + private def adapt(tree: Tree, pt: Type): Tree = localTyper.typed(tree, pt) + } + + private def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { + inform(s"In file '$location':") + inform(s"ANF transform expands to:\n $anfTree") + states foreach (s => inform(s)) + inform("===== DOT =====") + inform(block.toDot) + } } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala deleted file mode 100644 index 41f661ab9949..000000000000 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransform.scala +++ /dev/null @@ -1,143 +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 scala.tools.nsc.transform.TypingTransformers - -// TODO: check there's no await outside of an async block -trait AsyncTransform extends AnfTransform with AsyncAnalysis with Lifter with LiveVariables with TypingTransformers { - var currentTransformState: AsyncTransformState[global.type] = _ - import global._ - - // synthesize the state machine logic -- explode the apply method's rhs and lift local vals to field defs in the state machine - /* - val execContext0$async = `execContext`; - class stateMachine$async extends scala.runtime.AbstractFunction1 { - def (): stateMachine$async = { - stateMachine$async.super.(); - stateMachine$async.super./*Function0*/$init$(); - () - }; - 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 = Promise.apply(); - def result$async(): scala.concurrent.Promise = 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): Unit = { // symbol of this def is `applySym`, symbol of its param named "tr$async" is `trParamSym` - `asyncBody` - () - }; - def apply(v1: Object): Object = { - stateMachine$async.this.apply(v1.$asInstanceOf[scala.util.Try]()); - scala.runtime.BoxedUnit.UNIT - }; - }; - */ - def asyncTransform(asyncBody: Tree): (Tree, List[Tree]) = { - val transformState = currentTransformState - import transformState.{applySym, stateMachineClass} - val futureSystemOps = transformState.ops - - val asyncPos = asyncBody.pos - - markContainsAwait(asyncBody) // ANF transform also relies on whether something contains await - reportUnsupportedAwaits(asyncBody) - - // Transform to A-normal form: - // - no await calls in qualifiers or arguments, - // - if/match only used in statement position. - val anfTree0: Block = anfTransform(asyncBody, applySym) - - val anfTree = futureSystemOps.postAnfTransform(anfTree0) - - // The ANF transform re-parents some trees, so the previous traversal to mark ancestors of - // await is no longer reliable. - cleanupContainsAwaitAttachments(anfTree) - markContainsAwait(anfTree) - - val asyncBlock = buildAsyncBlock(anfTree) - - val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) - - // live variables analysis - val nullOut = true - if (nullOut) { - for ((state, flds) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { - val asyncState = asyncBlock.asyncStates.find(_.state == state).get - asyncState.insertNullAssignments(flds.iterator) - } - } - - val liftedSyms = liftedFields.map(_.symbol).toSet - - // Adjust the tree to: - // - lifted local variables are entered into the scope of the state machine class - // - references to them are rewritten as referencs to the fields. - // - the rhs of ValDefs that initialize such fields is turned into an assignment to the field - object UseFields extends explicitOuter.OuterPathTransformer(currentTransformState.localTyper) { - private def fieldSel(tree: Tree) = { - assert(currentOwner != NoSymbol) - val outerOrThis = if (stateMachineClass == currentClass) gen.mkAttributedThis(stateMachineClass) else { - // These references need to be selected from an outer reference, because explicitouter - // has already run we must perform this transform explicitly here. - tree.symbol.makeNotPrivate(tree.symbol.owner) - outerPath(outerValue, currentClass.outerClass, stateMachineClass) - } - atPos(tree.pos)(Select(outerOrThis.setType(stateMachineClass.tpe), tree.symbol).setType(tree.symbol.tpe)) - } - override def transform(tree: Tree): Tree = tree match { - case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) && currentOwner == applySym => - // Drop the lifted definitions from the apply method - assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner(tree.symbol, currentOwner)))) - case _: DefTree if liftedSyms(tree.symbol) && currentOwner == applySym => - // Drop the lifted definitions from the apply method - EmptyTree - case md: MemberDef if currentOwner == stateMachineClass && liftedSyms(tree.symbol)=> - stateMachineClass.info.decls.enter(md.symbol) - super.transform(tree) - case Assign(i @ Ident(name), rhs) if liftedSyms(i.symbol) => - treeCopy.Assign(tree, fieldSel(i), adapt(transform(rhs), i.symbol.tpe)) - case Ident(name) if liftedSyms(tree.symbol) => - fieldSel(tree).setType(tree.tpe) - case _ => - super.transform(tree) - } - - // Use localTyper to adapt () to BoxedUnit in `val ifRes: Object; if (cond) "" else ()` - private def adapt(tree: Tree, pt: Type): Tree = localTyper.typed(tree, pt) - } - val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) - val ClassDef(_, _, _, Template(_, _, (applyDefDef: DefDef) :: liftablesUseFields)) = - UseFields.transformAtOwner(stateMachineClass.owner, ClassDef(stateMachineClass, DefDef(applySym, applyBody) :: liftedFields)) - - if (settings.debug.value && shouldLogAtThisPhase) { - val location = try asyncBody.pos.source.path catch { - case _: UnsupportedOperationException => asyncBody.pos.toString - } - logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) - } - futureSystemOps.dot(applySym, asyncBody).foreach(f => f(asyncBlock.toDot)) - - (cleanupContainsAwaitAttachments(applyDefDef.rhs), liftablesUseFields) - } - - def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { - inform(s"In file '$location':") - inform(s"ANF transform expands to:\n $anfTree") - states foreach (s => inform(s)) - inform("===== DOT =====") - inform(block.toDot) - } -} diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 2928dbcca61e..b93aa94b62a9 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -23,8 +23,8 @@ import scala.tools.nsc.transform.TypingTransformers private[async] trait TransformUtils extends TypingTransformers { import global._ - def currentTransformState: AsyncTransformState[global.type] - val asyncNames: AsyncNames[global.type] + 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) def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index c71b48a173d6..abd69e4d75e6 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -19,7 +19,7 @@ import scala.tools.nsc.Global /** * An abstraction over a future system. * - * Used by the macro implementations in [[scala.tools.nsc.transform.async.AsyncTransform]] to + * 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 From 6f1546ce0f1b3ccfca2c22a45b67604026165a35 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 14:21:08 +1000 Subject: [PATCH 46/94] Use of AnfTransformer directly. --- .../scala/tools/nsc/transform/async/AnfTransform.scala | 7 +------ .../scala/tools/nsc/transform/async/AsyncPhase.scala | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 38a7c55999ab..0e8222a19f1b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -26,12 +26,7 @@ private[async] trait AnfTransform extends TransformUtils { * - calls to `await` are not allowed in compound expressions; * - execution order is reified in the tree by extracting temporary vals */ - final def anfTransform(tree: Tree, owner: Symbol): Block = { - val trans = new AnfTransformer() - trans.atOwner(tree, owner) { trans.apply(tree) } - } - - private final class AnfTransformer() extends TypingTransformer(currentTransformState.localTyper) { + final class AnfTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { /** Main entry point to the ANF transform. */ def apply(tree: Tree): Block = { transformNewControlFlowBlock(tree) match { diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index bb40a679bc1c..4d4c6c984e93 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -118,7 +118,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 anfTree0: Block = anfTransform(asyncBody, applySym) + val anfTree0: Block = new AnfTransformer(localTyper).apply(asyncBody) val anfTree = futureSystemOps.postAnfTransform(anfTree0) From 93748bd19a3a611fe897ae3250a29b5924090457 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 14:32:56 +1000 Subject: [PATCH 47/94] Refactor asyncTransform --- .../nsc/transform/async/AsyncPhase.scala | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 4d4c6c984e93..9bd58431cdfe 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -112,26 +112,27 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran val asyncPos = asyncBody.pos - markContainsAwait(asyncBody) // ANF transform also relies on whether something contains await + // We mark whether each sub-tree of `asyncBody` that do or do not contain an await in thus pre-processing pass. + // The ANF transform can then efficiently query this to selectively transform the tree. + markContainsAwait(asyncBody) reportUnsupportedAwaits(asyncBody) // Transform to A-normal form: // - no await calls in qualifiers or arguments, // - if/match only used in statement position. - val anfTree0: Block = new AnfTransformer(localTyper).apply(asyncBody) - - val anfTree = futureSystemOps.postAnfTransform(anfTree0) + val anfTree: Block = futureSystemOps.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. + // await is no longer reliable. Clear previous results and run it again for use in the `buildAsyncBlock`. cleanupContainsAwaitAttachments(anfTree) markContainsAwait(anfTree) val asyncBlock = buildAsyncBlock(anfTree) val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) + val liftedSyms = liftedFields.map(_.symbol).toSet - // live variables analysis + // Null out lifted fields become unreachable at each state. val nullOut = true if (nullOut) { for ((state, flds) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { @@ -140,19 +141,18 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran } } - val liftedSyms = liftedFields.map(_.symbol).toSet - + // Assemble the body of the apply method, which is dispactches on the current state id. val applyBody = atPos(asyncPos)(asyncBlock.onCompleteHandler) - if (settings.debug.value && shouldLogAtThisPhase) { - val location = try asyncBody.pos.source.path catch { - case _: UnsupportedOperationException => asyncBody.pos.toString - } - logDiagnostics(location, anfTree, asyncBlock, asyncBlock.asyncStates.map(_.toString)) - } + // Logging + 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)) - (cleanupContainsAwaitAttachments(applyBody), liftedFields) + cleanupContainsAwaitAttachments(applyBody) + + (applyBody, liftedFields) } // Adjust the tree to: @@ -193,7 +193,9 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran private def adapt(tree: Tree, pt: Type): Tree = localTyper.typed(tree, pt) } - private def logDiagnostics(location: String, anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { + private def logDiagnostics(anfTree: Tree, block: AsyncBlock, states: Seq[String]): Unit = { + val pos = currentTransformState.applySym.pos + val location = try pos.source.path catch { case _: UnsupportedOperationException => pos.toString } inform(s"In file '$location':") inform(s"ANF transform expands to:\n $anfTree") states foreach (s => inform(s)) From 22014fcb1e44d35ff6b73a1f8522c7dc88915493 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 14:34:22 +1000 Subject: [PATCH 48/94] Refactor addFutureSystemAttachment --- src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 9bd58431cdfe..bf53babcdeef 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -30,10 +30,9 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran // 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): Tree = { + final def addFutureSystemAttachment(unit: CompilationUnit, method: Tree, system: FutureSystem): method.type = { units += unit method.updateAttachment(new FutureSystemAttachment(system)) - method } protected object macroExpansion extends AsyncEarlyExpansion { From 0cea808904774ded3f7bac052f47e2fb5c8f1bda Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 14:49:32 +1000 Subject: [PATCH 49/94] Remove dead code. --- .../scala/tools/nsc/transform/async/user/FutureSystem.scala | 4 ---- test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala | 1 - 2 files changed, 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index abd69e4d75e6..f8ebf83a3afd 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -47,10 +47,7 @@ trait FutureSystem { def Async_async: Symbol def Async_await: Symbol - def literalUnitExpr: Tree = if (isPastErasure) gen.mkAttributedRef(definitions.BoxedUnit_UNIT) else Literal(Constant(())) - def tryType(tp: Type): Type - def tryTypeToResult(tp: Type): Type def stateMachineClassParents: List[Type] = Nil /** Construct a future to asynchronously compute the given expression -- tree shape should take isPastErasure into account */ @@ -127,7 +124,6 @@ object ScalaConcurrentFutureSystem extends FutureSystem { lazy val Try_get: Symbol = Try_class.info.member(TermName("get")) def tryType(tp: Type): Type = appliedType(Try_class, tp) - def tryTypeToResult(tp: Type): Type = tp.baseType(Try_class).typeArgs.headOption.getOrElse(NoType) def future(a: Tree, execContext: Tree): Tree = if (isPastErasure) Apply(Select(gen.mkAttributedStableRef(Future_class.companionModule), nme.apply), List(a, execContext)) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index b4fd1ce89e80..c1b17895d6c8 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -547,7 +547,6 @@ object CustomFutureFutureSystem extends FutureSystem { lazy val Async_await: Symbol = symbolOf[CustomFuture.type].info.member(TermName("_await")) def tryType(tp: Type): Type = appliedType(Either_class, tp) - def tryTypeToResult(tp: Type): Type = tp.baseType(Either_class).typeArgs.headOption.getOrElse(NoType) def future(a: Tree, execContext: Tree): Tree = Apply(Select(gen.mkAttributedStableRef(Future_class.companionModule), TermName("_apply")), List(a)) From 02bbc1d11c6cc1dac95af3799fbfe44bf0b7048a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 15:02:43 +1000 Subject: [PATCH 50/94] Avoid allocating containsAwaitTraverser so often. --- .../nsc/transform/async/TransformUtils.scala | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index b93aa94b62a9..c8683299832a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -214,19 +214,22 @@ private[async] trait TransformUtils extends TypingTransformers { * in search of a sub tree that was decorated with the cached answer. * * Requires markContainsAwaitTraverser has previously traversed `t`. - **/ - final def containsAwait(t: Tree): Boolean = { - object traverser extends Traverser { - var containsAwait = false - override def traverse(tree: Tree): Unit = - if (tree.attachments.containsElement(NoAwait)) {} // safe to skip - else if (!containsAwait) { - if (tree.attachments.containsElement(ContainsAwait)) containsAwait = true - else if (markContainsAwaitTraverser.shouldAttach(t)) super.traverse(tree) - } + */ + final def containsAwait(t: Tree): Boolean = containsAwaitTraverser.apply(t) + + private object containsAwaitTraverser extends Traverser { + var containsAwait = false + def apply(t: Tree): Boolean = { + containsAwait = false + traverse(t) + containsAwait } - traverser.traverse(t) - traverser.containsAwait + override def traverse(tree: Tree): Unit = + if (tree.attachments.containsElement(NoAwait)) {} // safe to skip + else if (!containsAwait) { + if (tree.attachments.containsElement(ContainsAwait)) containsAwait = true + else if (markContainsAwaitTraverser.shouldAttach(tree)) super.traverse(tree) + } } def markContainsAwait(t: Tree) = markContainsAwaitTraverser.traverse(t) From a3aaeda55892e2a225e71790f77c25697f2bd251 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 16:25:09 +1000 Subject: [PATCH 51/94] Avoid logging string allocation --- src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index f1ede1cad1a7..98c91bb3967a 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -239,7 +239,7 @@ abstract class SymbolLoaders { val currentphase = phase doComplete(root) phase = currentphase - informTime("loaded " + description, start) + if (settings.verbose) informTime("loaded " + description, start) ok = true setSource(root) setSource(root.companionSymbol) // module -> class, class -> module From 1285669734f7d9219daa0ac48744d86548d6e5a2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 16:29:46 +1000 Subject: [PATCH 52/94] Comment out debugging toString. --- .../scala/tools/nsc/transform/async/ExprBuilder.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 832ef4680b3f..078eab7c86bd 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -26,9 +26,9 @@ trait ExprBuilder extends TransformUtils { def mkHandlerCaseForState: CaseDef = CaseDef(Literal(Constant(state)), EmptyTree, adaptToUnit(stats)) - override def toString: String = mkToString + " (was: " + initToString + ")" private def mkToString = s"AsyncState #$state, next = ${nextStates.toList}" - private val initToString = mkToString + override def toString: String = mkToString //+ " (was: " + initToString + ")" + // private val initToString = mkToString def insertNullAssignments(flds: Iterator[Symbol]): Unit = { val stats1 = mutable.ListBuffer[Tree]() def addNullAssigments(): Unit = { From 4124c2530b18e2c5bca2fbee8254f9d7c0adaef8 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 16:38:29 +1000 Subject: [PATCH 53/94] More efficient Contains-/No-Await cleanup --- .../tools/nsc/transform/async/TransformUtils.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index c8683299832a..ce1c195be478 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -263,14 +263,18 @@ private[async] trait TransformUtils extends TypingTransformers { } } - final def cleanupContainsAwaitAttachments(t: Tree): t.type = { - t.foreach { + object cleanupContainsAwaitAttachments extends Traverser { + def apply(tree: Tree): tree.type = {traverse(tree); tree} + override def traverse(tree: Tree): Unit = tree match { case _: CannotHaveAttrs => case t => - t.setAttachments(t.attachments.removeElement(ContainsAwait)) - t.setAttachments(t.attachments.removeElement(NoAwait)) + if (t.attachments.containsElement(NoAwait)) { + t.setAttachments(t.attachments.removeElement(NoAwait)) + } else { + t.setAttachments(t.attachments.removeElement(ContainsAwait)) + super.traverse(t) + } } - t } val isMatchEnd: (Tree) => Boolean = t => From aabe72383e1dd892d43ec65dd84099a59e72cce6 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 17:08:56 +1000 Subject: [PATCH 54/94] Avoid overload resolution in typing default case of state handler. --- .../scala/tools/nsc/transform/async/ExprBuilder.scala | 6 +++++- .../scala/tools/nsc/transform/async/TransformUtils.scala | 2 ++ src/reflect/scala/reflect/internal/Definitions.scala | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 078eab7c86bd..4f737f883dfb 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -419,10 +419,12 @@ trait ExprBuilder extends TransformUtils { 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)))) val body = typed(Match(stateMemberRef, asyncStates.map(_.mkHandlerCaseForState) ++ - List(CaseDef(Ident(nme.WILDCARD), EmptyTree, Throw(Apply(Select(New(Ident(IllegalStateExceptionClass)), termNames.CONSTRUCTOR), List(gen.mkMethodCall(definitions.StringModule.info.member(nme.valueOf), stateMemberRef :: Nil)))))))) + List(CaseDef(Ident(nme.WILDCARD), EmptyTree, + throww)))) val body1 = compactStates(body.asInstanceOf[Match]) @@ -618,7 +620,9 @@ trait ExprBuilder extends TransformUtils { // Replace jumps to qualifying labels as a state transition. private class JumpReplacer(states: StateSet, shouldReplace: global.Symbol => Boolean) extends ThicketTransformer(currentTransformState.localTyper) { + val initOwner = currentTransformState.localTyper.context.owner override def transform(tree: Tree): Tree = tree match { + case _ if initOwner != currentOwner.enclMethod => tree case Apply(fun, args) if isLabel(fun.symbol) => if (shouldReplace(fun.symbol)) { val nextState = addLabelState(fun.symbol) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index ce1c195be478..98898f187031 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -38,6 +38,8 @@ private[async] trait TransformUtils extends TypingTransformers { if (emitTryCatch) Try(block, catches, finalizer) else block lazy val IllegalStateExceptionClass: Symbol = rootMirror.staticClass("java.lang.IllegalStateException") + 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 diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index e876ba276f18..f50df3f5fb39 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -1518,6 +1518,8 @@ trait Definitions extends api.StandardDefinitions { /** Efficient access to member symbols which must be looked up each run. Access via `currentRun.runDefinitions` */ final class RunDefinitions { lazy val StringAdd_+ = getMemberMethod(StringAddClass, nme.PLUS) + lazy val String_valueOf_Int = getMemberMethod(StringClass.companionModule, nme.valueOf).suchThat( + x => x.paramss.head.length == 1 && x.firstParam.info.typeSymbol == IntClass) // The given symbol represents either String.+ or StringAdd.+ // TODO: this misses Predef.any2stringadd From 01a83cbb68da742ec32836f88cfca32da1ac3a47 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 17:45:01 +1000 Subject: [PATCH 55/94] Limit UseFields transform to relevent subtrees. --- .../tools/nsc/transform/async/AsyncPhase.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index bf53babcdeef..97e6eeadaed8 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -177,13 +177,21 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran case _: DefTree if liftedSyms(tree.symbol) && currentOwner == applySym => // Drop the lifted definitions from the apply method EmptyTree - case md: MemberDef if currentOwner == stateMachineClass && liftedSyms(tree.symbol) => - stateMachineClass.info.decls.enter(md.symbol) - super.transform(tree) + case md: MemberDef => + if (currentOwner == stateMachineClass) { + if (liftedSyms(tree.symbol)) { + stateMachineClass.info.decls.enter(md.symbol) + super.transform(tree) + } else if (md.symbol == applySym || md.symbol == stateMachineClass) { + super.transform(tree) + } else tree + } else super.transform(tree) case Assign(i @ Ident(name), rhs) if liftedSyms(i.symbol) => treeCopy.Assign(tree, fieldSel(i), adapt(transform(rhs), i.symbol.tpe)) case Ident(name) if liftedSyms(tree.symbol) => fieldSel(tree).setType(tree.tpe) + case _: TypeTree => + tree case _ => super.transform(tree) } From c89c21b5a90420a45ae7d8eba5f27896deb4091b Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 18:02:35 +1000 Subject: [PATCH 56/94] More typed sub-trees avoiding working in `typed(...)` --- .../scala/tools/nsc/transform/async/ExprBuilder.scala | 10 +++++----- .../tools/nsc/transform/async/user/FutureSystem.scala | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 4f737f883dfb..2d9869eb36fd 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -517,8 +517,8 @@ trait ExprBuilder extends TransformUtils { // Resume execution by extracting the successful value and assigining it to the `awaitable.resultValDef` private def resumeTree(awaitable: Awaitable): List[Tree] = { val futureSystemOps = currentTransformState.ops - def tryyReference = Ident(currentTransformState.applyTrParam) - val assignTryGet = Assign(Ident(awaitable.resultName), futureSystemOps.tryyGet[Any](tryyReference)) + def tryyReference = gen.mkAttributedIdent(currentTransformState.applyTrParam) + val assignTryGet = Assign(gen.mkAttributedIdent(awaitable.resultValDef.symbol), futureSystemOps.tryyGet[Any](tryyReference)) val vd = deriveValDef(awaitable.resultValDef)(_ => gen.mkZero(awaitable.resultValDef.symbol.info)) vd.symbol.setFlag(Flag.MUTABLE) @@ -585,9 +585,9 @@ trait ExprBuilder extends TransformUtils { if (futureSystemOps.continueCompletedFutureOnSameThread) { val tempAwaitableSym = transformState.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) - val initTempCompleted = Assign(Ident(transformState.applyTrParam), futureSystemOps.getCompleted[Any](Ident(tempAwaitableSym))) - val null_ne = Select(Literal(Constant(null)), TermName("ne")) - val callOnComplete = futureSystemOps.onComplete[Any, Unit](Ident(tempAwaitableSym), fun, Ident(nme.execContext)) + 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 ifTree = If(Apply(null_ne, Ident(transformState.applyTrParam) :: Nil), Apply(Ident(currentTransformState.whileLabel), Nil), diff --git a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala index f8ebf83a3afd..c38f3ec44c9d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala +++ b/src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala @@ -145,7 +145,10 @@ object ScalaConcurrentFutureSystem extends FutureSystem { def mkAttributedSelectApplyIfNeeded(qual: Tree, sym: Symbol) = { val sel = gen.mkAttributedSelect(qual, sym) - if (isPastErasure) Apply(sel, Nil) else sel + 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]] = { From 33ca19f144c069cad18351f1a5ec5191626cea78 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 15 Mar 2020 21:59:55 +1000 Subject: [PATCH 57/94] Inline case class Awaitable --- .../nsc/transform/async/ExprBuilder.scala | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 2d9869eb36fd..6923aea24ddc 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -188,11 +188,11 @@ trait ExprBuilder extends TransformUtils { // always in statement position. // // Spawn a new state and transition to asynchronously it with `UpdateAndAwait` (ie an onComplete call) - val awaitable = Awaitable(arg.changeOwner(vd.symbol, vd.symbol.owner), stat.symbol, tpt.tpe, vd) val afterAwaitState = stateAssigner.nextState() - buildStateAndOpenNextState(afterAwaitState, style = StateTransitionStyle.UpdateAndAwait(awaitable)) + val transition = StateTransitionStyle.UpdateAndAwait(arg.changeOwner(vd.symbol, vd.symbol.owner)) + buildStateAndOpenNextState(afterAwaitState, style = transition) - stateBuilder.stats ++= resumeTree(awaitable) + stateBuilder.stats ++= resumeTree(vd) case If(cond, thenp, elsep) if containsAwait(stat) => // Emit a modified `If` in this state with each branch incorprating the @@ -512,15 +512,13 @@ trait ExprBuilder extends TransformUtils { } } - private case class Awaitable(expr: Tree, resultName: Symbol, resultType: Type, resultValDef: ValDef) - // Resume execution by extracting the successful value and assigining it to the `awaitable.resultValDef` - private def resumeTree(awaitable: Awaitable): List[Tree] = { + private def resumeTree(awaitableResult: ValDef): List[Tree] = { val futureSystemOps = currentTransformState.ops def tryyReference = gen.mkAttributedIdent(currentTransformState.applyTrParam) - val assignTryGet = Assign(gen.mkAttributedIdent(awaitable.resultValDef.symbol), futureSystemOps.tryyGet[Any](tryyReference)) + val assignTryGet = Assign(gen.mkAttributedIdent(awaitableResult.symbol), futureSystemOps.tryyGet[Any](tryyReference)) - val vd = deriveValDef(awaitable.resultValDef)(_ => gen.mkZero(awaitable.resultValDef.symbol.info)) + val vd = deriveValDef(awaitableResult)(_ => gen.mkZero(awaitableResult.symbol.info)) vd.symbol.setFlag(Flag.MUTABLE) val assignOrReturn = if (currentTransformState.futureSystem.emitTryCatch) { If(futureSystemOps.tryyIsFailure(tryyReference), @@ -571,7 +569,7 @@ trait ExprBuilder extends TransformUtils { } } /** Update the state variable and the await completion of `awaitble.expr`. */ - case class UpdateAndAwait(awaitable: Awaitable) extends StateTransitionStyle { + case class UpdateAndAwait(awaitable: Tree) extends StateTransitionStyle { def trees(nextState: Int, stateSet: StateSet): List[Tree] = { stateSet += nextState @@ -583,8 +581,8 @@ trait ExprBuilder extends TransformUtils { val fun = This(tpnme.EMPTY) val transformState = currentTransformState if (futureSystemOps.continueCompletedFutureOnSameThread) { - val tempAwaitableSym = transformState.applyTrParam.owner.newTermSymbol(nme.awaitable).setInfo(awaitable.expr.tpe) - val initAwaitableTemp = ValDef(tempAwaitableSym, awaitable.expr) + 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)) @@ -594,7 +592,7 @@ trait ExprBuilder extends TransformUtils { Block(toStats(callOnComplete), Return(literalUnit).setSymbol(currentTransformState.applySym))) typed(initAwaitableTemp) :: typed(initTempCompleted) :: mkStateTree(nextState) :: typed(ifTree) :: Nil } else { - val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable.expr, fun, Ident(nme.execContext)) + val callOnComplete = futureSystemOps.onComplete[Any, Unit](awaitable, fun, Ident(nme.execContext)) mkStateTree(nextState) :: toStats(typed(callOnComplete)) ::: typed(Return(literalUnit)) :: Nil } } From c5bc8671618b7681c6392fe1ff9238189338bdf0 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 19 Mar 2020 07:58:41 +1000 Subject: [PATCH 58/94] StateSet: Allocate caseSet lazily --- .../tools/nsc/transform/async/StateSet.scala | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala index ea1bbc903628..67a46a4ea637 100644 --- a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala +++ b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala @@ -20,25 +20,32 @@ import scala.collection.JavaConverters.{asScalaIteratorConverter, iterableAsScal // Set for StateIds, which are either small positive integers or -symbolID. final class StateSet { private val bitSet = new java.util.BitSet() - private val caseSet = new util.HashSet[Integer]() + private var _caseSet: util.HashSet[Integer] = null + private def caseSet: util.HashSet[Integer] = { + if (_caseSet == null) _caseSet = new util.HashSet[Integer]() + _caseSet + } + private def useBitSet(i: Int) = i > 0 && i < 1024 def +=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.set(stateId) else caseSet.add(stateId) def -=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.clear(stateId) else caseSet.remove(stateId) - def contains(stateId: Int): Boolean = if (useBitSet(stateId)) bitSet.get(stateId) else caseSet.contains(stateId) - def isEmpty = bitSet.isEmpty && caseSet.isEmpty + def contains(stateId: Int): Boolean = if (useBitSet(stateId)) bitSet.get(stateId) else (_caseSet != null && _caseSet.contains(stateId)) + def isEmpty = bitSet.isEmpty && (_caseSet == null || caseSet.isEmpty) def iterator: Iterator[Integer] = { - bitSet.stream().iterator().asScala ++ caseSet.asScala.iterator + bitSet.stream().iterator().asScala ++ (if (_caseSet == null) Nil else _caseSet.asScala.iterator) } def toArray: Array[Int] = { - val result = new Array[Int](bitSet.cardinality() + caseSet.size()) + val result = new Array[Int](bitSet.cardinality() + (if (_caseSet == null) 0 else caseSet.size())) var i = 0 foreach(value => {result(i) = value; i += 1 }) result } def foreach(f: IntConsumer): Unit = { bitSet.stream().forEach(f) - caseSet.stream().forEach(new Consumer[Integer] { - override def accept(value: Integer): Unit = f.accept(value) - }) + if (_caseSet != null) { + caseSet.stream().forEach(new Consumer[Integer] { + override def accept(value: Integer): Unit = f.accept(value) + }) + } } } From 2631d47515d0fe1990729f1419eb02f2e5fc0f29 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 19 Mar 2020 07:32:16 +1000 Subject: [PATCH 59/94] Be explicit about lambdaLift following the async phase. --- src/compiler/scala/tools/nsc/Global.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 8de915e918c5..65202dd475e0 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -579,7 +579,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) // phaseName = "lambdalift" object lambdaLift extends { val global: Global.this.type = Global.this - val runsAfter = List("posterasure") + val runsAfter = List("async") val runsRightAfter = None } with LambdaLift From 11733cbe1dbc294fd0f3f34ecadd7598fff8bb5f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 16 Mar 2020 17:45:20 +1000 Subject: [PATCH 60/94] Overhaul integration with front end macros - The state machine generated by the front end macro must Now implement a small set of methods as a facade over the particular Future/Awaitable/Task type being used. This replaces the AST factory methods in `FutureSystem` - Remove intrinsic implemnentation for scala-async's async macro. Show what it will need to do in a test implementation, `s.t.n.partest.Async` and update all test cases to use this. - Add a heuristic to interpret `@compileTimeOnly` annotated methods to register `await` methods. Defer the check in refchecks for all of these and instead check that no references survive the async phase. - Refactor the test implementations of async front ends to share an interface for the state machine. We don't ship this but it could be copy/pasted into third party integrations. - Add a test integration with Java's CompletableFuture. - Expose a method through macro `Internals` API to let front ends mark a method in the state machine for async translation. --- build.sbt | 4 + project/ScalaOptionParser.scala | 2 +- .../reflect/macros/contexts/Internals.scala | 5 +- src/compiler/scala/tools/nsc/Global.scala | 9 +- .../tools/nsc/settings/ScalaSettings.scala | 1 + .../nsc/transform/async/AnfTransform.scala | 2 - .../transform/async/AsyncEarlyExpansion.scala | 131 ---------- .../nsc/transform/async/AsyncPhase.scala | 94 ++++--- .../transform/async/AsyncTransformState.scala | 44 ---- .../async/AsyncTransformStates.scala | 57 +++++ .../nsc/transform/async/ExprBuilder.scala | 97 ++++---- .../nsc/transform/async/TransformUtils.scala | 7 +- .../transform/async/user/FutureSystem.scala | 183 -------------- .../tools/nsc/typechecker/RefChecks.scala | 17 +- .../scala/tools/reflect/FastTrack.scala | 3 +- src/library/scala/async/Async.scala | 63 ----- .../scala/tools/partest/async/Async.scala | 80 ++++++ .../partest/async/AsyncStateMachine.scala | 35 +++ src/reflect/scala/reflect/api/Internals.scala | 2 + .../scala/reflect/internal/Definitions.scala | 16 +- .../scala/reflect/internal/Phase.scala | 1 + .../internal/settings/MutableSettings.scala | 1 + .../reflect/runtime/JavaUniverseForce.scala | 6 + .../scala/reflect/runtime/Settings.scala | 1 + test/async/neg/naked_await.check | 7 + test/async/neg/naked_await.flags | 1 + test/async/neg/naked_await.scala | 12 + test/async/neg/stark_naked_await.check | 4 + test/async/neg/stark_naked_await.flags | 1 + test/async/neg/stark_naked_await.scala | 8 + test/async/run/anf.flags | 1 + test/async/run/anf.scala | 4 +- test/async/run/await0.flags | 1 + test/async/run/await0.scala | 2 +- test/async/run/block0.flags | 1 + test/async/run/block0.scala | 2 +- test/async/run/block1.flags | 1 + test/async/run/block1.scala | 2 +- .../run/concurrent_AfterRefchecksIssue.flags | 1 + .../run/concurrent_AfterRefchecksIssue.scala | 2 +- ...concurrent_ArrayIndexOutOfBoundIssue.flags | 1 + ...concurrent_ArrayIndexOutOfBoundIssue.scala | 2 +- .../concurrent_GenericTypeBoundaryIssue.flags | 1 + .../concurrent_GenericTypeBoundaryIssue.scala | 2 +- test/async/run/concurrent_MatchEndIssue.flags | 1 + test/async/run/concurrent_MatchEndIssue.scala | 2 +- ...oncurrent_NegativeArraySizeException.flags | 1 + ...oncurrent_NegativeArraySizeException.scala | 2 +- ...rent_NegativeArraySizeExceptionFine1.flags | 1 + ...rent_NegativeArraySizeExceptionFine1.scala | 2 +- .../run/concurrent_ReturnTupleIssue.flags | 1 + .../run/concurrent_ReturnTupleIssue.scala | 2 +- test/async/run/concurrent_fetch.flags | 1 + test/async/run/concurrent_fetch.scala | 2 +- .../run/concurrent_patternAlternative.flags | 1 + .../run/concurrent_patternAlternative.scala | 2 +- ...nt_patternAlternativeBothAnnotations.flags | 1 + ...nt_patternAlternativeBothAnnotations.scala | 2 +- .../run/concurrent_polymorphicMethod.flags | 1 + .../run/concurrent_polymorphicMethod.scala | 2 +- test/async/run/concurrent_shadowing.flags | 1 + test/async/run/concurrent_shadowing.scala | 2 +- test/async/run/concurrent_shadowing0.flags | 1 + test/async/run/concurrent_shadowing0.scala | 2 +- test/async/run/concurrent_shadowing2.flags | 1 + test/async/run/concurrent_shadowing2.scala | 2 +- .../concurrent_shadowingRefinedTypes.flags | 1 + .../concurrent_shadowingRefinedTypes.scala | 2 +- test/async/run/concurrent_test0.flags | 1 + test/async/run/concurrent_test0.scala | 2 +- test/async/run/exceptions.flags | 1 + test/async/run/exceptions.scala | 2 +- test/async/run/futures.flags | 1 + test/async/run/futures.scala | 2 +- test/async/run/hygiene.flags | 1 + test/async/run/hygiene.scala | 2 +- test/async/run/ifelse0.flags | 1 + test/async/run/ifelse0.scala | 2 +- test/async/run/ifelse0_while.flags | 1 + test/async/run/ifelse0_while.scala | 2 +- test/async/run/ifelse1.flags | 1 + test/async/run/ifelse1.scala | 2 +- test/async/run/ifelse2.flags | 1 + test/async/run/ifelse2.scala | 2 +- test/async/run/ifelse3.flags | 1 + test/async/run/ifelse3.scala | 2 +- test/async/run/ifelse4.flags | 1 + test/async/run/ifelse4.scala | 2 +- test/async/run/lazyval.flags | 1 + test/async/run/lazyval.scala | 2 +- test/async/run/live.flags | 1 + test/async/run/live.scala | 2 +- test/async/run/localclasses.flags | 1 + test/async/run/localclasses.scala | 2 +- test/async/run/match0.flags | 1 + test/async/run/match0.scala | 2 +- test/async/run/nesteddef.flags | 1 + test/async/run/nesteddef.scala | 2 +- test/async/run/noawait.flags | 1 + test/async/run/noawait.scala | 2 +- test/async/run/stackoverflow.flags | 1 + test/async/run/stackoverflow.scala | 2 +- test/async/run/syncOptimization.flags | 1 + test/async/run/syncOptimization.scala | 2 +- test/async/run/toughtype.flags | 1 + test/async/run/toughtype.scala | 10 +- .../nsc/async/AnnotationDrivenAsync.scala | 231 ++++++++++-------- .../nsc/async/CompletableFutureAwait.scala | 72 ++++++ 108 files changed, 649 insertions(+), 672 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncEarlyExpansion.scala delete mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncTransformState.scala create mode 100644 src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala delete mode 100644 src/compiler/scala/tools/nsc/transform/async/user/FutureSystem.scala delete mode 100644 src/library/scala/async/Async.scala create mode 100644 src/partest-extras/scala/tools/partest/async/Async.scala create mode 100644 src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala create mode 100644 test/async/neg/naked_await.check create mode 100644 test/async/neg/naked_await.flags create mode 100644 test/async/neg/naked_await.scala create mode 100644 test/async/neg/stark_naked_await.check create mode 100644 test/async/neg/stark_naked_await.flags create mode 100644 test/async/neg/stark_naked_await.scala create mode 100644 test/async/run/anf.flags create mode 100644 test/async/run/await0.flags create mode 100644 test/async/run/block0.flags create mode 100644 test/async/run/block1.flags create mode 100644 test/async/run/concurrent_AfterRefchecksIssue.flags create mode 100644 test/async/run/concurrent_ArrayIndexOutOfBoundIssue.flags create mode 100644 test/async/run/concurrent_GenericTypeBoundaryIssue.flags create mode 100644 test/async/run/concurrent_MatchEndIssue.flags create mode 100644 test/async/run/concurrent_NegativeArraySizeException.flags create mode 100644 test/async/run/concurrent_NegativeArraySizeExceptionFine1.flags create mode 100644 test/async/run/concurrent_ReturnTupleIssue.flags create mode 100644 test/async/run/concurrent_fetch.flags create mode 100644 test/async/run/concurrent_patternAlternative.flags create mode 100644 test/async/run/concurrent_patternAlternativeBothAnnotations.flags create mode 100644 test/async/run/concurrent_polymorphicMethod.flags create mode 100644 test/async/run/concurrent_shadowing.flags create mode 100644 test/async/run/concurrent_shadowing0.flags create mode 100644 test/async/run/concurrent_shadowing2.flags create mode 100644 test/async/run/concurrent_shadowingRefinedTypes.flags create mode 100644 test/async/run/concurrent_test0.flags create mode 100644 test/async/run/exceptions.flags create mode 100644 test/async/run/futures.flags create mode 100644 test/async/run/hygiene.flags create mode 100644 test/async/run/ifelse0.flags create mode 100644 test/async/run/ifelse0_while.flags create mode 100644 test/async/run/ifelse1.flags create mode 100644 test/async/run/ifelse2.flags create mode 100644 test/async/run/ifelse3.flags create mode 100644 test/async/run/ifelse4.flags create mode 100644 test/async/run/lazyval.flags create mode 100644 test/async/run/live.flags create mode 100644 test/async/run/localclasses.flags create mode 100644 test/async/run/match0.flags create mode 100644 test/async/run/nesteddef.flags create mode 100644 test/async/run/noawait.flags create mode 100644 test/async/run/stackoverflow.flags create mode 100644 test/async/run/syncOptimization.flags create mode 100644 test/async/run/toughtype.flags create mode 100644 test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala diff --git a/build.sbt b/build.sbt index 7e25505470e3..ce26f5e137f6 100644 --- a/build.sbt +++ b/build.sbt @@ -466,6 +466,10 @@ val mimaFilterSettings = Seq { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.removeElement"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.addElement"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.macros.Attachments.containsElement"), + + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.runtime.Settings.async"), + + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.reflect.api.Internals#InternalApi.markForAsyncTransform"), ) } 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 65202dd475e0..74b96d2f09c1 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 @@ -1007,7 +1013,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 @@ -1344,6 +1350,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 b23be84b803e..ff9bf247c10e 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..c6ce1dafbf62 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -13,56 +13,85 @@ 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.util.SourceFile 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 +103,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 +111,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 +121,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 +131,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 +149,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 +174,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 0431fc64791f..57f7fc83c7da 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1285,12 +1285,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) + } } } @@ -1719,7 +1722,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 f50df3f5fb39..4964a4e579fe 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 @@ -1556,6 +1563,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 /** @@ -1637,9 +1646,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/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index fb87309621b1..93e8bfd0839b 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -356,6 +356,12 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.TupleClass definitions.FunctionClass definitions.AbstractFunctionClass + definitions.TryClass + definitions.FailureClass + definitions.SuccessClass + definitions.FutureClass + definitions.PromiseClass + definitions.NonFatalClass definitions.MacroContextType definitions.ProductRootClass definitions.Any_$eq$eq 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/CompletableFutureAwait.scala b/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala new file mode 100644 index 000000000000..13cb807fb712 --- /dev/null +++ b/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala @@ -0,0 +1,72 @@ +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 + } +} From 791c01539c15b2b99d18874f390f2c19f9b15c48 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 21 Mar 2020 13:18:04 +1000 Subject: [PATCH 61/94] Don't insert null assignments for the terminal state. --- src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index c6ce1dafbf62..ac29b73584f1 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -166,7 +166,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran if (nullOut) { for ((state, flds) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { val asyncState = asyncBlock.asyncStates.find(_.state == state).get - asyncState.insertNullAssignments(flds.iterator) + if (asyncState.nextStates.nonEmpty) + asyncState.insertNullAssignments(flds.iterator) } } From 36ad222443c9bc0e593e490acb727b4d461958e1 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 21 Mar 2020 13:38:49 +1000 Subject: [PATCH 62/94] Remove dead code or make things more private. --- .../tools/nsc/transform/async/AsyncNames.scala | 12 ------------ .../transform/async/AsyncTransformStates.scala | 3 --- .../nsc/transform/async/ExprBuilder.scala | 12 ++++-------- .../nsc/transform/async/LiveVariables.scala | 18 +++--------------- .../tools/nsc/transform/async/StateSet.scala | 2 -- .../nsc/transform/async/TransformUtils.scala | 7 +------ 6 files changed, 8 insertions(+), 46 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala index 140abdbda884..cb30576914c1 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -41,9 +41,6 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { final class TermNameCache(base: String) extends NameCache[U#TermName](base) { override protected def newName(s: String): U#TermName = TermName(s) } - final class TypeNameCache(base: String) extends NameCache[U#TypeName](base) { - override protected def newName(s: String): U#TypeName = TypeName(s) - } private val matchRes: TermNameCache = new TermNameCache("match") private val ifRes: TermNameCache = new TermNameCache("if") private val await: TermNameCache = new TermNameCache("await") @@ -71,15 +68,6 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { freshen(name, counter) } } - final def freshenIfNeeded(name: TypeName): TypeName = { - seenPrefixes.getOrNull(name) match { - case null => - seenPrefixes.put(name, new AtomicInteger()) - name - case counter => - freshen(name, counter) - } - } final def freshen(name: TermName): TermName = { val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) freshen(name, counter) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala index 1721f5df1d9c..a3956aa22eef 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala @@ -47,11 +47,8 @@ trait AsyncTransformStates extends TypingTransformers { 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 ed40de66f0ff..5a5c79dd0aab 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -87,7 +87,6 @@ trait ExprBuilder extends TransformUtils { } } - def ++=(stats: List[Tree]): this.type = {stats.foreach(+=(_)); this} def +=(stat: Tree): this.type = { stats ++= jumpReplacer.atOwner(currentTransformState.localTyper.context.owner) { jumpReplacer.apply(stat) @@ -348,9 +347,6 @@ trait ExprBuilder extends TransformUtils { def toDot: String } - private lazy val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal") - private lazy val ThrowableClass = rootMirror.staticClass("java.lang.Throwable") - /** * Uses `AsyncBlockBuilder` to create an instance of `AsyncBlock`. * @@ -365,9 +361,9 @@ trait ExprBuilder extends TransformUtils { val blockBuilder = new AsyncBlockBuilder(stats, expr, startState, endState, startToEndUpdateStyle = StateTransitionStyle.Update) new AsyncBlock { - val switchIds = mutable.AnyRefMap[Integer, Integer]() - val emptyReplacements = mutable.AnyRefMap[Integer, Integer]() - def switchIdOf(state: Integer) = switchIds(emptyReplacements.getOrElse(state, state)) + private val switchIds = mutable.AnyRefMap[Integer, Integer]() + private val emptyReplacements = mutable.AnyRefMap[Integer, Integer]() + private def switchIdOf(state: Integer) = switchIds(emptyReplacements.getOrElse(state, state)) // render with http://graphviz.it/#/new def toDot: String = { @@ -435,7 +431,7 @@ trait ExprBuilder extends TransformUtils { body1, List( CaseDef( - Bind(nme.t, Typed(Ident(nme.WILDCARD), Ident(ThrowableClass))), + Bind(nme.t, Typed(Ident(nme.WILDCARD), Ident(definitions.ThrowableClass))), EmptyTree, Block(Apply(currentTransformState.memberRef(currentTransformState.stateCompleteFailure), Ident(nme.t) :: Nil) :: Nil, Return(literalUnit)) ) diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 8d0d973f4249..9311f0b96d24 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -101,19 +101,7 @@ trait LiveVariables extends ExprBuilder { override def toString = s"used: ${used.mkString(",")}\ncaptured: ${captured.mkString(",")}" } - /* Build the control-flow graph. - * - * A state `i` is contained in the list that is the value to which - * key `j` maps iff control can flow from state `j` to state `i`. - */ - val cfg: Map[Int, Array[Int]] = { - var res = IntMap.empty[Array[Int]] - - for (as <- asyncStates) res = res.updated(as.state, as.nextStates) - res - } - - if(settings.debug.value && shouldLogAtThisPhase) { + if (settings.debug.value && shouldLogAtThisPhase) { for (as <- asyncStates) debuglog(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") } @@ -215,12 +203,12 @@ trait LiveVariables extends ExprBuilder { val lastUsages: mutable.LinkedHashMap[Symbol, StateSet] = mutable.LinkedHashMap(liftables.map(fld => fld.symbol -> lastUsagesOf(fld, finalState)): _*) - if(settings.debug.value && shouldLogAtThisPhase) { + if (settings.debug.value && shouldLogAtThisPhase) { for ((fld, lastStates) <- lastUsages) debuglog(s"field ${fld.name} is last used in states ${lastStates.iterator.mkString(", ")}") } - if(settings.debug.value && shouldLogAtThisPhase) { + if (settings.debug.value && shouldLogAtThisPhase) { for ((fld, killAt) <- lastUsages) debuglog(s"field ${fld.name} should be nulled out at the conclusion of states ${killAt.iterator.mkString(", ")}") } diff --git a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala index 67a46a4ea637..1dd8c709351e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala +++ b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala @@ -29,8 +29,6 @@ final class StateSet { private def useBitSet(i: Int) = i > 0 && i < 1024 def +=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.set(stateId) else caseSet.add(stateId) def -=(stateId: Int): Unit = if (useBitSet(stateId)) bitSet.clear(stateId) else caseSet.remove(stateId) - def contains(stateId: Int): Boolean = if (useBitSet(stateId)) bitSet.get(stateId) else (_caseSet != null && _caseSet.contains(stateId)) - def isEmpty = bitSet.isEmpty && (_caseSet == null || caseSet.isEmpty) def iterator: Iterator[Integer] = { bitSet.stream().iterator().asScala ++ (if (_caseSet == null) Nil else _caseSet.asScala.iterator) } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index f00e8290d999..cefdd1659771 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -25,16 +25,11 @@ private[async] trait TransformUtils extends AsyncTransformStates { private[async] val asyncNames: AsyncNames[global.type] object name extends asyncNames.AsyncName { def fresh(name: TermName): TermName = freshenIfNeeded(name) - def fresh(name: String): String = currentFreshNameCreator.newName(name) // TODO ok? was c.freshName } def typedPos(pos: Position)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos)(tree: Tree) - def typedPos(pos: Position, mode: Mode, pt: Type)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos, mode, pt)(tree) def typed(tree: Tree): Tree = typedPos(currentTransformState.applySym.pos)(tree) - def maybeTry(emitTryCatch: Boolean)(block: Tree, catches: List[CaseDef], finalizer: Tree): Tree = - if (emitTryCatch) Try(block, catches, finalizer) else block - lazy val IllegalStateExceptionClass: Symbol = rootMirror.staticClass("java.lang.IllegalStateException") lazy val IllegalStateExceptionClass_NEW_String: Symbol = IllegalStateExceptionClass.info.decl(nme.CONSTRUCTOR).suchThat( x => x.paramss.head.size == 1 && x.firstParam.info.typeSymbol == definitions.StringClass) @@ -248,7 +243,7 @@ private[async] trait TransformUtils extends AsyncTransformStates { t.setAttachments(t.attachments.addElement(NoAwait)) } - var stack = mutable.ArrayStack[Tree]() + val stack = mutable.ArrayStack[Tree]() override def traverse(tree: Tree): Unit = { stack.push(tree) From d7a27646fd4861b07061d3b7010a1edb251d9cb9 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 23 Mar 2020 11:13:12 +1000 Subject: [PATCH 63/94] Local TypeDef no longer make it to the async phase. --- .../scala/tools/nsc/transform/async/Lifter.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index 558d024aac6d..e767d13b55a5 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -33,7 +33,7 @@ trait Lifter extends ExprBuilder { for { cd <- classes md <- moduleClasses - if (cd.name == md.name) + if cd.name == md.name } { companions(cd) = md companions(md) = cd @@ -52,8 +52,10 @@ trait Lifter extends ExprBuilder { private val classesBuffer, moduleClassesBuffer = mutable.ArrayBuffer[Symbol]() override def traverse(tree: Tree): Unit = tree match { case _: LabelDef => super.traverse(tree) + case _: TypeDef => + abort("Unexpected tree. TypeDefs should have been eliminated after erasure" + tree) case _: DefTree => childDefs += tree - case _: Function => Nil + case _: Function => case Block(stats, expr) => classesBuffer.clear() moduleClassesBuffer.clear() @@ -180,9 +182,6 @@ trait Lifter extends ExprBuilder { } treeCopy.ClassDef(cd, Modifiers(sym.flags), sym.name, tparams, impl) } - case td@TypeDef(_, _, tparams, rhs) => - sym.setName(name.freshen(sym.name.toTypeName)) - treeCopy.TypeDef(td, Modifiers(sym.flags), sym.name, tparams, rhs) } atPos(t.pos)(treeLifted) }.toList From 36d452ec71491a2fd32b99cc6fc1d7e322ba1835 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 23 Mar 2020 11:17:33 +1000 Subject: [PATCH 64/94] Allow compilation of async's partest suite in Junit This lets us use IntelliJ's code coverage analysis. --- .../nsc/async/AnnotationDrivenAsync.scala | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 05d4cbdbde69..17aae5e22809 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -442,13 +442,19 @@ class AnnotationDrivenAsync { assertEquals(100, run(code)) } - - - // Handy to debug the compiler - @Test @Ignore + // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. + @Test + @Ignore def testManualRunPartestUnderJUnit(): Unit = { - val code = new String(Files.readAllBytes(Paths.get("../async/run/concurrent_ArrayIndexOutOfBoundIssue.scala"))) - assertEquals(("a", "b"), run(code)) + import scala.collection.JavaConverters._ + for (path <- List(Paths.get("../async/run"), Paths.get("../async/neg"))) { + for (file <- Files.list(path).iterator.asScala) { + if (file.getFileName.toString.endsWith(".scala")) { + val code = new String(Files.readAllBytes(file)) + run(code, compileOnly = true) + } + } + } } private def createTempDir(): File = { @@ -458,7 +464,7 @@ class AnnotationDrivenAsync { f } - def run(code: String): Any = { + def run(code: String, compileOnly: Boolean = false): Any = { val out = createTempDir() try { val reporter = new StoreReporter { @@ -495,11 +501,18 @@ class AnnotationDrivenAsync { val run = new Run val source = newSourceFile(code) run.compileSources(source :: Nil) - Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasWarnings) + if (compileOnly) return null Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors) + Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasWarnings) val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader) val cls = loader.loadClass("Test") - cls.getMethod("test").invoke(null) match { + val result = try { + cls.getMethod("test").invoke(null) + } catch { + case _: NoSuchMethodException => + cls.getMethod("main", classOf[Array[String]]).invoke(null, null) + } + result match { case t: scala.concurrent.Future[_] => scala.concurrent.Await.result(t, Duration.Inf) case cf: CustomFuture[_] => From dd463d11a989d719e6f821517853e9dcf7b00c51 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 23 Mar 2020 11:43:14 +1000 Subject: [PATCH 65/94] Test and simplify ANF transform of Nothing-typed if/match --- .../nsc/transform/async/AnfTransform.scala | 14 +++----------- .../nsc/async/AnnotationDrivenAsync.scala | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 5921d2d26fa5..511057c47589 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -176,18 +176,10 @@ private[async] trait AnfTransform extends TransformUtils { } } - @tailrec private def transformMatchOrIf[T <: Tree](tree: Tree, needsResultVar: Boolean, nameSource: asyncNames.NameSource[TermName])(core: Symbol => T): Tree = { - // if type of if/match is Unit don't introduce assignment, - // but add Unit value to bring it into form expected by async transform - if (isUnitType(tree.tpe)) { - assignUnitType(core(NoSymbol)) - } else if (tree.tpe =:= definitions.NothingTpe) { - currentStats += assignUnitType(core(NoSymbol)) - localTyper.typedPos(tree.pos)(Throw(New(IllegalStateExceptionClass))) - } else if (isPatMatGeneratedJump(tree)) { - transformMatchOrIf(assignUnitType(tree), needsResultVar, nameSource)(core) - } else if (!needsResultVar) { + if (isPatMatGeneratedJump(tree)) assignUnitType(tree) + + if (!needsResultVar || isUnitType(tree.tpe) || (tree.tpe =:= definitions.NothingTpe)) { core(NoSymbol) } else { val varDef = defineVar(nameSource(), tree.tpe, tree.pos) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 17aae5e22809..946ca68f9e79 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -442,6 +442,24 @@ class AnnotationDrivenAsync { assertEquals(100, run(code)) } + @Test + def testNothingTypedExpr(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test: Future[Throwable] = async { if ("".isEmpty) {await(f("")); throw new RuntimeException("boo!")} else ??? }.failed + |} + |""".stripMargin + run(code) match { + case re: RuntimeException => assert(re.getMessage == "boo!") + case _ => Assert.fail() + } + } + // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. @Test @Ignore From de4fc23c08a3ac7fcf20aaa720a406771f19702e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 23 Mar 2020 16:08:27 +1000 Subject: [PATCH 66/94] Fix detection of ill-nested awaits - Adapt detection logic to the post-erasure tree shapes - Lift restriction about || and && in favour of a rewrite to `If` in the ANF transform - import relevant parts of the test from scala-async. --- .../scala/tools/nsc/transform/UnCurry.scala | 5 +- .../nsc/transform/async/AnfTransform.scala | 8 ++- .../nsc/transform/async/AsyncAnalysis.scala | 21 +++--- .../nsc/transform/async/LiveVariables.scala | 5 +- .../nsc/transform/async/TransformUtils.scala | 23 +++--- .../scala/reflect/internal/StdNames.scala | 2 + test/async/neg/ill-nested-await.check | 37 ++++++++++ test/async/neg/ill-nested-await.flags | 1 + test/async/neg/ill-nested-await.scala | 71 +++++++++++++++++++ .../nsc/async/AnnotationDrivenAsync.scala | 26 +++++++ 10 files changed, 165 insertions(+), 34 deletions(-) create mode 100644 test/async/neg/ill-nested-await.check create mode 100644 test/async/neg/ill-nested-await.flags create mode 100644 test/async/neg/ill-nested-await.scala diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index cc93fe3f1a59..b224c35ed28f 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -15,13 +15,12 @@ package tools.nsc package transform import scala.annotation.tailrec - import symtab.Flags._ import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.reflect.internal.util.ListOfNil - import PartialFunction.cond +import scala.reflect.NameTransformer /* */ /** - uncurry all symbol and tree types (@see UnCurryPhase) -- this includes normalizing all proper types. @@ -149,7 +148,7 @@ abstract class UnCurry extends InfoTransform /** Return non-local return key for given method */ private def nonLocalReturnKey(meth: Symbol) = nonLocalReturnKeys.getOrElseUpdate(meth, - meth.newValue(unit.freshTermName("nonLocalReturnKey"), meth.pos, SYNTHETIC) setInfo ObjectTpe + meth.newValue(unit.freshTermName(nme.NON_LOCAL_RETURN_KEY_STRING), meth.pos, SYNTHETIC) setInfo ObjectTpe ) /** Generate a non-local return throw with given return expression from given method. diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 511057c47589..69ac07cc3387 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -52,11 +52,13 @@ private[async] trait AnfTransform extends TransformUtils { tree match { case _: ClassDef | _: ModuleDef | _: Function | _: DefDef => tree - case _: RefTree if tree.symbol.hasPackageFlag => - tree case _ if !treeContainsAwait => tree - case Apply(fun, args) if !isBooleanShortCircuit(fun.symbol) => + case Apply(sel @ Select(fun, _), arg :: Nil) if isBooleanAnd(sel.symbol) && containsAwait(arg) => + transform(treeCopy.If(tree, fun, arg, literalBool(false))) + case Apply(sel @ Select(fun, _), arg :: Nil) if isBooleanOr(sel.symbol) && containsAwait(arg) => + transform(treeCopy.If(tree, fun, literalBool(true), arg)) + case Apply(fun, args) => val lastAwaitArgIndex: Int = args.lastIndexWhere(containsAwait) val simpleFun = transform(fun) var i = 0 diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index 4cfbe4958069..d593876380a5 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -13,7 +13,7 @@ package scala.tools.nsc.transform.async import scala.collection.mutable.ListBuffer -import scala.reflect.internal.Flags +import scala.reflect.NameTransformer trait AsyncAnalysis extends TransformUtils { import global._ @@ -35,8 +35,8 @@ trait AsyncAnalysis extends TransformUtils { reportUnsupportedAwait(classDef, s"nested $kind") } - override def nestedModule(module: ModuleDef): Unit = { - reportUnsupportedAwait(module, "nested object") + override def nestedModuleClass(moduleClass: ClassDef): Unit = { + reportUnsupportedAwait(moduleClass, "nested object") } override def nestedMethod(defDef: DefDef): Unit = { @@ -50,9 +50,8 @@ trait AsyncAnalysis extends TransformUtils { override def function(function: Function): Unit = { reportUnsupportedAwait(function, "nested function") } - - override def patMatFunction(tree: Match): Unit = { - reportUnsupportedAwait(tree, "nested function") + override def function(expandedFunction: ClassDef): Unit = { + reportUnsupportedAwait(expandedFunction, "nested function") } override def traverse(tree: Tree): Unit = { @@ -60,14 +59,10 @@ trait AsyncAnalysis extends TransformUtils { case Try(_, _, _) if containsAwait(tree) => reportUnsupportedAwait(tree, "try/catch") super.traverse(tree) - case Return(_) => + case Throw(Apply(fun, Ident(name) :: _)) if fun.symbol.isConstructor && fun.symbol.owner == definitions.NonLocalReturnControlClass && name.startsWith(nme.NON_LOCAL_RETURN_KEY_STRING) => global.reporter.error(tree.pos, "return is illegal within a async block") - case DefDef(mods, _, _, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => - reportUnsupportedAwait(tree, "lazy val initializer") - case ValDef(mods, _, _, _) if mods.hasFlag(Flags.LAZY) && containsAwait(tree) => + case DefDef(mods, _, _, _, _, _) if tree.symbol.name.endsWith(nme.LAZY_SLOW_SUFFIX) && containsAwait(tree) => reportUnsupportedAwait(tree, "lazy val initializer") - case CaseDef(_, guard, _) if guard exists isAwait => - reportUnsupportedAwait(tree, "pattern guard") case _ => super.traverse(tree) } @@ -91,7 +86,7 @@ trait AsyncAnalysis extends TransformUtils { traverser(tree) badAwaits foreach { tree => - reportError(tree.pos, s"await must not be used under a $whyUnsupported.") + reportError(tree.pos, s"${currentTransformState.Async_await.decodedName} must not be used under a $whyUnsupported.") } badAwaits.nonEmpty } diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 9311f0b96d24..0a2fb1d0bd97 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -82,15 +82,14 @@ trait LiveVariables extends ExprBuilder { override def nestedClass(classDef: ClassDef): Unit = capturingCheck(classDef) - override def nestedModule(module: ModuleDef): Unit = capturingCheck(module) + override def nestedModuleClass(moduleClass: ClassDef): Unit = capturingCheck(moduleClass) override def nestedMethod(defdef: DefDef): Unit = capturingCheck(defdef) override def byNameArgument(arg: Tree): Unit = capturingCheck(arg) override def function(function: Function): Unit = capturingCheck(function) - - override def patMatFunction(tree: Match): Unit = capturingCheck(tree) + override def function(expandedFunction: ClassDef): Unit = capturingCheck(expandedFunction) } val findUses = new FindUseTraverser diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index cefdd1659771..8640ff3515b3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -14,7 +14,6 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer -import scala.reflect.internal.Mode /** * Utilities used in both `ExprBuilder` and `AnfTransform`. @@ -36,8 +35,10 @@ private[async] trait TransformUtils extends AsyncTransformStates { 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) + def isBooleanAnd(sym: Symbol): Boolean = + sym.owner == definitions.BooleanClass && sym == definitions.Boolean_and + def isBooleanOr(sym: Symbol): Boolean = + sym.owner == definitions.BooleanClass && sym == definitions.Boolean_or def isLabel(sym: Symbol): Boolean = sym != null && sym.isLabel def isCaseLabel(sym: Symbol): Boolean = sym != null && sym.isLabel && sym.name.startsWith("case") @@ -67,6 +68,7 @@ private[async] trait TransformUtils extends AsyncTransformStates { def literalUnit: Tree = Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees def literalBoxedUnit: Tree = gen.mkAttributedRef(definitions.BoxedUnit_UNIT) + def literalBool(b: Boolean): Tree = Literal(Constant(b)).setType(definitions.BooleanTpe) def isLiteralUnit(t: Tree): Boolean = t match { case Literal(Constant(())) => true @@ -158,7 +160,7 @@ private[async] trait TransformUtils extends AsyncTransformStates { def nestedClass(classDef: ClassDef): Unit = { } - def nestedModule(module: ModuleDef): Unit = { + def nestedModuleClass(moduleClass: ClassDef): Unit = { } def nestedMethod(defdef: DefDef): Unit = { @@ -170,20 +172,17 @@ private[async] trait TransformUtils extends AsyncTransformStates { def function(function: Function): Unit = { } - def patMatFunction(tree: Match): Unit = { + def function(expandedFunction: ClassDef): Unit = { } override def traverse(tree: Tree): Unit = { tree match { - case cd: ClassDef => nestedClass(cd) - case md: ModuleDef => nestedModule(md) + case cd: ClassDef => + if (cd.symbol.isAnonymousClass) function(cd) + else if (cd.symbol.isModuleClass) nestedModuleClass(cd) + else nestedClass(cd) case dd: DefDef => nestedMethod(dd) case fun: Function => function(fun) - case m@Match(EmptyTree, _) => patMatFunction(m) // Pattern matching anonymous function under -Xoldpatmat of after `restorePatternMatchingFunctions` - case Apply(fun, arg1 :: arg2 :: Nil) if isBooleanShortCircuit(fun.symbol) => - traverse(fun) - traverse(arg1) - byNameArgument(arg2) case _ => super.traverse(tree) } } diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 30117a8fec89..42633d36987c 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -129,6 +129,8 @@ trait StdNames { val NESTED_IN_ANON_FUN: String = NESTED_IN + ANON_FUN_NAME.toString.replace("$", "") val NESTED_IN_LAMBDA: String = NESTED_IN + DELAMBDAFY_LAMBDA_CLASS_NAME.toString.replace("$", "") + val NON_LOCAL_RETURN_KEY_STRING: String = "nonLocalReturnKey" + /** * Ensures that name mangling does not accidentally make a class respond `true` to any of * isAnonymousClass, isAnonymousFunction, isDelambdafyFunction, e.g. by introducing "$anon". diff --git a/test/async/neg/ill-nested-await.check b/test/async/neg/ill-nested-await.check new file mode 100644 index 000000000000..1afd14d57bdd --- /dev/null +++ b/test/async/neg/ill-nested-await.check @@ -0,0 +1,37 @@ +ill-nested-await.scala:15: error: await must not be used under a nested method. + async { foo(0)(await(f(0))) } + ^ +ill-nested-await.scala:20: error: await must not be used under a nested object. + async { object Nested { await(f(false)) } } + ^ +ill-nested-await.scala:25: error: await must not be used under a nested trait. + async { trait Nested { await(f(false)) } } + ^ +ill-nested-await.scala:30: error: await must not be used under a nested class. + async { class Nested { await(f(false)) } } + ^ +ill-nested-await.scala:35: error: await must not be used under a nested method. + async { () => { await(f(false)) } } + ^ +ill-nested-await.scala:40: error: await must not be used under a nested function. + async { { case 0 => { await(f(false)) } } : PartialFunction[Int, Boolean] } + ^ +ill-nested-await.scala:45: error: await must not be used under a try/catch. + async { try { await(f(false)) } catch { case _: Throwable => } } + ^ +ill-nested-await.scala:50: error: await must not be used under a try/catch. + async { try { () } catch { case _: Throwable => await(f(false)) } } + ^ +ill-nested-await.scala:55: error: await must not be used under a try/catch. + async { try { () } finally { await(f(false)) } } + ^ +ill-nested-await.scala:60: error: await must not be used under a nested method. + async { def foo = await(f(false)) } + ^ +ill-nested-await.scala:69: error: await must not be used under a lazy val initializer. + def foo(): Any = async { val x = { lazy val y = await(f(0)); y } } + ^ +ill-nested-await.scala:9: error: `await` must be enclosed in an `async` block + await[Any](f(null)) + ^ +12 errors found diff --git a/test/async/neg/ill-nested-await.flags b/test/async/neg/ill-nested-await.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/neg/ill-nested-await.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/neg/ill-nested-await.scala b/test/async/neg/ill-nested-await.scala new file mode 100644 index 000000000000..7a4aebc5d002 --- /dev/null +++ b/test/async/neg/ill-nested-await.scala @@ -0,0 +1,71 @@ +import scala.concurrent._ +import ExecutionContext.Implicits.global +import scala.tools.partest.async.Async._ +import Future.{successful => f} + + +class NakedAwait { + def `await only allowed in async neg`(): Unit = { + await[Any](f(null)) + } + + def `await not allowed in by-name argument`(): Unit = { + // expectError("await must not be used under a by-name argument.") { + def foo(a: Int)(b: => Int) = 0 + async { foo(0)(await(f(0))) } + } + + def nestedObject(): Unit = { + // expectError("await must not be used under a nested object.") { + async { object Nested { await(f(false)) } } + } + + def nestedTrait(): Unit = { + // expectError("await must not be used under a nested trait.") { + async { trait Nested { await(f(false)) } } + } + + def nestedClass(): Unit = { + // expectError("await must not be used under a nested class.") { + async { class Nested { await(f(false)) } } + } + + def nestedFunction(): Unit = { + // expectError("await must not be used under a nested function.") { + async { () => { await(f(false)) } } + } + + def nestedPatMatFunction(): Unit = { + // expectError("await must not be used under a nested class.") { // TODO more specific error message + async { { case 0 => { await(f(false)) } } : PartialFunction[Int, Boolean] } + } + + def tryBody(): Unit = { + // expectError("await must not be used under a try/catch.") { + async { try { await(f(false)) } catch { case _: Throwable => } } + } + + def catchBody(): Unit = { + // expectError("await must not be used under a try/catch.") { + async { try { () } catch { case _: Throwable => await(f(false)) } } + } + + def finallyBody(): Unit = { + // expectError("await must not be used under a try/catch.") { + async { try { () } finally { await(f(false)) } } + } + + def nestedMethod(): Unit = { + // expectError("await must not be used under a nested method.") { + async { def foo = await(f(false)) } + } + +// def returnIllegal(): Unit = { +// def foo(): Any = async { return false } //!!! +// } + + def lazyValIllegal(): Unit = { + //expectError("await must not be used under a lazy val initializer") + def foo(): Any = async { val x = { lazy val y = await(f(0)); y } } + } +} diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 946ca68f9e79..c128b8953dbb 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -34,6 +34,32 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def testBooleanAndOr(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | var counter = 0 + | def ordered(i: Int, b: Boolean): Boolean = { assert(counter == i, (counter, i)); counter += 1; b } + | def test: Future[Any] = async { + | counter = 0; assert(!(ordered(0, false) && await(f(ordered(-1, true))))) + | counter = 0; assert(!(ordered(0, false) && await(f(ordered(-1, false))))) + | counter = 0; assert( (ordered(0, true) && await(f(ordered( 1, true))))) + | counter = 0; assert(!(ordered(0, true) && await(f(ordered( 1, false))))) + | counter = 0; assert( (ordered(0, false) || await(f(ordered( 1, true))))) + | counter = 0; assert(!(ordered(0, false) || await(f(ordered( 1, false))))) + | counter = 0; assert( (ordered(0, true) || await(f(ordered(-1, false))))) + | counter = 0; assert( (ordered(0, true) || await(f(ordered(-1, true))))) + | () + | } + |} + |""".stripMargin + assertEquals((), run(code)) + } + @Test def testBasicScalaConcurrentViaMacroFrontEnd(): Unit = { val code = From 7d46e6b4ce47360817e7639dd7a60325cfd89501 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 24 Mar 2020 13:40:27 +1000 Subject: [PATCH 67/94] Move ThicketTransformer to s.t.n.transform It seems generally useful enough to move it out of the async utilities. I've also added a unit test. --- .../nsc/transform/TypingTransformers.scala | 54 ++++++++++++ .../nsc/transform/async/TransformUtils.scala | 2 +- .../transform/ThicketTransformerTest.scala | 82 +++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 test/junit/scala/tools/nsc/transform/ThicketTransformerTest.scala diff --git a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala index 795df159c2ae..7dd656a3e286 100644 --- a/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala +++ b/src/compiler/scala/tools/nsc/transform/TypingTransformers.scala @@ -13,6 +13,9 @@ package scala.tools.nsc package transform +import scala.collection.mutable + + /** A base class for transforms. * A transform contains a compiler phase which applies a tree transformer. */ @@ -56,5 +59,56 @@ trait TypingTransformers { } def transformAtOwner(owner: Symbol, tree: Tree): Tree = atOwner(tree, owner) { transform(tree) } } + + private object ThicketAttachment + /** A base class for typing transformers that need to perform "thicket expansion". A thicket is the output of a + * transformation that is flattened into the enclosing block. + */ + abstract class ThicketTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { + private def expandThicket(t: Tree): List[Tree] = t match { + case Block(stats, expr) if t.attachments.containsElement(ThicketAttachment) => + stats :+ expr + case _ => t :: Nil + } + + def apply(tree: Tree): List[Tree] = expandThicket(transform(tree)) + + protected def Thicket(stats: List[Tree], expr: Tree): Tree = { + Block(stats, expr).updateAttachment(ThicketAttachment) + } + protected def Thicket(block: Block): Tree = { + block.updateAttachment(ThicketAttachment) + } + + override def transform(tree: Tree): Tree = tree match { + case Block(stats, expr) => + val transformedStats = transformTrees(stats) + val transformedExpr = transform(expr) + if ((stats eq transformedStats) && (expr eq transformedExpr)) tree + else { + val expanded = new mutable.ListBuffer[Tree] + def expandStats(): Unit = transformedStats.foreach { + case EmptyTree => + case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => + stats.foreach { s => if (s != EmptyTree) expanded += s } + if (expr != EmptyTree) expanded += expr + case t => + expanded += t + } + def expandExpr(): Tree = transformedExpr match { + case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => + stats.foreach { s => if (s != EmptyTree) expanded += s } + expr + case t => + t + } + expandStats() + val expr1 = expandExpr() + treeCopy.Block(tree, expanded.toList, expr1) + } + case _ => + super.transform(tree) + } + } } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 8640ff3515b3..f7c764b9f26b 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -89,7 +89,7 @@ private[async] trait TransformUtils extends AsyncTransformStates { } private object ThicketAttachment - class ThicketTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { + abstract class ThicketTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { private def expandThicket(t: Tree): List[Tree] = t match { case Block(stats, expr) if t.attachments.containsElement(ThicketAttachment) => stats :+ expr diff --git a/test/junit/scala/tools/nsc/transform/ThicketTransformerTest.scala b/test/junit/scala/tools/nsc/transform/ThicketTransformerTest.scala new file mode 100644 index 000000000000..93e0aea0e1ed --- /dev/null +++ b/test/junit/scala/tools/nsc/transform/ThicketTransformerTest.scala @@ -0,0 +1,82 @@ +package scala.tools.nsc.transform + +import org.junit.Assert.assertSame +import org.junit.{Assert, Test} + +import scala.tools.nsc.reporters.StoreReporter + +class ThicketTransformerTest { + @Test def thicketExpansion(): Unit = { + val g = new scala.tools.nsc.Global(new StoreReporter) + g.settings.usejavacp.value = true + new g.Run + import g._ + object testTransformers extends TypingTransformers { + val global: g.type = g + val dummyUnit = newCompilationUnit("") + + object testTransformer extends ThicketTransformer(newRootLocalTyper(dummyUnit)) { + override def transform(tree: Tree): Tree = tree match { + case Literal(Constant(s: String)) if (s.toLowerCase() != s) => Literal(Constant(s.toLowerCase())) + case Literal(Constant(s: String)) => s.toList.filterNot(_ == '-') match { + case Nil => Thicket(Nil, EmptyTree) + case a :: Nil => tree + case as => + Thicket(Block(as.map(c => Literal(Constant(c.toString)).setType(definitions.StringTpe)): _*)) + } + case t => super.transform(t) + } + } + } + def t(t: Tree): Tree = testTransformers.testTransformer.transform(t) + def assertTreeEquals(expected: Tree, actual: Tree) = Assert.assertEquals(expected.toString, actual.toString) + + def s(s: String) = Literal(Constant(s)) + def i(i: Int) = Literal(Constant(i)) + + locally { + val noTransform = Block(s("a") :: Nil, s("b")) + assertSame(noTransform, t(noTransform)) + } + + locally { + val noTransform = Block(s("a") :: s("b") :: Nil, s("c")) + assertSame(noTransform, t(noTransform)) + } + + locally { + val transformStats = Block(s("ab") :: i(1) :: s("cd") :: Nil, s("e")) + assertTreeEquals(Block(s("a") :: s("b") :: i(1) :: s("c") :: s("d") :: Nil, s("e")), t(transformStats)) + } + + locally { + val transformStats = Block(s("ab") :: s("cd") :: Nil, s("e")) + assertTreeEquals(Block(s("a") :: s("b") :: s("c") :: s("d") :: Nil, s("e")), t(transformStats)) + } + + locally { + val transformExpr = Block(s("a") :: s("b") :: Nil, s("cd")) + assertTreeEquals(Block(s("a") :: s("b") :: s("c") :: Nil, s("d")), t(transformExpr)) + } + + locally { + val transformStatsExpr = Block(s("ab") :: s("cd") :: Nil, s("ef")) + assertTreeEquals(Block(s("a") :: s("b") :: s("c") :: s("d") :: s("e") :: Nil, s("f")), t(transformStatsExpr)) + } + + locally { + val transformStatsExpr = Block(s("A") :: s("B") :: Nil, s("cd")) + assertTreeEquals(Block(s("a") :: s("b") :: s("c") :: Nil, s("d")), t(transformStatsExpr)) + } + + locally { + val transformStatsExpr = Block(s("A") :: s("B") :: Nil, s("C")) + assertTreeEquals(Block(s("a") :: s("b") :: Nil, s("c")), t(transformStatsExpr)) + } + + locally { + val transformStatsExpr = Block(s("-") :: Nil, s("a")) + assertTreeEquals(Block(Nil, s("a")), t(transformStatsExpr)) + } + } +} From 469cfc151a9602e490139c2e4695dc32a4f8268f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 24 Mar 2020 14:17:45 +1000 Subject: [PATCH 68/94] Add tests from scala/scala-async#210 https://github.com/scala/scala-async/pull/210 --- .../nsc/async/AnnotationDrivenAsync.scala | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index c128b8953dbb..bfac85561d8c 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -486,6 +486,57 @@ class AnnotationDrivenAsync { } } + @Test def testByNameOwner(): Unit = { + val result = run( + """ + import scala.tools.nsc.async.{autoawait, customAsync} + + object Bleh { + @autoawait def asyncCall(): Int = 0 + def byName[T](fn: => T): T = fn + } + object Boffo { + @autoawait @customAsync def jerk(): Unit = { + val pointlessSymbolOwner = 1 match { + case _ => + Bleh.asyncCall() + Bleh.byName { + val whyDoHateMe = 1 + whyDoHateMe + } + } + } + } + object Test { + @customAsync def test() = Boffo.jerk() + } + """) + } + + @Test def testByNameOwner2(): Unit = { + val result = run( + """ + import scala.tools.nsc.async.{autoawait, customAsync} + object Bleh { + @autoawait def bleh = Bleh + def byName[T](fn: => T): T = fn + } + object Boffo { + @autoawait @customAsync def slob(): Unit = { + val pointlessSymbolOwner = { + Bleh.bleh.byName { + val whyDoHateMeToo = 1 + whyDoHateMeToo + } + } + } + } + object Test { + @customAsync def test() = Boffo.slob() + } + """) + } + // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. @Test @Ignore From 074b480a2753cc98dbb197b4774e85272cc98862 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 24 Mar 2020 14:25:36 +1000 Subject: [PATCH 69/94] Add tests from scala/scala-async#206 https://github.com/scala/scala-async/pull/206 --- .../nsc/async/AnnotationDrivenAsync.scala | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index bfac85561d8c..fb68c2b420a1 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -537,6 +537,77 @@ class AnnotationDrivenAsync { """) } + @Test def testRewrittenApply(): Unit = { + val result = run( + """ + |import scala.tools.nsc.async.{autoawait, customAsync} + |object O { + | case class Foo(a: Any) + |} + |object Test { + | @autoawait def id(a: String) = a + | @customAsync + | def test = { + | O.Foo + | id("foo") + id("bar") + | O.Foo(1) + | } + |} + | """.stripMargin) + assertEquals("Foo(1)", result.toString) + } + + @Test def testIsInstanceOfType(): Unit = { + val result = run( + """ import scala.tools.nsc.async.{autoawait, customAsync} + | + | class Outer + | object Test { + | @autoawait def id(a: String) = a + | @customAsync def test = { + | val o = new Outer + | id("foo") + id("bar") + | ("": Object).isInstanceOf[o.type] + | } + | } + | """.stripMargin) + assertEquals(false, result) + } + + @Test def testIsInstanceOfTerm(): Unit = { + val result = run( + """import scala.tools.nsc.async.{autoawait, customAsync} + | + | class Outer + | object Test { + | @autoawait def id(a: String) = a + | @customAsync def test = { + | val o = new Outer + | id("foo") + id("bar") + | o.isInstanceOf[Outer] + | } + | } + | """.stripMargin) + assertEquals(true, result) + } + + @Test def testArrayLocalModule(): Unit = { + val result = run( + """ import scala.tools.nsc.async.{autoawait, customAsync} + | + | class Outer + | object Test { + | @autoawait def id(a: String) = a + | @customAsync def test = { + | val O = "" + | id("foo") + id("bar") + | new Array[O.type](0) + | } + | } + | """.stripMargin) + assertEquals(classOf[Array[String]], result.getClass) + } + // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. @Test @Ignore From 3a2882f6c5777fcde812c3b562e66afe7ddc0fbf Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 27 Mar 2020 22:20:10 +1000 Subject: [PATCH 70/94] Fix regression with non-awaiting patterns in tail position --- .../nsc/transform/async/AnfTransform.scala | 2 + .../nsc/transform/async/AsyncNames.scala | 2 + .../nsc/transform/async/ExprBuilder.scala | 14 +++++-- .../nsc/async/AnnotationDrivenAsync.scala | 41 ++++++++++++++++++- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 69ac07cc3387..8e85536f92b4 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -164,6 +164,8 @@ private[async] trait AnfTransform extends TransformUtils { case ld @ LabelDef(name, params, rhs) => treeCopy.LabelDef(tree, name, params, transformNewControlFlowBlock(rhs)) + case t @ Typed(expr, tpt) => + transform(expr).setType(t.tpe) case _ => super.transform(tree) } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala index cb30576914c1..29fa11a714e9 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -43,6 +43,7 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { } private val matchRes: TermNameCache = new TermNameCache("match") private val ifRes: TermNameCache = new TermNameCache("if") + private val qual: TermNameCache = new TermNameCache("qual") private val await: TermNameCache = new TermNameCache("await") @@ -54,6 +55,7 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { class AsyncName { final val matchRes = new NameSource[U#TermName](self.matchRes) final val ifRes = new NameSource[U#TermName](self.ifRes) + final val qual = new NameSource[U#TermName](self.qual) final val await = new NameSource[U#TermName](self.await) private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]() diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 5a5c79dd0aab..aec329ea2a54 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -113,7 +113,14 @@ trait ExprBuilder extends TransformUtils { // An exception should bubble out to the enclosing handler, don't insert a complete call. } else { val expr = stats.remove(stats.size - 1) - stats += completeSuccess(expr) + def pushIntoMatchEnd(t: Tree): Tree = { + t match { + case MatchEnd(ld) => treeCopy.LabelDef(ld, ld.name, ld.params, pushIntoMatchEnd(ld.rhs)) + case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, pushIntoMatchEnd(caseExpr))) + case expr => completeSuccess(expr) + } + } + stats += pushIntoMatchEnd(expr) } stats += typed(Return(literalUnit).setSymbol(currentTransformState.applySym)) allNextStates -= nextState @@ -243,7 +250,7 @@ trait ExprBuilder extends TransformUtils { } case ld @ LabelDef(name, params, rhs) => - if (isCaseLabel(ld.symbol) || isMatchEndLabel(ld.symbol)) { + if (isCaseLabel(ld.symbol) || (isMatchEndLabel(ld.symbol) && labelDefStates.contains(ld.symbol))) { // LabelDefs from patterns are a bit trickier as they can (forward) branch to each other. labelDefStates.get(ld.symbol).foreach { startLabelState => @@ -259,7 +266,7 @@ trait ExprBuilder extends TransformUtils { } val afterLabelState = afterState() - val (inlinedState, nestedStates) = buildNestedStatesFirstForInlining(rhs, afterLabelState) + val (inlinedState, nestedStates) = buildNestedStatesFirstForInlining(rhs, afterLabelState) // Leave this label here for synchronous jumps from previous cases. This is // allowed even if this case has its own state (ie if there is an asynchrounous path @@ -288,7 +295,6 @@ trait ExprBuilder extends TransformUtils { checkForUnsupportedAwait(stat) stateBuilder += stat } - case _ => checkForUnsupportedAwait(stat) stateBuilder += stat diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index fb68c2b420a1..d2e4680056fc 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -34,6 +34,43 @@ class AnnotationDrivenAsync { assertEquals(3, run(code)) } + @Test + def patternTailPosition(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test = async { + | { + | await(f(1)) + | "foo" match { + | case x if "".isEmpty => x + | } + | }: AnyRef + | } + |} + |""".stripMargin + assertEquals("foo", run(code)) + } + + @Test + def awaitTyped(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test = async {(("msg: " + await(f(0))): String).toString} + |} + |""".stripMargin + assertEquals("msg: 0", run(code)) + } + @Test def testBooleanAndOr(): Unit = { val code = @@ -646,8 +683,8 @@ class AnnotationDrivenAsync { // settings.debug.value = true // settings.uniqid.value = true - // settings.processArgumentString("-Xprint:async -nowarn") - // settings.log.value = List("async") + settings.processArgumentString("-Xprint:async -nowarn") + settings.log.value = List("async") // NOTE: edit ANFTransform.traceAsync to `= true` to get additional diagnostic tracing. From b68e118b102e73e0f05e2345dd8217907c0085ba Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 28 Mar 2020 14:19:32 +1000 Subject: [PATCH 71/94] More granular fresh names. --- .../tools/nsc/transform/async/AnfTransform.scala | 13 +++++++------ .../tools/nsc/transform/async/AsyncNames.scala | 3 --- .../tools/nsc/transform/async/AsyncPhase.scala | 2 +- .../nsc/transform/async/AsyncTransformStates.scala | 6 +++++- .../scala/tools/nsc/transform/async/Lifter.scala | 8 ++++---- .../tools/nsc/transform/async/TransformUtils.scala | 3 --- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 8e85536f92b4..af9b22e1c6db 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -45,6 +45,7 @@ private[async] trait AnfTransform extends TransformUtils { // `qual` before the `args` of a `Apply`). This is the default transform behaviour and the // conventional way to write transforms in any case. private var currentStats = ListBuffer[Tree]() + private val transformState = AnfTransform.this.currentTransformState override def transform(tree: Tree): Tree = trace(tree) { curTree = tree @@ -76,7 +77,7 @@ private[async] trait AnfTransform extends TransformUtils { currentStats += expr1 literalBoxedUnit } else { - val valDef = defineVal(name.freshen(argName), expr1, expr1.pos) + val valDef = defineVal(transformState.name.freshen(argName), expr1, expr1.pos) currentStats += valDef gen.mkAttributedIdent(valDef.symbol) } @@ -88,7 +89,7 @@ private[async] trait AnfTransform extends TransformUtils { val simpleApply = treeCopy.Apply(tree, simpleFun, argExprss) simpleApply.attachments.remove[ContainsAwait.type] if (isAwait(fun)) { - val valDef = defineVal(name.await(), treeCopy.Apply(tree, fun, argExprss), tree.pos) + val valDef = defineVal(transformState.name.await(), treeCopy.Apply(tree, fun, argExprss), tree.pos) val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe) currentStats += valDef atPos(tree.pos)(ref) @@ -144,7 +145,7 @@ private[async] trait AnfTransform extends TransformUtils { case If(cond, thenp, elsep) => val needsResultVar = (containsAwait(thenp) || containsAwait(elsep)) - transformMatchOrIf(tree, needsResultVar, name.ifRes) { varSym => + transformMatchOrIf(tree, needsResultVar, transformState.name.ifRes) { varSym => val condExpr = transform(cond) val thenBlock = transformNewControlFlowBlock(thenp) val elseBlock = transformNewControlFlowBlock(elsep) @@ -153,7 +154,7 @@ private[async] trait AnfTransform extends TransformUtils { case Match(scrut, cases) => val needResultVar = cases.exists(containsAwait) - transformMatchOrIf(tree, needResultVar, name.matchRes) { varSym => + transformMatchOrIf(tree, needResultVar, transformState.name.matchRes) { varSym => val scrutExpr = transform(scrut) val casesWithAssign = cases map { case cd@CaseDef(pat, guard, body) => @@ -180,7 +181,7 @@ private[async] trait AnfTransform extends TransformUtils { } } - private def transformMatchOrIf[T <: Tree](tree: Tree, needsResultVar: Boolean, nameSource: asyncNames.NameSource[TermName])(core: Symbol => T): Tree = { + private def transformMatchOrIf[T <: Tree](tree: Tree, needsResultVar: Boolean, nameSource: transformState.asyncNames.NameSource[TermName])(core: Symbol => T): Tree = { if (isPatMatGeneratedJump(tree)) assignUnitType(tree) if (!needsResultVar || isUnitType(tree.tpe) || (tree.tpe =:= definitions.NothingTpe)) { @@ -283,7 +284,7 @@ private[async] trait AnfTransform extends TransformUtils { // Otherwise, create the matchres var. We'll callers of the label def below. // Remember: we're iterating through the statement sequence in reverse, so we'll get // to the LabelDef and mutate `matchResults` before we'll get to its callers. - val matchResult = defineVar(name.matchRes(), param.tpe, ld.pos) + val matchResult = defineVar(transformState.name.matchRes(), param.tpe, ld.pos) matchResults += matchResult caseDefToMatchResult(ld.symbol) = matchResult.symbol (unitLabelDef, ld.rhs.substituteSymbols(param.symbol :: Nil, matchResult.symbol :: Nil)) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala index 29fa11a714e9..58322e871c5c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -43,10 +43,8 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { } private val matchRes: TermNameCache = new TermNameCache("match") private val ifRes: TermNameCache = new TermNameCache("if") - private val qual: TermNameCache = new TermNameCache("qual") private val await: TermNameCache = new TermNameCache("await") - final class NameSource[N <: U#Name](cache: NameCache[N]) { private val count = new AtomicInteger(0) def apply(): N = cache(count.getAndIncrement()) @@ -55,7 +53,6 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { class AsyncName { final val matchRes = new NameSource[U#TermName](self.matchRes) final val ifRes = new NameSource[U#TermName](self.ifRes) - final val qual = new NameSource[U#TermName](self.qual) final val await = new NameSource[U#TermName](self.await) private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]() diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index ac29b73584f1..4693022fef1a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -122,7 +122,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran val trSym = dd.vparamss.head.head.symbol val saved = currentTransformState currentTransformState = new AsyncTransformState(asyncAttachment.awaitSymbol, - asyncAttachment.postAnfTransform, asyncAttachment.stateDiagram, this, trSym, asyncBody.tpe) + asyncAttachment.postAnfTransform, asyncAttachment.stateDiagram, this, trSym, asyncBody.tpe, asyncNames) try { val (newRhs, liftableFields) = asyncTransform(asyncBody) liftableMap(dd.symbol.owner) = (dd.symbol, liftableFields) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala index a3956aa22eef..86f04bed77fa 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala @@ -26,10 +26,14 @@ trait AsyncTransformStates extends TypingTransformers { val dotDiagram: (Symbol, Tree) => Option[String => Unit], val typingTransformer: TypingTransformer, val applyTrParam: Symbol, - val asyncType: Type) { + val asyncType: Type, + val asyncNames: AsyncNames[global.type]) { val localTyper: analyzer.Typer = typingTransformer.localTyper val stateAssigner = new StateAssigner val labelDefStates = collection.mutable.Map[Symbol, Int]() + object name extends asyncNames.AsyncName { + def fresh(name: TermName): TermName = freshenIfNeeded(name) + } lazy val Async_await: Symbol = awaitSymbol diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index e767d13b55a5..4dbe8ba24257 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -152,12 +152,12 @@ trait Lifter extends ExprBuilder { val isLazy = sym.isLazy sym.setFlag(STABLE | PRIVATE | LOCAL) if (isLazy) sym.resetFlag(LAZY) else sym.setFlag(MUTABLE) - sym.setName(name.fresh(sym.name.toTermName)) + sym.setName(currentTransformState.name.fresh(sym.name.toTermName)) sym.setInfo(sym.info.deconst) val rhs1 = if (isLazy) rhs else EmptyTree treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(sym.info).setPos(t.pos), rhs1) case dd@DefDef(_, _, tparams, vparamss, tpt, rhs) => - sym.setName(this.name.freshen(sym.name.toTermName)) + sym.setName(currentTransformState.name.freshen(sym.name.toTermName)) sym.setFlag(PRIVATE | LOCAL) // Was `DefDef(sym, rhs)`, but this ran afoul of `ToughTypeSpec.nestedMethodWithInconsistencyTreeAndInfoParamSymbols` // due to the handling of type parameter skolems in `thisMethodType` in `Namers` @@ -165,7 +165,7 @@ trait Lifter extends ExprBuilder { case cd@ClassDef(_, _, tparams, impl) => val companion = companionship.companionOf(cd.symbol) if (!cd.symbol.isModuleClass) { - sym.setName(name.freshen(sym.name.toTypeName)) + sym.setName(currentTransformState.name.freshen(sym.name.toTypeName)) companion match { case NoSymbol => case moduleClassSymbol => @@ -176,7 +176,7 @@ trait Lifter extends ExprBuilder { } else { companion match { case NoSymbol => - sym.setName(name.freshen(sym.name.toTypeName)) + sym.setName(currentTransformState.name.freshen(sym.name.toTypeName)) sym.setName(sym.name.toTypeName) case classSymbol => // will be renamed by above. } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index f7c764b9f26b..a2b3043527f6 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -22,9 +22,6 @@ private[async] trait TransformUtils extends AsyncTransformStates { import global._ private[async] val asyncNames: AsyncNames[global.type] - object name extends asyncNames.AsyncName { - def fresh(name: TermName): TermName = freshenIfNeeded(name) - } def typedPos(pos: Position)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos)(tree: Tree) def typed(tree: Tree): Tree = typedPos(currentTransformState.applySym.pos)(tree) From 0775a0dbd6ba39a86c31679510304adcb068d80e Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 28 Mar 2020 20:25:21 +1000 Subject: [PATCH 72/94] erasure: push casts into LabelDefs to simplify async phase --- .../transform/TypeAdaptingTransformer.scala | 13 ++++- .../nsc/transform/async/ExprBuilder.scala | 2 +- .../nsc/async/AnnotationDrivenAsync.scala | 52 ++++++++++++++++++- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala index 78b1191e0855..91af26a73a88 100644 --- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -134,7 +134,18 @@ trait TypeAdaptingTransformer { self: TreeDSL => val needsExtraCast = isPrimitiveValueType(tree.tpe.typeArgs.head) && !isPrimitiveValueType(pt.typeArgs.head) val tree1 = if (needsExtraCast) gen.mkRuntimeCall(nme.toObjectArray, List(tree)) else tree gen.mkAttributedCast(tree1, pt) - } else gen.mkAttributedCast(tree, pt) + } else { + tree match { + case ld: LabelDef => + // Push the cast into the RHS of matchEnd LabelDefs. + ld.symbol.modifyInfo { + case MethodType(params, _) => MethodType(params, pt) + } + deriveLabelDef(ld)(rhs => cast(rhs, pt)).setType(pt) + case _ => + gen.mkAttributedCast(tree, pt) + } + } } /** Adapt `tree` to expected type `pt`. diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index aec329ea2a54..da7d68170ae8 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -611,7 +611,7 @@ trait ExprBuilder extends TransformUtils { case _ => tree :: Nil } - private def addLabelState(label: Symbol): Int = + def addLabelState(label: Symbol): Int = labelDefStates.getOrElseUpdate(label, StateAssigner.stateIdForLabel(label)) // Replace jumps to qualifying labels as a state transition. diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index d2e4680056fc..ca1508ba044b 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -2,6 +2,7 @@ package scala.tools.nsc package async import java.io.File +import java.lang.reflect.InvocationTargetException import java.nio.file.{Files, Paths} import java.util.concurrent.CompletableFuture @@ -12,6 +13,7 @@ import scala.annotation.StaticAnnotation import scala.concurrent.duration.Duration import scala.reflect.internal.util.Position import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader +import scala.tools.nsc.backend.jvm.AsmUtils import scala.tools.nsc.plugins.{Plugin, PluginComponent} import scala.tools.nsc.reporters.StoreReporter import scala.tools.nsc.transform.TypingTransformers @@ -56,6 +58,42 @@ class AnnotationDrivenAsync { assertEquals("foo", run(code)) } + @Test + def patternTailPositionMatchEndCast(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + |trait A1 + |trait B1 + |trait B2 + |trait Z1 + |class C1 extends B1 with A1 with Z1 + |class C2 extends A1 with B2 with Z1 + |class C2A extends C2 + |class C2B extends C2 + |object Test { + | def test = async[Z1] { + | val result = if ("foo".isEmpty) { + | await(f(1)) + | null : Z1 + | } else { + | (0: Any) match { + | case 0 => + | await(f(1)) + | null: C2 with Z1 + | case _ => + | null: C1 with Z1 + | } + | } + | result + | } + |} + |""".stripMargin + assertEquals(null, run(code)) + } + @Test def awaitTyped(): Unit = { val code = @@ -683,8 +721,8 @@ class AnnotationDrivenAsync { // settings.debug.value = true // settings.uniqid.value = true - settings.processArgumentString("-Xprint:async -nowarn") - settings.log.value = List("async") + // settings.processArgumentString("-Xprint:typer,posterasure,async -nowarn") + // settings.log.value = List("async") // NOTE: edit ANFTransform.traceAsync to `= true` to get additional diagnostic tracing. @@ -712,6 +750,7 @@ class AnnotationDrivenAsync { val result = try { cls.getMethod("test").invoke(null) } catch { + case ite: InvocationTargetException => throw ite.getCause case _: NoSuchMethodException => cls.getMethod("main", classOf[Array[String]]).invoke(null, null) } @@ -724,6 +763,15 @@ class AnnotationDrivenAsync { cf.get() case value => value } + } catch { + case ve: VerifyError => + val asm = out.listFiles().filter(_.getName.contains("stateMachine")).flatMap { file => + import scala.sys.process._ + val javap = List("/usr/local/bin/javap", "-v", file.getAbsolutePath).!! + val asmp = AsmUtils.textify(AsmUtils.readClass(file.getAbsolutePath)) + javap :: asmp :: Nil + }.mkString("\n\n") + throw new AssertionError(asm, ve) } finally { scala.reflect.io.Path.apply(out).deleteRecursively() } From d1d3daab061c78e653e467ce36c4aa9ee8dd448a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sat, 28 Mar 2020 23:56:54 +1000 Subject: [PATCH 73/94] Avoid VerifyErrors wrapping Try in Assign. --- .../nsc/transform/async/AnfTransform.scala | 10 +- .../nsc/transform/async/AsyncPhase.scala | 3 +- .../nsc/transform/async/ExprBuilder.scala | 15 ++- .../nsc/transform/async/TransformUtils.scala | 18 +++ .../nsc/async/AnnotationDrivenAsync.scala | 125 ++++++++++++++++++ 5 files changed, 156 insertions(+), 15 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index af9b22e1c6db..4cd32d39a669 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -71,7 +71,7 @@ private[async] trait AnfTransform extends TransformUtils { // argument containing an `await` calls. val elideVal = treeInfo.isExprSafeToInline(expr1) || lastAwaitArgIndex < 0 || i > lastAwaitArgIndex || !treeContainsAwait val result = if (elideVal) { - expr1 + localTyper.typed(expr1, arg.tpe) // Adapt () to BoxedUnit } else { if (isUnitType(expr1.tpe)) { currentStats += expr1 @@ -173,12 +173,8 @@ private[async] trait AnfTransform extends TransformUtils { } private def pushAssignmentIntoExpr(varSym: Symbol, t: Tree): Tree = { - t match { - case _ if varSym == NoSymbol || t.tpe.typeSymbol == definitions.NothingClass => t - case MatchEnd(ld) => treeCopy.LabelDef(ld, ld.name, ld.params, pushAssignmentIntoExpr(varSym, ld.rhs)) - case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, pushAssignmentIntoExpr(varSym, caseExpr))) - case _ => typedAssign(t, varSym) - } + if (varSym == NoSymbol || t.tpe.typeSymbol == definitions.NothingClass) t + else deriveTree(t, definitions.UnitTpe)(t => typedAssign(t, varSym)) } private def transformMatchOrIf[T <: Tree](tree: Tree, needsResultVar: Boolean, nameSource: transformState.asyncNames.NameSource[TermName])(core: Symbol => T): Tree = { diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 4693022fef1a..9da016141c03 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -204,7 +204,8 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran override def transform(tree: Tree): Tree = tree match { case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) && currentOwner == applySym => // Drop the lifted definitions from the apply method - assignUnitType(treeCopy.Assign(tree, fieldSel(tree), transform(rhs.changeOwner(tree.symbol, currentOwner)))) + val rhs1 = transform(rhs.changeOwner(tree.symbol, currentOwner)) + deriveTree(rhs1, definitions.UnitTpe)(t => treeCopy.Assign(rhs1, fieldSel(tree), adapt(t, tree.symbol.tpe))) case _: DefTree if liftedSyms(tree.symbol) && currentOwner == applySym => // Drop the lifted definitions from the apply method EmptyTree diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index da7d68170ae8..0bb6eb1f4f64 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -287,7 +287,7 @@ trait ExprBuilder extends TransformUtils { // while (cond) { if (z) { await(f); return }; i += 1 } // val startLabelState = addLabelState(ld.symbol) - val afterLabelState = stateAssigner.nextState() + val afterLabelState = afterState() val nestedStates = buildNestedStates(rhs, startLabelState, afterLabelState) nestedStates.foreach(addState) buildStateAndOpenNextState(startLabelState, afterLabelState, StateTransitionStyle.UpdateAndContinue) @@ -459,18 +459,17 @@ trait ExprBuilder extends TransformUtils { } else state all.foreach {state => val state1 = followEmptyState(state) - if (state1 ne state) + if ((state1 ne state) && state.state != StateAssigner.Initial) emptyReplacements(state.state) = state1.state } all.foreach { state => state.nextStates = state.nextStates.map(s => emptyReplacements.getOrElse[Integer](s, s).toInt).distinct } def loop(state: AsyncState): Unit = { - if (!emptyReplacements.contains(state.state)) { + if (!emptyReplacements.contains(state.state)) seen.add(state.state) - for (i <- state.nextStates if !seen.contains(i) && i != StateAssigner.Terminal) { - loop(map(i)) - } + for (i <- state.nextStates if !seen.contains(i) && i != StateAssigner.Terminal) { + loop(map(i)) } } loop(initial) @@ -528,7 +527,9 @@ trait ExprBuilder extends TransformUtils { // Comlete the Promise in the `result` field with the final successful result of this async block. private def completeSuccess(expr: Tree): Tree = { - typed(Apply(currentTransformState.memberRef(currentTransformState.stateCompleteSuccess), expr :: Nil)) + deriveTree(expr, definitions.UnitTpe) { 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? */ diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index a2b3043527f6..76f1d896c48c 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -61,6 +61,24 @@ private[async] trait TransformUtils extends AsyncTransformStates { def assignUnitType(t: Tree): t.type = t.setType(definitions.UnitTpe) + // Avoid moving a Try into expression position with a potentially non-empty stack. + // Uncurry has already run and is responsible for faking try expressions! See needsTryLift. + final def deriveTree(tree: Tree, exprType: Type)(deriveExpr: Tree => Tree): Tree = tree match { + case Try(block, catches, finalizer) => + val block1 = deriveTree(block, exprType)(deriveExpr) + val catches1 = catches.mapConserve(cd => deriveCaseDef(cd)(body => deriveTree(body, exprType)(deriveExpr))) + treeCopy.Try(tree, block1, catches1, finalizer).setType(exprType) + case Block(stats, expr) => + treeCopy.Block(tree, stats, deriveTree(expr, exprType)(deriveExpr)) + case MatchEnd(ld) => + ld.symbol.modifyInfo { + case MethodType(params, _) => MethodType(params, exprType) + } + treeCopy.LabelDef(ld, ld.name, ld.params, deriveTree(ld.rhs, exprType)(deriveExpr)).setType(exprType) + case _ => + deriveExpr(tree).setType(exprType) + } + def isUnitType(tp: Type): Boolean = tp.typeSymbol == definitions.UnitClass || tp =:= definitions.BoxedUnitTpe def literalUnit: Tree = Literal(Constant(())).setType(definitions.UnitTpe) // a def to avoid sharing trees diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index ca1508ba044b..356341b88ca8 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -109,6 +109,131 @@ class AnnotationDrivenAsync { assertEquals("msg: 0", run(code)) } + @Test + def avoidLiftingTryIntoExpression(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test = async { + | //var info = "" + | var info = try { + | "body1" + | } catch { + | case _: Throwable => "fallback" + | } + | var info1 = "" + | info1 = try { + | "body2" + | } catch { + | case _: Throwable => "fallback" + | } + | + | await(f(0)) + | (info, info1) + | } + |} + |""".stripMargin + assertEquals(("body1", "body2"), run(code)) + } + + @Test + def avoidLiftingTryIntoExpression2(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test = async { + | await(f(1)) + | try { + | "body" + | } catch { + | case _: Throwable => + | "catch" + | } + | } + |} + |""".stripMargin + assertEquals("body", run(code)) + } + + @Test + def avoidWhileExprPosition(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test = async { + | if ("".isEmpty) { + | () + | } else { + | var continue = true + | while (continue) { + | continue = await(f(false)) + | } + | } + | await(f("result")) + | } + |} + |""".stripMargin + assertEquals("result", run(code)) + } + + @Test + def whileExpr1(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | var continue = true + | def test = async { (await(test1), await(test2)) } + | def test1 = async { + | while(continue) { + | continue = false + | await(f(())) + | } + | await(f("result1")) + | } + | def test2 = async { + | await(f("")) + | while(continue) { + | continue = false + | await(f(())) + | } + | await(f("result2")) + | } + |} + |""".stripMargin + assertEquals(("result1", "result2"), run(code)) + } + + @Test + def genericUnitTypedAssert(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def finish[T](t: T): T = t + | def test = async { + | finish(this match { case _ if "".isEmpty => (); case _ => await(f(())) }) + | } + |} + |""".stripMargin + assertEquals((), run(code)) + } + @Test def testBooleanAndOr(): Unit = { val code = From 8c770f2b25b15f981d83a41d230a518b923352e4 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 30 Mar 2020 17:13:17 +1000 Subject: [PATCH 74/94] await in by-name function arg is repoted as ill-nested. --- test/async/neg/ill-nested-await.check | 5 ++++- test/async/neg/ill-nested-await.scala | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/async/neg/ill-nested-await.check b/test/async/neg/ill-nested-await.check index 1afd14d57bdd..3fa72873188b 100644 --- a/test/async/neg/ill-nested-await.check +++ b/test/async/neg/ill-nested-await.check @@ -31,7 +31,10 @@ ill-nested-await.scala:60: error: await must not be used under a nested method. ill-nested-await.scala:69: error: await must not be used under a lazy val initializer. def foo(): Any = async { val x = { lazy val y = await(f(0)); y } } ^ +ill-nested-await.scala:75: error: await must not be used under a nested method. + async { fooAsByNameLambda(await(f(""))) } + ^ ill-nested-await.scala:9: error: `await` must be enclosed in an `async` block await[Any](f(null)) ^ -12 errors found +13 errors found diff --git a/test/async/neg/ill-nested-await.scala b/test/async/neg/ill-nested-await.scala index 7a4aebc5d002..afb5656d1bee 100644 --- a/test/async/neg/ill-nested-await.scala +++ b/test/async/neg/ill-nested-await.scala @@ -68,4 +68,10 @@ class NakedAwait { //expectError("await must not be used under a lazy val initializer") def foo(): Any = async { val x = { lazy val y = await(f(0)); y } } } + + def byNameFunction(): Unit = { + def foo(x: String)(a: => String): String = a.reverse + def fooAsByNameLambda = foo("") _ // : (_ => String) = String + async { fooAsByNameLambda(await(f(""))) } + } } From 4d0f6bb37a029caf3097132b0248c702dfe20444 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 31 Mar 2020 13:30:04 +1000 Subject: [PATCH 75/94] Improve error reporting in asynj junit test. Show source file context for reported compiler errors and warnings, as we would expect from the normal ConsoleReporter. --- .../scala/tools/nsc/async/AnnotationDrivenAsync.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 356341b88ca8..78f43db305e1 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -868,8 +868,11 @@ class AnnotationDrivenAsync { val source = newSourceFile(code) run.compileSources(source :: Nil) if (compileOnly) return null - Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasErrors) - Assert.assertTrue(reporter.infos.mkString("\n"), !reporter.hasWarnings) + def showInfo(info: StoreReporter#Info): String = { + Position.formatMessage(info.pos, info.severity.toString.toLowerCase + " : " + info.msg, false) + } + Assert.assertTrue(reporter.infos.map(showInfo).mkString("\n"), !reporter.hasErrors) + Assert.assertTrue(reporter.infos.map(showInfo).mkString("\n"), !reporter.hasWarnings) val loader = new URLClassLoader(Seq(new File(settings.outdir.value).toURI.toURL), global.getClass.getClassLoader) val cls = loader.loadClass("Test") val result = try { From a5298c514d94b2820105864bd45d80fd6de953ce Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 1 Apr 2020 09:23:51 +1000 Subject: [PATCH 76/94] Support state getter/setter methods Rather than assuming its a var. --- .../tools/nsc/transform/async/AsyncTransformStates.scala | 2 +- test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala index 86f04bed77fa..ecfce017b95e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala @@ -41,7 +41,7 @@ trait AsyncTransformStates extends TypingTransformers { 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 stateSetter: Symbol = stateMachineMember(nme.state.setterName) lazy val stateOnComplete: Symbol = stateMachineMember(TermName("onComplete")) lazy val stateCompleteSuccess: Symbol = stateMachineMember(TermName("completeSuccess")) lazy val stateCompleteFailure: Symbol = stateMachineMember(TermName("completeFailure")) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 78f43db305e1..11c83346540f 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -17,7 +17,6 @@ import scala.tools.nsc.backend.jvm.AsmUtils 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.partest.async.AsyncStateMachine class AnnotationDrivenAsync { @@ -986,7 +985,9 @@ final class customAsync extends StaticAnnotation 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 + private[this] var _state = 0 + protected def state$async: Int = _state + protected def state$async_=(i: Int) = _state = i def apply(tr$async: R[AnyRef]): Unit type F[A] = CustomFuture[A] From 499296c8f9e77893302e026ce89c00485ebaef41 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 2 Apr 2020 15:33:08 +1000 Subject: [PATCH 77/94] Better integration with compiler fresh names - No longer add the `$async` suffix to fresh names. Add a test to show that we no longer risk clashing with lambda lifted names because we're coordinating through the unit's FreshNameCreator. - Add a facility to FreshNameCreator to support clients that want to generate many fresh names from a single prefix and avoid the overhead of going through `newTermName` when subsequent units also want to generate `x$0`, etc. - Add a little efficiency to name concatenation by use of presized of string builders. --- .../scala/tools/nsc/transform/UnCurry.scala | 2 +- .../nsc/transform/async/AsyncNames.scala | 98 +++++++++---------- .../nsc/transform/async/AsyncPhase.scala | 4 +- .../async/AsyncTransformStates.scala | 4 +- .../tools/nsc/transform/async/Lifter.scala | 2 +- .../nsc/typechecker/SyntheticMethods.scala | 4 +- .../scala/reflect/internal/Names.scala | 6 ++ .../scala/reflect/internal/StdNames.scala | 3 - .../internal/util/FreshNameCreator.scala | 40 +++++++- .../nsc/async/AnnotationDrivenAsync.scala | 26 +++++ 10 files changed, 121 insertions(+), 68 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index b224c35ed28f..ae360eb75715 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -753,7 +753,7 @@ abstract class UnCurry extends InfoTransform tpe } val info = info0.normalize - val tempValName = unit freshTermName (p.name + "$") + val tempValName = unit freshTermName (p.name.toStringWithSuffix("$")) val newSym = dd.symbol.newTermSymbol(tempValName, p.pos, SYNTHETIC).setInfo(info) atPos(p.pos)(ValDef(newSym, gen.mkAttributedCast(Ident(p.symbol), info))) } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala index 58322e871c5c..82fd4dc35a2a 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncNames.scala @@ -12,10 +12,8 @@ package scala.tools.nsc.transform.async -import java.util.concurrent.atomic.AtomicInteger - import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer +import scala.reflect.internal.util.FreshNameCreator /** * A per-global cache of names needed by the Async macro. @@ -24,16 +22,25 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { self => import u._ - abstract class NameCache[N <: U#Name](base: String) { - val cached = new ArrayBuffer[N]() + abstract class NameCache[N <: U#Name](val base: String) { + val prefix: String = base + "$" + private final val CacheLimit = 128 + val cached = new java.util.ArrayList[N](CacheLimit) + cached.addAll(java.util.Collections.nCopies(CacheLimit, null.asInstanceOf[N])) protected def newName(s: String): N - def apply(i: Int): N = { - if (cached.isDefinedAt(i)) cached(i) - else { - assert(cached.length == i) - val name = newName(freshenString(base, i)) - cached += name - name + def apply(nameFactory: FreshNameCreator#NameFactory): N = { + val i = nameFactory.index() + if (i < CacheLimit.toLong) { + cached.get(i.toInt) match { + case null => + val result = newName(nameFactory.newNameAtIndex(i)) + cached.set(i.toInt, result) + result + case name => + name + } + } else { + newName(nameFactory.newNameAtIndex(i)) } } } @@ -45,58 +52,41 @@ final class AsyncNames[U <: reflect.internal.Names with Singleton](val u: U) { private val ifRes: TermNameCache = new TermNameCache("if") private val await: TermNameCache = new TermNameCache("await") - final class NameSource[N <: U#Name](cache: NameCache[N]) { - private val count = new AtomicInteger(0) - def apply(): N = cache(count.getAndIncrement()) + final class NameSource[N <: U#Name](cache: NameCache[N], freshNameCreator: FreshNameCreator) { + private val factory = freshNameCreator.newNameFactory(cache.prefix) + def apply(): N = { + cache(factory) + } } - class AsyncName { - final val matchRes = new NameSource[U#TermName](self.matchRes) - final val ifRes = new NameSource[U#TermName](self.ifRes) - final val await = new NameSource[U#TermName](self.await) + class AsyncName(freshNameCreator: FreshNameCreator) { + final val matchRes = new NameSource[U#TermName](self.matchRes, freshNameCreator) + final val ifRes = new NameSource[U#TermName](self.ifRes, freshNameCreator) + final val await = new NameSource[U#TermName](self.await, freshNameCreator) - private val seenPrefixes = mutable.AnyRefMap[Name, AtomicInteger]() - private val freshened = mutable.HashSet[Name]() + private val seenPrefixes = mutable.HashSet[Name]() final def freshenIfNeeded(name: TermName): TermName = { - seenPrefixes.getOrNull(name) match { - case null => - seenPrefixes.put(name, new AtomicInteger()) - name - case counter => - freshen(name, counter) + if (seenPrefixes.contains(name)) { + TermName(freshNameCreator.newName(name.toStringWithSuffix("$"))) + } else { + seenPrefixes.add(name) + name + } + } + final def freshenIfNeeded(name: TypeName): TypeName = { + if (seenPrefixes.contains(name)) { + TypeName(freshNameCreator.newName(name.toStringWithSuffix("$"))) + } else { + seenPrefixes.add(name) + name } } final def freshen(name: TermName): TermName = { - val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) - freshen(name, counter) + TermName(freshNameCreator.newName(name.toStringWithSuffix("$"))) } final def freshen(name: TypeName): TypeName = { - val counter = seenPrefixes.getOrElseUpdate(name, new AtomicInteger()) - freshen(name, counter) + TypeName(freshNameCreator.newName(name.toStringWithSuffix("$"))) } - private def freshen(name: TermName, counter: AtomicInteger): TermName = { - if (freshened.contains(name)) name - else TermName(freshenString(name, counter.incrementAndGet())) - } - private def freshen(name: TypeName, counter: AtomicInteger): TypeName = { - if (freshened.contains(name)) name - else TypeName(freshenString(name, counter.incrementAndGet())) - } - } - - private def freshenString(name: CharSequence, counter: Int): String = { - val suffix = "$async$" - val length = name.length + suffix.length + decimalLength(counter) - val buffer = new java.lang.StringBuffer(length) - buffer.append(name) - buffer.append(suffix) - buffer.append(counter) - buffer.toString - } - - private def decimalLength(i: Int): Int = { - require(i >= 0, i) - if (i == 0) 1 else (Math.log10(i).floor + 1d).toInt } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 9da016141c03..e6d62f617dbc 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -109,7 +109,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) + val withFields = new UseFields(localTyper, cd.symbol, applySym, liftedSyms).transform(cd1) + withFields case dd: DefDef if dd.hasAttachment[AsyncAttachment] => val asyncAttachment = dd.getAndRemoveAttachment[AsyncAttachment].get @@ -117,7 +118,6 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran case blk@Block(stats, Literal(Constant(()))) => treeCopy.Block(blk, stats.init, stats.last).setType(stats.last.tpe) } - val saved = currentTransformState atOwner(dd, dd.symbol) { val trSym = dd.vparamss.head.head.symbol val saved = currentTransformState diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala index ecfce017b95e..054ea1420a0f 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala @@ -31,9 +31,7 @@ trait AsyncTransformStates extends TypingTransformers { val localTyper: analyzer.Typer = typingTransformer.localTyper val stateAssigner = new StateAssigner val labelDefStates = collection.mutable.Map[Symbol, Int]() - object name extends asyncNames.AsyncName { - def fresh(name: TermName): TermName = freshenIfNeeded(name) - } + val name = new asyncNames.AsyncName(localTyper.context.unit.fresh) lazy val Async_await: Symbol = awaitSymbol diff --git a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala index 4dbe8ba24257..1f88d586dcec 100644 --- a/src/compiler/scala/tools/nsc/transform/async/Lifter.scala +++ b/src/compiler/scala/tools/nsc/transform/async/Lifter.scala @@ -152,7 +152,7 @@ trait Lifter extends ExprBuilder { val isLazy = sym.isLazy sym.setFlag(STABLE | PRIVATE | LOCAL) if (isLazy) sym.resetFlag(LAZY) else sym.setFlag(MUTABLE) - sym.setName(currentTransformState.name.fresh(sym.name.toTermName)) + sym.setName(currentTransformState.name.freshenIfNeeded(sym.name.toTermName)) sym.setInfo(sym.info.deconst) val rhs1 = if (isLazy) rhs else EmptyTree treeCopy.ValDef(vd, Modifiers(sym.flags), sym.name, TypeTree(sym.info).setPos(t.pos), rhs1) diff --git a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala index f2bf6ca04a0a..f25daa006af2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SyntheticMethods.scala @@ -184,7 +184,7 @@ trait SyntheticMethods extends ast.TreeDSL { rt != NothingTpe && rt != NullTpe && rt != UnitTpe } - val otherName = freshTermName(clazz.name + "$")(freshNameCreatorFor(context)) + val otherName = freshTermName(clazz.name.toStringWithSuffix("$"))(freshNameCreatorFor(context)) val otherSym = eqmeth.newValue(otherName, eqmeth.pos, SYNTHETIC) setInfo clazz.tpe val pairwise = accessors collect { case acc if usefulEquality(acc) => @@ -397,7 +397,7 @@ trait SyntheticMethods extends ast.TreeDSL { val i = original.owner.caseFieldAccessors.indexOf(original) def freshAccessorName = { devWarning(s"Unable to find $original among case accessors of ${original.owner}: ${original.owner.caseFieldAccessors}") - freshTermName(original.name + "$")(freshNameCreatorFor(context)) + freshTermName(original.name.toStringWithSuffix("$"))(freshNameCreatorFor(context)) } def nameSuffixedByParamIndex = original.name.append(nme.CASE_ACCESSOR + "$" + i).toTermName val newName = if (i < 0) freshAccessorName else nameSuffixedByParamIndex diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala index 1883f4b172ae..e133ffb4093c 100644 --- a/src/reflect/scala/reflect/internal/Names.scala +++ b/src/reflect/scala/reflect/internal/Names.scala @@ -487,6 +487,12 @@ trait Names extends api.Names { def isOperatorName: Boolean = decode != toString // used by ide def longString: String = nameKind + " " + decode def debugString = { val s = decode ; if (isTypeName) s + "!" else s } + final def toStringWithSuffix(suffix: String): String = { + val builder = new java.lang.StringBuilder(length + suffix.length) + builder.append(this: CharSequence) + builder.append(suffix) + builder.toString + } override final def toString: String = if (cachedString == null) new String(_chrs, index, len) else cachedString final def appendTo(buffer: java.lang.StringBuffer, start: Int, length: Int): Unit = { diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 42633d36987c..7acb250fedc0 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -842,11 +842,8 @@ trait StdNames { val completed : NameType = "completed$async" val stateMachine : NameType = "stateMachine$async" val state : NameType = "state$async" - val execContextTemp : NameType = "execContext0$async" - val execContext : NameType = "execContext$async" val tr : NameType = "tr$async" val t : NameType = "throwable$async" - val future : NameType = "future" // quasiquote interpolators: val q: NameType = "q" diff --git a/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala b/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala index bcf13f181d78..248e15b9edf5 100644 --- a/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala +++ b/src/reflect/scala/reflect/internal/util/FreshNameCreator.scala @@ -27,9 +27,45 @@ class FreshNameCreator(creatorPrefix: String = "") { */ def newName(prefix: String): String = { val safePrefix = NameTransformer.encode(prefix) - val counter = counters.computeIfAbsent(safePrefix, (s: String) => new AtomicLong(0)) - val idx = counter.incrementAndGet() + val idx = allocateCounter(safePrefix).incrementAndGet() + assemble(safePrefix, idx) + } + + /** Low level API for clients that want to perform multiple fresh names from the same prefix. */ + def newNameFactory(prefix: String): NameFactory = { + val safePrefix = NameTransformer.encode(prefix) + val counter = allocateCounter(safePrefix) + new NameFactory(safePrefix, counter) + } + + private def assemble(safePrefix: String, idx: Long) = { + val result = new java.lang.StringBuilder(creatorPrefix.length + safePrefix.length + decimalLength(idx)) + result.append(creatorPrefix) + result.append(safePrefix) + result.append(idx) creatorPrefix + safePrefix + idx } + private def allocateCounter(safePrefix: String): AtomicLong = { + counters.computeIfAbsent(safePrefix, (s: String) => new AtomicLong(0)) + } + + final class NameFactory(safePrefix: String, counter: AtomicLong) { + def index(): Long = counter.incrementAndGet() + def newNameAtIndex(index: Long): String = assemble(safePrefix, index) + def newName(): String = newNameAtIndex(index()) + } + + def decimalLength(i: Long): Int = { + require(i >= 0, i) + var ceiling = 10 + var numDigits = 1 + val MaxValueLength = 19 // = Long.MaxValue.toString.length + while (numDigits <= MaxValueLength) { + if (i < ceiling) return numDigits + numDigits += 1 + ceiling *= 10 + } + MaxValueLength + } } diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 11c83346540f..dbaa925bbcd8 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -29,6 +29,7 @@ class AnnotationDrivenAsync { | |object Test { | def test: Future[Int] = async { await(f(1)) + await(f(2)) } + | def test1: Future[Int] = async { await(f(1)) + await(f(2)) } | def f(x: Int): Future[Int] = Future.successful(x) |} |""".stripMargin @@ -807,6 +808,31 @@ class AnnotationDrivenAsync { assertEquals(classOf[Array[String]], result.getClass) } + @Test def testLambdaLiftClash(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def test: Future[Int] = async { + | def foo = 42 + | await(f("")); // so that the preceding def will be lifted to foo$N + | + | { + | // lambdalift will later lift this to foo$N. + | def foo = 43 + | foo + | }; + | foo + | } + |} + |""".stripMargin + // If async and lambdalift phase both use the compilation units FreshNameCreator, we get foo$1 and foo$2, no clash! + assertEquals(42, run(code)) + } + // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. @Test @Ignore From e73f5020c39722601429aeb50d1ffcb878d95dca Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Sun, 19 Apr 2020 10:04:16 +1000 Subject: [PATCH 78/94] Position synthetic trees accurately --- .../tools/nsc/transform/async/AsyncTransformStates.scala | 6 ++++-- .../scala/tools/nsc/transform/async/ExprBuilder.scala | 1 + .../scala/tools/nsc/transform/async/TransformUtils.scala | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala index 054ea1420a0f..4e2d3b8161d9 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncTransformStates.scala @@ -35,8 +35,10 @@ trait AsyncTransformStates extends TypingTransformers { lazy val Async_await: Symbol = awaitSymbol - lazy val applyTr: Symbol = applyTrParam - lazy val applySym: Symbol = applyTr.owner + val applyTr: Symbol = applyTrParam + val applySym: Symbol = applyTr.owner + var currentPos: Position = applySym.pos + lazy val stateMachineClass: Symbol = applySym.owner lazy val stateGetter: Symbol = stateMachineMember(nme.state) lazy val stateSetter: Symbol = stateMachineMember(nme.state.setterName) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 0bb6eb1f4f64..f1f6379b5f05 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -188,6 +188,7 @@ trait ExprBuilder extends TransformUtils { private def add(stat: Tree, isExpr: Boolean): Unit = { def afterState() = if (isExpr) endState else stateAssigner.nextState() + currentTransformState.currentPos = stat.pos stat match { case vd @ ValDef(mods, name, tpt, UnwrapBoxedUnit(Apply(fun, arg :: Nil))) if isAwait(fun) => // The val await$0 = await(someFuture) pattern. The ANF tranform makes sure this is diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 76f1d896c48c..72fc87b05fe9 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -24,7 +24,7 @@ private[async] trait TransformUtils extends AsyncTransformStates { private[async] val asyncNames: AsyncNames[global.type] def typedPos(pos: Position)(tree: Tree): Tree = currentTransformState.localTyper.typedPos(pos)(tree: Tree) - def typed(tree: Tree): Tree = typedPos(currentTransformState.applySym.pos)(tree) + def typed(tree: Tree): Tree = typedPos(currentTransformState.currentPos)(tree) lazy val IllegalStateExceptionClass: Symbol = rootMirror.staticClass("java.lang.IllegalStateException") lazy val IllegalStateExceptionClass_NEW_String: Symbol = IllegalStateExceptionClass.info.decl(nme.CONSTRUCTOR).suchThat( From bcb5dc65f2b72928292f7079fd130ded530f37ca Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 30 Mar 2020 21:25:04 +1000 Subject: [PATCH 79/94] Overhaul/fix live variables --- .../nsc/transform/async/AnfTransform.scala | 9 +- .../nsc/transform/async/AsyncPhase.scala | 5 +- .../nsc/transform/async/ExprBuilder.scala | 48 +-- .../nsc/transform/async/LiveVariables.scala | 293 +++++++++--------- .../tools/nsc/transform/async/StateSet.scala | 1 + .../nsc/transform/async/TransformUtils.scala | 6 +- .../nsc/async/AnnotationDrivenAsync.scala | 154 +++++++++ 7 files changed, 347 insertions(+), 169 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 4cd32d39a669..2dd59c0bafcd 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -139,8 +139,13 @@ private[async] trait AnfTransform extends TransformUtils { // Definitions within stats lifted out of the `ValDef` rhs should no longer be owned by the // the ValDef. statsIterator.foreach(_.changeOwner((currentOwner, currentOwner.owner))) - - treeCopy.ValDef(tree, mods, name, tpt, expr) + val expr1 = if (isUnitType(expr.tpe)) { + currentStats += expr + literalBoxedUnit + } else { + expr + } + treeCopy.ValDef(tree, mods, name, tpt, expr1) } case If(cond, thenp, elsep) => diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index e6d62f617dbc..54cb5ddcc8a8 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -159,15 +159,14 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran val asyncBlock = buildAsyncBlock(anfTree) val liftedFields: List[Tree] = liftables(asyncBlock.asyncStates) - val liftedSyms = liftedFields.map(_.symbol).toSet // Null out lifted fields become unreachable at each state. val nullOut = true if (nullOut) { - for ((state, flds) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { + for ((state, (preNulls, postNulls)) <- fieldsToNullOut(asyncBlock.asyncStates, asyncBlock.asyncStates.last, liftedFields)) { val asyncState = asyncBlock.asyncStates.find(_.state == state).get if (asyncState.nextStates.nonEmpty) - asyncState.insertNullAssignments(flds.iterator) + asyncState.insertNullAssignments(preNulls.iterator, postNulls.iterator) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index f1f6379b5f05..6345acfef6b7 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -29,14 +29,15 @@ trait ExprBuilder extends TransformUtils { private def mkToString = s"AsyncState #$state, next = ${nextStates.toList}" override def toString: String = mkToString //+ " (was: " + initToString + ")" // private val initToString = mkToString - def insertNullAssignments(flds: Iterator[Symbol]): Unit = { + def insertNullAssignments(preNulls: Iterator[Symbol], postNulls: Iterator[Symbol]): Unit = { val stats1 = mutable.ListBuffer[Tree]() - def addNullAssigments(): Unit = { - // Insert the null assignments immediately after the state transition - for (fieldSym <- flds) { + def addNullAssigments(syms: Iterator[Symbol]): Unit = { + for (fieldSym <- syms) { stats1 += typed(Assign(gen.mkAttributedStableRef(fieldSym.owner.thisPrefix, fieldSym), gen.mkZero(fieldSym.info))) } } + // Add pre-state null assigments at the beginning. + addNullAssigments(preNulls) var foundStateTransition = false stats.foreach { stat => @@ -44,12 +45,12 @@ trait ExprBuilder extends TransformUtils { if (stat.attachments.containsElement(StateTransitionTree)) { assert(!foundStateTransition) foundStateTransition = true - // Insert the null assignments immediately after the state transition - addNullAssigments() + // Insert post-state null assignments immediately after the state transition + addNullAssigments(postNulls) } } if (!foundStateTransition) { - addNullAssigments() + addNullAssigments(postNulls) } stats = stats1.toList } @@ -94,8 +95,11 @@ trait ExprBuilder extends TransformUtils { this } + private[this] var built: Boolean = false /** Build the state using the accumulated `stats` followed by a state transition. */ def build(nextState: Int, style: StateTransitionStyle): AsyncState = { + assert(!built) + built = true // Record whether this state was free of meaningful stats (exclkuding unit literals which creep in after // the ANF and state maching transforms and the state transition code added bekow. // @@ -113,19 +117,15 @@ trait ExprBuilder extends TransformUtils { // An exception should bubble out to the enclosing handler, don't insert a complete call. } else { val expr = stats.remove(stats.size - 1) - def pushIntoMatchEnd(t: Tree): Tree = { - t match { - case MatchEnd(ld) => treeCopy.LabelDef(ld, ld.name, ld.params, pushIntoMatchEnd(ld.rhs)) - case b@Block(caseStats, caseExpr) => assignUnitType(treeCopy.Block(b, caseStats, pushIntoMatchEnd(caseExpr))) - case expr => completeSuccess(expr) - } - } - stats += pushIntoMatchEnd(expr) + stats += completeSuccess(expr) } + + allNextStates += nextState stats += typed(Return(literalUnit).setSymbol(currentTransformState.applySym)) - allNextStates -= nextState } - if (nextState == StateAssigner.Terminal) + if (state == StateAssigner.Terminal) { + // noop + } else if (nextState == StateAssigner.Terminal) completeAsyncBlock() else if (!concludesWithJump) stats ++= style.trees(nextState, allNextStates) @@ -175,15 +175,18 @@ trait ExprBuilder extends TransformUtils { statesMap(state.state) = state state } + def isRoot = outer.isEmpty private def addStats(): Unit = { stats.foreach(stat => add(stat, isExpr = false)) add(expr, isExpr = true) - def isRoot = outer.isEmpty if (!stateBuilder.stats.isEmpty || isRoot) { - val style = if (currState == startState) startToEndUpdateStyle else StateTransitionStyle.Update + val style = if (currState == startState) startToEndUpdateStyle else StateTransitionStyle.UpdateAndContinue addState(stateBuilder.build(endState, style = style)) } + if (isRoot && currState != endState) { + addState(new AsyncState(Nil, endState, Array(), true)) + } } private def add(stat: Tree, isExpr: Boolean): Unit = { @@ -220,7 +223,7 @@ trait ExprBuilder extends TransformUtils { stateBuilder.stats += treeCopy.If(stat, cond, mkBranch(thenp), mkBranch(elsep)) if (needAfterIfState) { stateBuilder.nextStates += afterIfState - buildStateAndOpenNextState(afterIfState, style = StateTransitionStyle.Update) + buildStateAndOpenNextState(afterIfState, style = StateTransitionStyle.UpdateAndContinue) } case Match(scrutinee, cases) if containsAwait(stat) => @@ -247,7 +250,7 @@ trait ExprBuilder extends TransformUtils { if (needAfterMatchState) { stateBuilder.nextStates += afterMatchState - buildStateAndOpenNextState(afterMatchState, StateTransitionStyle.Update) + buildStateAndOpenNextState(afterMatchState, StateTransitionStyle.UpdateAndContinue) } case ld @ LabelDef(name, params, rhs) => @@ -306,6 +309,7 @@ trait ExprBuilder extends TransformUtils { val (nestedStats, nestedExpr) = statsAndExpr(nestedTree) val nestedBuilder = new AsyncBlockBuilder(nestedStats, nestedExpr, currState, endState, StateTransitionStyle.None, Some(this)) val ((inlinedState :: Nil), nestedStates) = nestedBuilder.build.partition(_.state == currState) + inlinedState.nextStates.foreach(stateBuilder.nextStates += _) (inlinedState, nestedStates) } private def buildNestedStates(nestedTree: Tree, startState: Int, endState: Int): List[AsyncState] = { @@ -339,7 +343,6 @@ trait ExprBuilder extends TransformUtils { case Nil => needsAfterState case state :: rest => addState(state) - stateBuilder.nextStates += state.state loop(rest, needsAfterState || state.nextStates.contains(afterState)) } loop(nestedStates, false) @@ -453,6 +456,7 @@ trait ExprBuilder extends TransformUtils { val ((initial :: Nil), rest) = all.partition(_.state == blockBuilder.startState) val map = all.iterator.map(x => (x.state, x)).toMap val seen = mutable.HashSet[Int]() + seen.add(all.last.state) def followEmptyState(state: AsyncState): AsyncState = if (state.isEmpty && state.nextStates.size == 1) { val next = state.nextStates(0) if (next == blockBuilder.endState) state diff --git a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala index 0a2fb1d0bd97..2623eb5e893d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala +++ b/src/compiler/scala/tools/nsc/transform/async/LiveVariables.scala @@ -12,9 +12,6 @@ package scala.tools.nsc.transform.async -import java.util.function.IntConsumer - -import scala.collection.immutable.IntMap import scala.collection.mutable import scala.reflect.internal.Flags._ @@ -30,21 +27,24 @@ trait LiveVariables extends ExprBuilder { * @param liftables the lifted fields * @return a map which indicates fields which are used for the final time in each state. */ - def fieldsToNullOut(asyncStates: List[AsyncState], finalState: AsyncState, liftables: List[Tree]): mutable.LinkedHashMap[Int, mutable.LinkedHashSet[Symbol]] = { - val liftedSyms: Set[Symbol] = // include only vars - liftables.iterator.filter { - case ValDef(mods, _, _, _) => mods.hasFlag(MUTABLE) - case _ => false - }.map(_.symbol).toSet + def fieldsToNullOut(asyncStates: List[AsyncState], finalState: AsyncState, + liftables: List[Tree]): mutable.LinkedHashMap[Int, (mutable.LinkedHashSet[Symbol], mutable.LinkedHashSet[Symbol])] = { + + val liftedSyms = mutable.HashSet[Symbol]() + + // include only vars + liftedSyms ++= liftables.iterator.collect { + case vd : ValDef if vd.symbol.hasFlag(MUTABLE) => + vd.symbol + } // determine which fields should be live also at the end (will not be nulled out) - val noNull: Set[Symbol] = liftedSyms.filter { sym => + liftedSyms.foreach { sym => val tpSym = sym.info.typeSymbol - (tpSym.isPrimitiveValueClass || tpSym == definitions.NothingClass) || liftables.exists { tree => - !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym) - } + if ((tpSym.isPrimitiveValueClass || tpSym == definitions.NothingClass) || liftables.exists { tree => + !liftedSyms.contains(tree.symbol) && tree.exists(_.symbol == sym)}) + liftedSyms -= sym } - debuglog(s"fields never zero-ed out: ${noNull.mkString(", ")}") /** * Traverse statements of an `AsyncState`, collect `Ident`-s referring to lifted fields. @@ -52,10 +52,10 @@ trait LiveVariables extends ExprBuilder { * @param as a state of an `async` expression * @return a set of lifted fields that are used within state `as` */ - def fieldsUsedIn(as: AsyncState): ReferencedFields = { + def fieldsUsedIn(as: AsyncState): (collection.Set[Symbol], collection.Set[Symbol]) = { class FindUseTraverser extends AsyncTraverser { - var usedFields: Set[Symbol] = Set[Symbol]() - var capturedFields: Set[Symbol] = Set[Symbol]() + val usedBeforeAssignment = new mutable.HashSet[Symbol]() + val assignedFields = new mutable.HashSet[Symbol]() private def capturing[A](body: => A): A = { val saved = capturing try { @@ -63,21 +63,25 @@ trait LiveVariables extends ExprBuilder { body } finally capturing = saved } - private def capturingCheck(tree: Tree) = capturing(tree foreach check) + private def capturingCheck(tree: Tree) = capturing(super[Traverser].traverse(tree)) private var capturing: Boolean = false - private def check(tree: Tree): Unit = { - tree match { - case Ident(_) if liftedSyms(tree.symbol) => - if (capturing) - capturedFields += tree.symbol - else - usedFields += tree.symbol - case _ => - } - } - override def traverse(tree: Tree) = { - check(tree) - super.traverse(tree) + override def traverse(tree: Tree) = tree match { + case Assign(i @ Ident(_), rhs) if liftedSyms(tree.symbol) => + if (!capturing) + assignedFields += i.symbol + traverse(rhs) + case ValDef(_, _, _, rhs) if liftedSyms(tree.symbol) => + if (!capturing) + assignedFields += tree.symbol + traverse(rhs) + case Ident(_) if liftedSyms(tree.symbol) => + if (capturing) { + liftedSyms -= tree.symbol + } else if (!assignedFields.contains(tree.symbol)) { + usedBeforeAssignment += tree.symbol + } + case _ => + super.traverse(tree) } override def nestedClass(classDef: ClassDef): Unit = capturingCheck(classDef) @@ -94,131 +98,138 @@ trait LiveVariables extends ExprBuilder { val findUses = new FindUseTraverser findUses.traverse(Block(as.stats: _*)) - ReferencedFields(findUses.usedFields, findUses.capturedFields) + (findUses.usedBeforeAssignment, findUses.assignedFields) } - case class ReferencedFields(used: Set[Symbol], captured: Set[Symbol]) { - override def toString = s"used: ${used.mkString(",")}\ncaptured: ${captured.mkString(",")}" - } - - if (settings.debug.value && shouldLogAtThisPhase) { - for (as <- asyncStates) - debuglog(s"fields used in state #${as.state}: ${fieldsUsedIn(as)}") + val graph: Graph[AsyncState] = { + val g = new Graph[AsyncState] + val stateIdToState = asyncStates.iterator.map(x => (x.state, x)).toMap + for (asyncState <- asyncStates) { + val (used, assigned) = fieldsUsedIn(asyncState) + g.addNode(asyncState, used, assigned, asyncState.nextStates.map(stateIdToState).toList) + } + g.finish() } - /* Backwards data-flow analysis. Computes live variables information at entry and exit - * of each async state. - * - * Compute using a simple fixed point iteration: - * - * 1. currStates = List(finalState) - * 2. for each cs \in currStates, compute LVentry(cs) from LVexit(cs) and used fields information for cs - * 3. record if LVentry(cs) has changed for some cs. - * 4. obtain predecessors pred of each cs \in currStates - * 5. for each p \in pred, compute LVexit(p) as union of the LVentry of its successors - * 6. currStates = pred - * 7. repeat if something has changed - */ - - var LVentry = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]() - var LVexit = IntMap[Set[Symbol]]() withDefaultValue Set[Symbol]() - - // All fields are declared to be dead at the exit of the final async state, except for the ones - // that cannot be nulled out at all (those in noNull), because they have been captured by a nested def. - LVexit = LVexit + (finalState.state -> noNull) - - var currStates = List(finalState) // start at final state - var captured: Set[Symbol] = Set() + graph.lastReferences[Int](liftedSyms.toArray[Symbol])(_.t.state) + } - def contains(as: Array[Int], a: Int): Boolean = { - var i = 0 - while (i < as.length) { - if (as(i) == a) return true - i += 1 + private final class Graph[T] { + import java.util.BitSet + private val nodes = mutable.LinkedHashMap[T, Node]() + private class Node(val t: T, val refs: collection.Set[Symbol], val assign: collection.Set[Symbol], val succTs: List[T]) { + val succ = new Array[Node](succTs.size) + val pred = new mutable.ArrayBuffer[Node](4) + // Live variables at node entry + val entry: BitSet = new BitSet + // Live variables at node exit + var exit = new BitSet + // Variables generated at this node + val gen = new BitSet + val kill = new BitSet + var visited: Boolean = false + + def updateEntry(): Boolean = { + val card = entry.cardinality() + entry.clear() + entry.or(exit) + entry.andNot(kill) + entry.or(gen) + if (!visited) { + visited = true + true + } else (entry.cardinality() != card) } - false - } - while (!currStates.isEmpty) { - var entryChanged: List[AsyncState] = Nil - - for (cs <- currStates) { - val LVentryOld = LVentry(cs.state) - val referenced = fieldsUsedIn(cs) - captured ++= referenced.captured - val LVentryNew = LVexit(cs.state) ++ referenced.used - if (!LVentryNew.sameElements(LVentryOld)) { - LVentry = LVentry.updated(cs.state, LVentryNew) - entryChanged ::= cs + def updateExit(): Boolean = { + var changed = false + if (exit == null) { + changed = true + exit = new BitSet() + } + var i = 0 + val card = exit.cardinality() + while (i < succ.length) { + exit.or(succ(i).entry) + i += 1 } + card != exit.cardinality() } - - val pred = entryChanged.flatMap(cs => asyncStates.filter(state => contains(state.nextStates, cs.state))) - var exitChanged: List[AsyncState] = Nil - - for (p <- pred) { - val LVexitOld = LVexit(p.state) - val LVexitNew = p.nextStates.flatMap(succ => LVentry(succ)).toSet - if (!LVexitNew.sameElements(LVexitOld)) { - LVexit = LVexit.updated(p.state, LVexitNew) - exitChanged ::= p + def deadOnEntryLiveOnPredecessorExit: BitSet = { + val result = new BitSet + if (!pred.isEmpty) { + val it = pred.iterator + while (it.hasNext) { + val pred = it.next() + result.or(pred.exit) + } + result.andNot(entry) } + result } - - currStates = exitChanged + def deadOnExitLiveOnEntry: BitSet = { + val result = entry.clone.asInstanceOf[BitSet] + result.andNot(exit) + result + } + override def toString = s"Node($t, gen = $gen, kill = $kill, entry = $entry, exit = $exit, null = $deadOnEntryLiveOnPredecessorExit)" } - - if(settings.debug.value && shouldLogAtThisPhase) { - for (as <- asyncStates) { - debuglog(s"LVentry at state #${as.state}: ${LVentry(as.state).mkString(", ")}") - debuglog(s"LVexit at state #${as.state}: ${LVexit(as.state).mkString(", ")}") + def addNode(t: T, refs: collection.Set[Symbol], assign: collection.Set[Symbol], succ: List[T]): Unit = { + nodes(t) = new Node(t, refs, assign, succ) + } + private var finished = false + def finish(): this.type = { + assert(!finished) + for (node <- nodes.valuesIterator) { + foreachWithIndex(node.succTs) {(succT, i) => + val succ = nodes(succT) + node.succ(i) = succ + succ.pred += node + } } + finished = true + this } - - def lastUsagesOf(field: Tree, at: AsyncState): StateSet = { - val avoid = scala.collection.mutable.HashSet[AsyncState]() - - val result = new StateSet - def lastUsagesOf0(field: Tree, at: AsyncState): Unit = { - if (avoid(at)) () - else if (captured(field.symbol)) { - () + def lastReferences[K](syms: IndexedSeq[Symbol])(keyMapping: Node => K): mutable.LinkedHashMap[K, (mutable.LinkedHashSet[Symbol], mutable.LinkedHashSet[Symbol])] = { + assert(finished) + val symIndices: Map[Symbol, Int] = syms.zipWithIndex.toMap + val nodeValues = nodes.values.toArray + nodeValues.foreach { node => + for (ref <- node.refs) { + symIndices.getOrElse(ref, -1) match { + case -1 => + case n => node.gen.set(n) + } } - else LVentry get at.state match { - case Some(fields) if fields.contains(field.symbol) => - result += at.state - case _ => - avoid += at - for (state <- asyncStates) { - if (contains(state.nextStates, at.state)) { - lastUsagesOf0(field, state) - } - } + for (ref <- node.assign) { + symIndices.getOrElse(ref, -1) match { + case -1 => + case n => node.kill.set(n) + } } } - - lastUsagesOf0(field, at) - result - } - - val lastUsages: mutable.LinkedHashMap[Symbol, StateSet] = - mutable.LinkedHashMap(liftables.map(fld => fld.symbol -> lastUsagesOf(fld, finalState)): _*) - - if (settings.debug.value && shouldLogAtThisPhase) { - for ((fld, lastStates) <- lastUsages) - debuglog(s"field ${fld.name} is last used in states ${lastStates.iterator.mkString(", ")}") - } - - if (settings.debug.value && shouldLogAtThisPhase) { - for ((fld, killAt) <- lastUsages) - debuglog(s"field ${fld.name} should be nulled out at the conclusion of states ${killAt.iterator.mkString(", ")}") - } - - val assignsOf = mutable.LinkedHashMap[Int, mutable.LinkedHashSet[Symbol]]() - - for ((fld, where) <- lastUsages) { - where.foreach { new IntConsumer { def accept(state: Int): Unit = { - assignsOf.getOrElseUpdate(state, new mutable.LinkedHashSet()) += fld - }}} + val terminal = nodeValues.last + val workList = mutable.Queue[Node](terminal) + while (!workList.isEmpty) { + val node = workList.dequeue() + node.updateExit() + val entryChanged = node.updateEntry() + if (entryChanged) { + workList ++= node.pred + } + } + val empty = mutable.LinkedHashSet[Symbol]() + def toSymSet(indices: BitSet): mutable.LinkedHashSet[Symbol] = { + if (indices.isEmpty) empty + else { + val result = mutable.LinkedHashSet[Symbol]() + indices.stream().forEach(i => result += syms(i)) + result + } + } + mutable.LinkedHashMap(nodeValues.map { x => + val pre = toSymSet(x.deadOnEntryLiveOnPredecessorExit) + val post = toSymSet(x.deadOnExitLiveOnEntry) + (keyMapping(x), (pre, post)) + }: _*) } - assignsOf } } diff --git a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala index 1dd8c709351e..62c8aebb87b9 100644 --- a/src/compiler/scala/tools/nsc/transform/async/StateSet.scala +++ b/src/compiler/scala/tools/nsc/transform/async/StateSet.scala @@ -46,4 +46,5 @@ final class StateSet { }) } } + override def toString: String = toArray.sorted.toSeq.mkString("[", ",", "]") } diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 72fc87b05fe9..23bb2616b3a7 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -68,8 +68,12 @@ private[async] trait TransformUtils extends AsyncTransformStates { val block1 = deriveTree(block, exprType)(deriveExpr) val catches1 = catches.mapConserve(cd => deriveCaseDef(cd)(body => deriveTree(body, exprType)(deriveExpr))) treeCopy.Try(tree, block1, catches1, finalizer).setType(exprType) + case If(cond, thenp, elsep) => + treeCopy.If(tree, cond, deriveTree(thenp, exprType)(deriveExpr), deriveTree(elsep, exprType)(deriveExpr)).setType(exprType) + case Match(scrut, cases) => + treeCopy.Match(tree, scrut, cases.map(cd => deriveCaseDef(cd)(body => deriveTree(body, exprType)(deriveExpr)))).setType(exprType) case Block(stats, expr) => - treeCopy.Block(tree, stats, deriveTree(expr, exprType)(deriveExpr)) + treeCopy.Block(tree, stats, deriveTree(expr, exprType)(deriveExpr)).setType(exprType) case MatchEnd(ld) => ld.symbol.modifyInfo { case MethodType(params, _) => MethodType(params, exprType) diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index dbaa925bbcd8..8fa70e3ebf0d 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -58,6 +58,85 @@ class AnnotationDrivenAsync { assertEquals("foo", run(code)) } + @Test + @Ignore // TODO XASYNC + def testBoxedUnitNotImplemented(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.successful + |class A { + | def f = successful(this) + |} + |object Test { + | val data = List(("0", "0")) + | def test = async { + | val s1 = await(new A().f) + | s1.toString + | val s2 = await(s1.f) + | s2.toString + | val it = data.iterator + | while (it.hasNext) { + | val v = it.next() + | v match { + | case (x, y) => + | "".isEmpty + | val r1 = await(s1.f).toString + | val r2 = await(s1.f).toString + | (r1, r2) + | val it = Nil.iterator + | while (it.hasNext) { + | val v = it.next() + | val r = await(s1.f).equals(v) + | } + | } + | } + | } + |} + |""".stripMargin + assertEquals((), run(code)) + } + + @Test + def testMixedBagNPE(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.successful + |class A { + | def f = successful(this) + |} + |object Test { + | val data = List(("0", "0")) + | def test = async { + | val s1 = await(new A().f) + | s1.toString + | val s2 = await(s1.f) + | s2.toString + | val it = data.iterator + | while (it.hasNext) { + | val v = it.next() + | v match { + | case (x, y) => + | "".isEmpty + | val r1 = await(s2.f).toString + | val r2 = await(s2.f).toString + | (r1, r2) + | val it = List("").iterator + | while (it.hasNext) { + | val v = it.next() + | val r = await(s2.f).equals(v) + | } + | } + | } + | } + |} + |""".stripMargin + assertEquals((), run(code)) + } + @Test def patternTailPositionMatchEndCast(): Unit = { val code = @@ -234,6 +313,81 @@ class AnnotationDrivenAsync { assertEquals((), run(code)) } + @Test + def unitTypedValAwaitingWhileRhs(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def finish[T](t: T): T = t + | var continue = true + | def test = async { + | val x = while(continue) { + | await(f(())) + | continue = false + | () + | } + | "result" + | } + |} + |""".stripMargin + assertEquals("result", run(code)) + } + + @Test + def nestedBlock(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def finish[T](t: T): T = t + | var continue = true + | def condition = true + | def test = async { + | if (condition) { + | toString + | if (condition) { + | scala.runtime.BoxedUnit.UNIT + | } else { + | ( { if (condition) await(f("")) ; scala.runtime.BoxedUnit.UNIT } : scala.runtime.BoxedUnit ) + | } + | } else { + | identity(()) + | } + | } + |} + |""".stripMargin + assertEquals((), run(code)) + } + + @Test + def matchWithIf(): Unit = { + val code = + """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.partest.async.Async.{async, await} + |import Future.{successful => f} + | + |object Test { + | def finish[T](t: T): T = t + | def condition = true + | def scrut: Some[AnyRef] = Some("") + | def test = async { + | scrut match { + | case Some(_) => + | val a = "a" + | val x = if (condition) "then" else { await(f("")); "else" } + | identity((a, x)) + | } + | } + |} + |""".stripMargin + assertEquals(("a", "then"), run(code)) + } + @Test def testBooleanAndOr(): Unit = { val code = From d81ef533619402cfca2d0f85ab03f1bddd4ed26d Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 22 Apr 2020 11:12:51 +1000 Subject: [PATCH 80/94] Add an another example of a Writer/Option monad --- .../nsc/async/AnnotationDrivenAsync.scala | 19 +++++ .../scala/tools/nsc/async/OutputAwait.scala | 79 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 test/junit/scala/tools/nsc/async/OutputAwait.scala diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 8fa70e3ebf0d..267b75d63847 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -987,6 +987,25 @@ class AnnotationDrivenAsync { assertEquals(42, run(code)) } + @Test def testOutputMonad(): Unit = { + val code = + """ + |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global + |import scala.tools.nsc.async._ + |import OutputAwait._ + | + |object Test { + | def v1 = Output("v1", ("line" -> "1")) + | def v2 = Output("v2", ("line" -> "2"), ("foo", "bar")) + | def test: Output[String] = writing { + | value(v1) + value(v2) + | } + |} + |""".stripMargin + // If async and lambdalift phase both use the compilation units FreshNameCreator, we get foo$1 and foo$2, no clash! + assertEquals("Output(Some(v1v2),Map(line -> Vector(1, 2), foo -> Vector(bar)))", run(code).toString) + } + // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. @Test @Ignore diff --git a/test/junit/scala/tools/nsc/async/OutputAwait.scala b/test/junit/scala/tools/nsc/async/OutputAwait.scala new file mode 100644 index 000000000000..f3b46977a07b --- /dev/null +++ b/test/junit/scala/tools/nsc/async/OutputAwait.scala @@ -0,0 +1,79 @@ +package scala.tools.nsc +package async + +import scala.annotation.compileTimeOnly +import scala.collection.immutable.HashMap +import scala.collection.mutable +import scala.language.experimental.macros +import scala.reflect.macros.blackbox +import scala.tools.nsc.transform.async.StateAssigner +import scala.tools.partest.async.AsyncStateMachine + +object OutputAwait { + def writing[T](body: T): Output[T] = macro impl + @compileTimeOnly("[async] `value` must be enclosed in `writing`") + def value[T](output: Output[T]): T = ??? + def impl(c: blackbox.Context)(body: c.Tree): c.Tree = { + import c.universe._ + val awaitSym = typeOf[OutputAwait.type].decl(TermName("value")) + 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.OutputStateMachine { + ${mark(q"""override def apply(tr$$async: _root_.scala.Option[_root_.scala.AnyRef]) = ${body}""")} + } + new $name().start().asInstanceOf[${c.macroApplication.tpe}] + """ + } +} + +case class Output[T](value: Option[T], written: HashMap[String, Vector[Any]]) +object Output { + def apply[T](value: T, written: (String, Any)*): Output[T] = { + new Output(Some(value), toMultiMap[String, Any](written)) + } + + def mergeMultiMap[K, V](m1: HashMap[K, Vector[V]], m2: HashMap[K, Vector[V]]): HashMap[K, Vector[V]] = { + m1.merged(m2) { + case ((k1, v1), (k2, v2)) => (k1, v1 ++ v2) + } + } + + private def toMultiMap[K, V](written: Seq[(K, V)]): HashMap[K, Vector[V]] = { + val mutableMap = collection.mutable.HashMap[K, mutable.Builder[V, Vector[V]]]() + for ((k, v) <- written) mutableMap.getOrElseUpdate(k, Vector.newBuilder[V]) += v + val immutableMapBuilder = collection.immutable.HashMap.newBuilder[K, Vector[V]] + immutableMapBuilder ++= mutableMap.mapValues(_.result()) + immutableMapBuilder.result() + } +} + +abstract class OutputStateMachine extends AsyncStateMachine[Output[AnyRef], Option[AnyRef]] { + private var written = collection.immutable.HashMap[String, Vector[Any]]() + var result$async: Output[AnyRef] = _ + + // FSM translated method + def apply(tr$async: Option[AnyRef]): Unit + + // Required methods + protected var state$async: Int = StateAssigner.Initial + protected def completeFailure(t: Throwable): Unit = throw t + protected def completeSuccess(value: AnyRef): Unit = result$async = Output(Some(value), written) + protected def onComplete(f: Output[AnyRef]): Unit = ??? + protected def getCompleted(f: Output[AnyRef]): Option[AnyRef] = { + written = Output.mergeMultiMap(written, f.written) + f.value + } + protected def tryGet(tr: Option[AnyRef]): AnyRef = tr match { + case Some(value) => + value.asInstanceOf[AnyRef] + case None => + result$async = Output(None, written) + this // sentinel value to indicate the dispatch loop should exit. + } + def start(): Output[AnyRef] = { + apply(None) + result$async + } +} + From 502c127bb0d8b0b93be51384843e2f8367f43611 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 4 Jun 2020 14:16:08 +1000 Subject: [PATCH 81/94] Fixup remove stray .log files from partest suite --- test/async/run/late-run.log | 31 -------------------------- test/async/run/toughtype.scala-run.log | 7 ------ test/async/run/uncheckedBounds-run.log | 16 ------------- 3 files changed, 54 deletions(-) delete mode 100644 test/async/run/late-run.log delete mode 100644 test/async/run/toughtype.scala-run.log delete mode 100644 test/async/run/uncheckedBounds-run.log diff --git a/test/async/run/late-run.log b/test/async/run/late-run.log deleted file mode 100644 index 43d7223eea56..000000000000 --- a/test/async/run/late-run.log +++ /dev/null @@ -1,31 +0,0 @@ -java.lang.ClassNotFoundException: Test - at java.net.URLClassLoader.findClass(URLClassLoader.java:382) - at java.lang.ClassLoader.loadClass(ClassLoader.java:424) - at java.lang.ClassLoader.loadClass(ClassLoader.java:357) - at scala.tools.partest.nest.Runner.$anonfun$execTestInProcess$2(Runner.scala:267) - at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62) - at scala.Console$.withOut(Console.scala:167) - at scala.tools.partest.nest.StreamCapture$.$anonfun$capturingOutErr$2(StreamCapture.scala:32) - at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62) - at scala.Console$.withErr(Console.scala:196) - at scala.tools.partest.nest.StreamCapture$.$anonfun$capturingOutErr$1(StreamCapture.scala:31) - at scala.tools.partest.nest.Runner.$anonfun$execTestInProcess$1(Runner.scala:266) - at scala.tools.partest.nest.StreamCapture$.withExtraProperties(StreamCapture.scala:56) - at scala.tools.partest.nest.Runner.run$2(Runner.scala:262) - at scala.tools.partest.nest.Runner.$anonfun$execTestInProcess$3(Runner.scala:289) - at scala.tools.partest.nest.Runner.execTestInProcess(Runner.scala:289) - at scala.tools.partest.nest.Runner.exec$1(Runner.scala:691) - at scala.tools.partest.nest.Runner.$anonfun$runRunTest$1(Runner.scala:692) - at scala.tools.partest.nest.Runner.$anonfun$runTestCommon$1(Runner.scala:591) - at scala.tools.partest.nest.Runner.runInContext(Runner.scala:478) - at scala.tools.partest.nest.Runner.runTestCommon(Runner.scala:591) - at scala.tools.partest.nest.Runner.runRunTest(Runner.scala:692) - at scala.tools.partest.nest.Runner.run(Runner.scala:681) - at scala.tools.partest.nest.SuiteRunner.liftedTree1$1(Runner.scala:815) - at scala.tools.partest.nest.SuiteRunner.runTest(Runner.scala:815) - at scala.tools.partest.nest.SuiteRunner.$anonfun$runTestsForFiles$2(Runner.scala:833) - at scala.tools.partest.package$$anon$2.call(package.scala:134) - at java.util.concurrent.FutureTask.run(FutureTask.java:266) - at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) - at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) - at java.lang.Thread.run(Thread.java:748) diff --git a/test/async/run/toughtype.scala-run.log b/test/async/run/toughtype.scala-run.log deleted file mode 100644 index f80e2e844733..000000000000 --- a/test/async/run/toughtype.scala-run.log +++ /dev/null @@ -1,7 +0,0 @@ -toughtype.scala.scala:205: error: type mismatch; - found : scala.concurrent.Future[Unit] - required: scala.concurrent.Future[W] - Note: implicit method Something to do with List is not applicable here because it comes after the application point and it lacks an explicit result type - }(SomeExecutionContext) - ^ -one error found diff --git a/test/async/run/uncheckedBounds-run.log b/test/async/run/uncheckedBounds-run.log deleted file mode 100644 index 9159d1925e62..000000000000 --- a/test/async/run/uncheckedBounds-run.log +++ /dev/null @@ -1,16 +0,0 @@ -uncheckedBounds.scala:18: error: object TreeInterrogation is not a member of package async -import scala.async.TreeInterrogation - ^ -uncheckedBounds.scala:22: error: not found: value eval - eval( s""" - ^ -uncheckedBounds.scala:30: error: not found: value compileOptions - """, compileOptions = s"-cp ${toolboxClasspath} ") - ^ -uncheckedBounds.scala:34: error: not found: value eval - eval( s""" - ^ -uncheckedBounds.scala:44: error: not found: value compileOptions - """, compileOptions = s"-cp ${toolboxClasspath} ") - ^ -5 errors found From e63a133c65fd0c6c98688d2d80603d8e1ccb045b Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 4 Jun 2020 14:16:35 +1000 Subject: [PATCH 82/94] Move async run tests to jvm category Some will be made Scala.js compatible in a subsequent commit and moved back to run. --- test/async/{run => jvm}/anf.flags | 0 test/async/{run => jvm}/anf.scala | 0 test/async/{run => jvm}/await0.flags | 0 test/async/{run => jvm}/await0.scala | 0 test/async/{run => jvm}/block0.flags | 0 test/async/{run => jvm}/block0.scala | 0 test/async/{run => jvm}/block1.flags | 0 test/async/{run => jvm}/block1.scala | 0 test/async/{run => jvm}/concurrent_AfterRefchecksIssue.flags | 0 test/async/{run => jvm}/concurrent_AfterRefchecksIssue.scala | 0 .../async/{run => jvm}/concurrent_ArrayIndexOutOfBoundIssue.flags | 0 .../async/{run => jvm}/concurrent_ArrayIndexOutOfBoundIssue.scala | 0 test/async/{run => jvm}/concurrent_GenericTypeBoundaryIssue.check | 0 test/async/{run => jvm}/concurrent_GenericTypeBoundaryIssue.flags | 0 test/async/{run => jvm}/concurrent_GenericTypeBoundaryIssue.scala | 0 test/async/{run => jvm}/concurrent_MatchEndIssue.flags | 0 test/async/{run => jvm}/concurrent_MatchEndIssue.scala | 0 .../{run => jvm}/concurrent_NegativeArraySizeException.flags | 0 .../{run => jvm}/concurrent_NegativeArraySizeException.scala | 0 .../{run => jvm}/concurrent_NegativeArraySizeExceptionFine1.flags | 0 .../{run => jvm}/concurrent_NegativeArraySizeExceptionFine1.scala | 0 test/async/{run => jvm}/concurrent_ReturnTupleIssue.flags | 0 test/async/{run => jvm}/concurrent_ReturnTupleIssue.scala | 0 test/async/{run => jvm}/concurrent_fetch.check | 0 test/async/{run => jvm}/concurrent_fetch.flags | 0 test/async/{run => jvm}/concurrent_fetch.scala | 0 test/async/{run => jvm}/concurrent_patternAlternative.flags | 0 test/async/{run => jvm}/concurrent_patternAlternative.scala | 0 .../concurrent_patternAlternativeBothAnnotations.flags | 0 .../concurrent_patternAlternativeBothAnnotations.scala | 0 test/async/{run => jvm}/concurrent_polymorphicMethod.check | 0 test/async/{run => jvm}/concurrent_polymorphicMethod.flags | 0 test/async/{run => jvm}/concurrent_polymorphicMethod.scala | 0 test/async/{run => jvm}/concurrent_shadowing.check | 0 test/async/{run => jvm}/concurrent_shadowing.flags | 0 test/async/{run => jvm}/concurrent_shadowing.scala | 0 test/async/{run => jvm}/concurrent_shadowing0.flags | 0 test/async/{run => jvm}/concurrent_shadowing0.scala | 0 test/async/{run => jvm}/concurrent_shadowing2.flags | 0 test/async/{run => jvm}/concurrent_shadowing2.scala | 0 test/async/{run => jvm}/concurrent_shadowingRefinedTypes.flags | 0 test/async/{run => jvm}/concurrent_shadowingRefinedTypes.scala | 0 test/async/{run => jvm}/concurrent_test0.flags | 0 test/async/{run => jvm}/concurrent_test0.scala | 0 test/async/{run => jvm}/exceptions.flags | 0 test/async/{run => jvm}/exceptions.scala | 0 test/async/{run => jvm}/futures.check | 0 test/async/{run => jvm}/futures.flags | 0 test/async/{run => jvm}/futures.scala | 0 test/async/{run => jvm}/hygiene.flags | 0 test/async/{run => jvm}/hygiene.scala | 0 test/async/{run => jvm}/ifelse0.flags | 0 test/async/{run => jvm}/ifelse0.scala | 0 test/async/{run => jvm}/ifelse0_while.flags | 0 test/async/{run => jvm}/ifelse0_while.scala | 0 test/async/{run => jvm}/ifelse1.flags | 0 test/async/{run => jvm}/ifelse1.scala | 0 test/async/{run => jvm}/ifelse2.flags | 0 test/async/{run => jvm}/ifelse2.scala | 0 test/async/{run => jvm}/ifelse3.flags | 0 test/async/{run => jvm}/ifelse3.scala | 0 test/async/{run => jvm}/ifelse4.flags | 0 test/async/{run => jvm}/ifelse4.scala | 0 test/async/{run => jvm}/lazyval.flags | 0 test/async/{run => jvm}/lazyval.scala | 0 test/async/{run => jvm}/live.flags | 0 test/async/{run => jvm}/live.scala | 0 test/async/{run => jvm}/localclasses.flags | 0 test/async/{run => jvm}/localclasses.scala | 0 test/async/{run => jvm}/match0.flags | 0 test/async/{run => jvm}/match0.scala | 0 test/async/{run => jvm}/nesteddef.flags | 0 test/async/{run => jvm}/nesteddef.scala | 0 test/async/{run => jvm}/noawait.flags | 0 test/async/{run => jvm}/noawait.scala | 0 test/async/{run => jvm}/stackoverflow.flags | 0 test/async/{run => jvm}/stackoverflow.scala | 0 test/async/{run => jvm}/syncOptimization.flags | 0 test/async/{run => jvm}/syncOptimization.scala | 0 test/async/{run => jvm}/toughtype.flags | 0 test/async/{run => jvm}/toughtype.scala | 0 81 files changed, 0 insertions(+), 0 deletions(-) rename test/async/{run => jvm}/anf.flags (100%) rename test/async/{run => jvm}/anf.scala (100%) rename test/async/{run => jvm}/await0.flags (100%) rename test/async/{run => jvm}/await0.scala (100%) rename test/async/{run => jvm}/block0.flags (100%) rename test/async/{run => jvm}/block0.scala (100%) rename test/async/{run => jvm}/block1.flags (100%) rename test/async/{run => jvm}/block1.scala (100%) rename test/async/{run => jvm}/concurrent_AfterRefchecksIssue.flags (100%) rename test/async/{run => jvm}/concurrent_AfterRefchecksIssue.scala (100%) rename test/async/{run => jvm}/concurrent_ArrayIndexOutOfBoundIssue.flags (100%) rename test/async/{run => jvm}/concurrent_ArrayIndexOutOfBoundIssue.scala (100%) rename test/async/{run => jvm}/concurrent_GenericTypeBoundaryIssue.check (100%) rename test/async/{run => jvm}/concurrent_GenericTypeBoundaryIssue.flags (100%) rename test/async/{run => jvm}/concurrent_GenericTypeBoundaryIssue.scala (100%) rename test/async/{run => jvm}/concurrent_MatchEndIssue.flags (100%) rename test/async/{run => jvm}/concurrent_MatchEndIssue.scala (100%) rename test/async/{run => jvm}/concurrent_NegativeArraySizeException.flags (100%) rename test/async/{run => jvm}/concurrent_NegativeArraySizeException.scala (100%) rename test/async/{run => jvm}/concurrent_NegativeArraySizeExceptionFine1.flags (100%) rename test/async/{run => jvm}/concurrent_NegativeArraySizeExceptionFine1.scala (100%) rename test/async/{run => jvm}/concurrent_ReturnTupleIssue.flags (100%) rename test/async/{run => jvm}/concurrent_ReturnTupleIssue.scala (100%) rename test/async/{run => jvm}/concurrent_fetch.check (100%) rename test/async/{run => jvm}/concurrent_fetch.flags (100%) rename test/async/{run => jvm}/concurrent_fetch.scala (100%) rename test/async/{run => jvm}/concurrent_patternAlternative.flags (100%) rename test/async/{run => jvm}/concurrent_patternAlternative.scala (100%) rename test/async/{run => jvm}/concurrent_patternAlternativeBothAnnotations.flags (100%) rename test/async/{run => jvm}/concurrent_patternAlternativeBothAnnotations.scala (100%) rename test/async/{run => jvm}/concurrent_polymorphicMethod.check (100%) rename test/async/{run => jvm}/concurrent_polymorphicMethod.flags (100%) rename test/async/{run => jvm}/concurrent_polymorphicMethod.scala (100%) rename test/async/{run => jvm}/concurrent_shadowing.check (100%) rename test/async/{run => jvm}/concurrent_shadowing.flags (100%) rename test/async/{run => jvm}/concurrent_shadowing.scala (100%) rename test/async/{run => jvm}/concurrent_shadowing0.flags (100%) rename test/async/{run => jvm}/concurrent_shadowing0.scala (100%) rename test/async/{run => jvm}/concurrent_shadowing2.flags (100%) rename test/async/{run => jvm}/concurrent_shadowing2.scala (100%) rename test/async/{run => jvm}/concurrent_shadowingRefinedTypes.flags (100%) rename test/async/{run => jvm}/concurrent_shadowingRefinedTypes.scala (100%) rename test/async/{run => jvm}/concurrent_test0.flags (100%) rename test/async/{run => jvm}/concurrent_test0.scala (100%) rename test/async/{run => jvm}/exceptions.flags (100%) rename test/async/{run => jvm}/exceptions.scala (100%) rename test/async/{run => jvm}/futures.check (100%) rename test/async/{run => jvm}/futures.flags (100%) rename test/async/{run => jvm}/futures.scala (100%) rename test/async/{run => jvm}/hygiene.flags (100%) rename test/async/{run => jvm}/hygiene.scala (100%) rename test/async/{run => jvm}/ifelse0.flags (100%) rename test/async/{run => jvm}/ifelse0.scala (100%) rename test/async/{run => jvm}/ifelse0_while.flags (100%) rename test/async/{run => jvm}/ifelse0_while.scala (100%) rename test/async/{run => jvm}/ifelse1.flags (100%) rename test/async/{run => jvm}/ifelse1.scala (100%) rename test/async/{run => jvm}/ifelse2.flags (100%) rename test/async/{run => jvm}/ifelse2.scala (100%) rename test/async/{run => jvm}/ifelse3.flags (100%) rename test/async/{run => jvm}/ifelse3.scala (100%) rename test/async/{run => jvm}/ifelse4.flags (100%) rename test/async/{run => jvm}/ifelse4.scala (100%) rename test/async/{run => jvm}/lazyval.flags (100%) rename test/async/{run => jvm}/lazyval.scala (100%) rename test/async/{run => jvm}/live.flags (100%) rename test/async/{run => jvm}/live.scala (100%) rename test/async/{run => jvm}/localclasses.flags (100%) rename test/async/{run => jvm}/localclasses.scala (100%) rename test/async/{run => jvm}/match0.flags (100%) rename test/async/{run => jvm}/match0.scala (100%) rename test/async/{run => jvm}/nesteddef.flags (100%) rename test/async/{run => jvm}/nesteddef.scala (100%) rename test/async/{run => jvm}/noawait.flags (100%) rename test/async/{run => jvm}/noawait.scala (100%) rename test/async/{run => jvm}/stackoverflow.flags (100%) rename test/async/{run => jvm}/stackoverflow.scala (100%) rename test/async/{run => jvm}/syncOptimization.flags (100%) rename test/async/{run => jvm}/syncOptimization.scala (100%) rename test/async/{run => jvm}/toughtype.flags (100%) rename test/async/{run => jvm}/toughtype.scala (100%) diff --git a/test/async/run/anf.flags b/test/async/jvm/anf.flags similarity index 100% rename from test/async/run/anf.flags rename to test/async/jvm/anf.flags diff --git a/test/async/run/anf.scala b/test/async/jvm/anf.scala similarity index 100% rename from test/async/run/anf.scala rename to test/async/jvm/anf.scala diff --git a/test/async/run/await0.flags b/test/async/jvm/await0.flags similarity index 100% rename from test/async/run/await0.flags rename to test/async/jvm/await0.flags diff --git a/test/async/run/await0.scala b/test/async/jvm/await0.scala similarity index 100% rename from test/async/run/await0.scala rename to test/async/jvm/await0.scala diff --git a/test/async/run/block0.flags b/test/async/jvm/block0.flags similarity index 100% rename from test/async/run/block0.flags rename to test/async/jvm/block0.flags diff --git a/test/async/run/block0.scala b/test/async/jvm/block0.scala similarity index 100% rename from test/async/run/block0.scala rename to test/async/jvm/block0.scala diff --git a/test/async/run/block1.flags b/test/async/jvm/block1.flags similarity index 100% rename from test/async/run/block1.flags rename to test/async/jvm/block1.flags diff --git a/test/async/run/block1.scala b/test/async/jvm/block1.scala similarity index 100% rename from test/async/run/block1.scala rename to test/async/jvm/block1.scala diff --git a/test/async/run/concurrent_AfterRefchecksIssue.flags b/test/async/jvm/concurrent_AfterRefchecksIssue.flags similarity index 100% rename from test/async/run/concurrent_AfterRefchecksIssue.flags rename to test/async/jvm/concurrent_AfterRefchecksIssue.flags diff --git a/test/async/run/concurrent_AfterRefchecksIssue.scala b/test/async/jvm/concurrent_AfterRefchecksIssue.scala similarity index 100% rename from test/async/run/concurrent_AfterRefchecksIssue.scala rename to test/async/jvm/concurrent_AfterRefchecksIssue.scala diff --git a/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.flags b/test/async/jvm/concurrent_ArrayIndexOutOfBoundIssue.flags similarity index 100% rename from test/async/run/concurrent_ArrayIndexOutOfBoundIssue.flags rename to test/async/jvm/concurrent_ArrayIndexOutOfBoundIssue.flags diff --git a/test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala b/test/async/jvm/concurrent_ArrayIndexOutOfBoundIssue.scala similarity index 100% rename from test/async/run/concurrent_ArrayIndexOutOfBoundIssue.scala rename to test/async/jvm/concurrent_ArrayIndexOutOfBoundIssue.scala diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.check b/test/async/jvm/concurrent_GenericTypeBoundaryIssue.check similarity index 100% rename from test/async/run/concurrent_GenericTypeBoundaryIssue.check rename to test/async/jvm/concurrent_GenericTypeBoundaryIssue.check diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.flags b/test/async/jvm/concurrent_GenericTypeBoundaryIssue.flags similarity index 100% rename from test/async/run/concurrent_GenericTypeBoundaryIssue.flags rename to test/async/jvm/concurrent_GenericTypeBoundaryIssue.flags diff --git a/test/async/run/concurrent_GenericTypeBoundaryIssue.scala b/test/async/jvm/concurrent_GenericTypeBoundaryIssue.scala similarity index 100% rename from test/async/run/concurrent_GenericTypeBoundaryIssue.scala rename to test/async/jvm/concurrent_GenericTypeBoundaryIssue.scala diff --git a/test/async/run/concurrent_MatchEndIssue.flags b/test/async/jvm/concurrent_MatchEndIssue.flags similarity index 100% rename from test/async/run/concurrent_MatchEndIssue.flags rename to test/async/jvm/concurrent_MatchEndIssue.flags diff --git a/test/async/run/concurrent_MatchEndIssue.scala b/test/async/jvm/concurrent_MatchEndIssue.scala similarity index 100% rename from test/async/run/concurrent_MatchEndIssue.scala rename to test/async/jvm/concurrent_MatchEndIssue.scala diff --git a/test/async/run/concurrent_NegativeArraySizeException.flags b/test/async/jvm/concurrent_NegativeArraySizeException.flags similarity index 100% rename from test/async/run/concurrent_NegativeArraySizeException.flags rename to test/async/jvm/concurrent_NegativeArraySizeException.flags diff --git a/test/async/run/concurrent_NegativeArraySizeException.scala b/test/async/jvm/concurrent_NegativeArraySizeException.scala similarity index 100% rename from test/async/run/concurrent_NegativeArraySizeException.scala rename to test/async/jvm/concurrent_NegativeArraySizeException.scala diff --git a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.flags b/test/async/jvm/concurrent_NegativeArraySizeExceptionFine1.flags similarity index 100% rename from test/async/run/concurrent_NegativeArraySizeExceptionFine1.flags rename to test/async/jvm/concurrent_NegativeArraySizeExceptionFine1.flags diff --git a/test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala b/test/async/jvm/concurrent_NegativeArraySizeExceptionFine1.scala similarity index 100% rename from test/async/run/concurrent_NegativeArraySizeExceptionFine1.scala rename to test/async/jvm/concurrent_NegativeArraySizeExceptionFine1.scala diff --git a/test/async/run/concurrent_ReturnTupleIssue.flags b/test/async/jvm/concurrent_ReturnTupleIssue.flags similarity index 100% rename from test/async/run/concurrent_ReturnTupleIssue.flags rename to test/async/jvm/concurrent_ReturnTupleIssue.flags diff --git a/test/async/run/concurrent_ReturnTupleIssue.scala b/test/async/jvm/concurrent_ReturnTupleIssue.scala similarity index 100% rename from test/async/run/concurrent_ReturnTupleIssue.scala rename to test/async/jvm/concurrent_ReturnTupleIssue.scala diff --git a/test/async/run/concurrent_fetch.check b/test/async/jvm/concurrent_fetch.check similarity index 100% rename from test/async/run/concurrent_fetch.check rename to test/async/jvm/concurrent_fetch.check diff --git a/test/async/run/concurrent_fetch.flags b/test/async/jvm/concurrent_fetch.flags similarity index 100% rename from test/async/run/concurrent_fetch.flags rename to test/async/jvm/concurrent_fetch.flags diff --git a/test/async/run/concurrent_fetch.scala b/test/async/jvm/concurrent_fetch.scala similarity index 100% rename from test/async/run/concurrent_fetch.scala rename to test/async/jvm/concurrent_fetch.scala diff --git a/test/async/run/concurrent_patternAlternative.flags b/test/async/jvm/concurrent_patternAlternative.flags similarity index 100% rename from test/async/run/concurrent_patternAlternative.flags rename to test/async/jvm/concurrent_patternAlternative.flags diff --git a/test/async/run/concurrent_patternAlternative.scala b/test/async/jvm/concurrent_patternAlternative.scala similarity index 100% rename from test/async/run/concurrent_patternAlternative.scala rename to test/async/jvm/concurrent_patternAlternative.scala diff --git a/test/async/run/concurrent_patternAlternativeBothAnnotations.flags b/test/async/jvm/concurrent_patternAlternativeBothAnnotations.flags similarity index 100% rename from test/async/run/concurrent_patternAlternativeBothAnnotations.flags rename to test/async/jvm/concurrent_patternAlternativeBothAnnotations.flags diff --git a/test/async/run/concurrent_patternAlternativeBothAnnotations.scala b/test/async/jvm/concurrent_patternAlternativeBothAnnotations.scala similarity index 100% rename from test/async/run/concurrent_patternAlternativeBothAnnotations.scala rename to test/async/jvm/concurrent_patternAlternativeBothAnnotations.scala diff --git a/test/async/run/concurrent_polymorphicMethod.check b/test/async/jvm/concurrent_polymorphicMethod.check similarity index 100% rename from test/async/run/concurrent_polymorphicMethod.check rename to test/async/jvm/concurrent_polymorphicMethod.check diff --git a/test/async/run/concurrent_polymorphicMethod.flags b/test/async/jvm/concurrent_polymorphicMethod.flags similarity index 100% rename from test/async/run/concurrent_polymorphicMethod.flags rename to test/async/jvm/concurrent_polymorphicMethod.flags diff --git a/test/async/run/concurrent_polymorphicMethod.scala b/test/async/jvm/concurrent_polymorphicMethod.scala similarity index 100% rename from test/async/run/concurrent_polymorphicMethod.scala rename to test/async/jvm/concurrent_polymorphicMethod.scala diff --git a/test/async/run/concurrent_shadowing.check b/test/async/jvm/concurrent_shadowing.check similarity index 100% rename from test/async/run/concurrent_shadowing.check rename to test/async/jvm/concurrent_shadowing.check diff --git a/test/async/run/concurrent_shadowing.flags b/test/async/jvm/concurrent_shadowing.flags similarity index 100% rename from test/async/run/concurrent_shadowing.flags rename to test/async/jvm/concurrent_shadowing.flags diff --git a/test/async/run/concurrent_shadowing.scala b/test/async/jvm/concurrent_shadowing.scala similarity index 100% rename from test/async/run/concurrent_shadowing.scala rename to test/async/jvm/concurrent_shadowing.scala diff --git a/test/async/run/concurrent_shadowing0.flags b/test/async/jvm/concurrent_shadowing0.flags similarity index 100% rename from test/async/run/concurrent_shadowing0.flags rename to test/async/jvm/concurrent_shadowing0.flags diff --git a/test/async/run/concurrent_shadowing0.scala b/test/async/jvm/concurrent_shadowing0.scala similarity index 100% rename from test/async/run/concurrent_shadowing0.scala rename to test/async/jvm/concurrent_shadowing0.scala diff --git a/test/async/run/concurrent_shadowing2.flags b/test/async/jvm/concurrent_shadowing2.flags similarity index 100% rename from test/async/run/concurrent_shadowing2.flags rename to test/async/jvm/concurrent_shadowing2.flags diff --git a/test/async/run/concurrent_shadowing2.scala b/test/async/jvm/concurrent_shadowing2.scala similarity index 100% rename from test/async/run/concurrent_shadowing2.scala rename to test/async/jvm/concurrent_shadowing2.scala diff --git a/test/async/run/concurrent_shadowingRefinedTypes.flags b/test/async/jvm/concurrent_shadowingRefinedTypes.flags similarity index 100% rename from test/async/run/concurrent_shadowingRefinedTypes.flags rename to test/async/jvm/concurrent_shadowingRefinedTypes.flags diff --git a/test/async/run/concurrent_shadowingRefinedTypes.scala b/test/async/jvm/concurrent_shadowingRefinedTypes.scala similarity index 100% rename from test/async/run/concurrent_shadowingRefinedTypes.scala rename to test/async/jvm/concurrent_shadowingRefinedTypes.scala diff --git a/test/async/run/concurrent_test0.flags b/test/async/jvm/concurrent_test0.flags similarity index 100% rename from test/async/run/concurrent_test0.flags rename to test/async/jvm/concurrent_test0.flags diff --git a/test/async/run/concurrent_test0.scala b/test/async/jvm/concurrent_test0.scala similarity index 100% rename from test/async/run/concurrent_test0.scala rename to test/async/jvm/concurrent_test0.scala diff --git a/test/async/run/exceptions.flags b/test/async/jvm/exceptions.flags similarity index 100% rename from test/async/run/exceptions.flags rename to test/async/jvm/exceptions.flags diff --git a/test/async/run/exceptions.scala b/test/async/jvm/exceptions.scala similarity index 100% rename from test/async/run/exceptions.scala rename to test/async/jvm/exceptions.scala diff --git a/test/async/run/futures.check b/test/async/jvm/futures.check similarity index 100% rename from test/async/run/futures.check rename to test/async/jvm/futures.check diff --git a/test/async/run/futures.flags b/test/async/jvm/futures.flags similarity index 100% rename from test/async/run/futures.flags rename to test/async/jvm/futures.flags diff --git a/test/async/run/futures.scala b/test/async/jvm/futures.scala similarity index 100% rename from test/async/run/futures.scala rename to test/async/jvm/futures.scala diff --git a/test/async/run/hygiene.flags b/test/async/jvm/hygiene.flags similarity index 100% rename from test/async/run/hygiene.flags rename to test/async/jvm/hygiene.flags diff --git a/test/async/run/hygiene.scala b/test/async/jvm/hygiene.scala similarity index 100% rename from test/async/run/hygiene.scala rename to test/async/jvm/hygiene.scala diff --git a/test/async/run/ifelse0.flags b/test/async/jvm/ifelse0.flags similarity index 100% rename from test/async/run/ifelse0.flags rename to test/async/jvm/ifelse0.flags diff --git a/test/async/run/ifelse0.scala b/test/async/jvm/ifelse0.scala similarity index 100% rename from test/async/run/ifelse0.scala rename to test/async/jvm/ifelse0.scala diff --git a/test/async/run/ifelse0_while.flags b/test/async/jvm/ifelse0_while.flags similarity index 100% rename from test/async/run/ifelse0_while.flags rename to test/async/jvm/ifelse0_while.flags diff --git a/test/async/run/ifelse0_while.scala b/test/async/jvm/ifelse0_while.scala similarity index 100% rename from test/async/run/ifelse0_while.scala rename to test/async/jvm/ifelse0_while.scala diff --git a/test/async/run/ifelse1.flags b/test/async/jvm/ifelse1.flags similarity index 100% rename from test/async/run/ifelse1.flags rename to test/async/jvm/ifelse1.flags diff --git a/test/async/run/ifelse1.scala b/test/async/jvm/ifelse1.scala similarity index 100% rename from test/async/run/ifelse1.scala rename to test/async/jvm/ifelse1.scala diff --git a/test/async/run/ifelse2.flags b/test/async/jvm/ifelse2.flags similarity index 100% rename from test/async/run/ifelse2.flags rename to test/async/jvm/ifelse2.flags diff --git a/test/async/run/ifelse2.scala b/test/async/jvm/ifelse2.scala similarity index 100% rename from test/async/run/ifelse2.scala rename to test/async/jvm/ifelse2.scala diff --git a/test/async/run/ifelse3.flags b/test/async/jvm/ifelse3.flags similarity index 100% rename from test/async/run/ifelse3.flags rename to test/async/jvm/ifelse3.flags diff --git a/test/async/run/ifelse3.scala b/test/async/jvm/ifelse3.scala similarity index 100% rename from test/async/run/ifelse3.scala rename to test/async/jvm/ifelse3.scala diff --git a/test/async/run/ifelse4.flags b/test/async/jvm/ifelse4.flags similarity index 100% rename from test/async/run/ifelse4.flags rename to test/async/jvm/ifelse4.flags diff --git a/test/async/run/ifelse4.scala b/test/async/jvm/ifelse4.scala similarity index 100% rename from test/async/run/ifelse4.scala rename to test/async/jvm/ifelse4.scala diff --git a/test/async/run/lazyval.flags b/test/async/jvm/lazyval.flags similarity index 100% rename from test/async/run/lazyval.flags rename to test/async/jvm/lazyval.flags diff --git a/test/async/run/lazyval.scala b/test/async/jvm/lazyval.scala similarity index 100% rename from test/async/run/lazyval.scala rename to test/async/jvm/lazyval.scala diff --git a/test/async/run/live.flags b/test/async/jvm/live.flags similarity index 100% rename from test/async/run/live.flags rename to test/async/jvm/live.flags diff --git a/test/async/run/live.scala b/test/async/jvm/live.scala similarity index 100% rename from test/async/run/live.scala rename to test/async/jvm/live.scala diff --git a/test/async/run/localclasses.flags b/test/async/jvm/localclasses.flags similarity index 100% rename from test/async/run/localclasses.flags rename to test/async/jvm/localclasses.flags diff --git a/test/async/run/localclasses.scala b/test/async/jvm/localclasses.scala similarity index 100% rename from test/async/run/localclasses.scala rename to test/async/jvm/localclasses.scala diff --git a/test/async/run/match0.flags b/test/async/jvm/match0.flags similarity index 100% rename from test/async/run/match0.flags rename to test/async/jvm/match0.flags diff --git a/test/async/run/match0.scala b/test/async/jvm/match0.scala similarity index 100% rename from test/async/run/match0.scala rename to test/async/jvm/match0.scala diff --git a/test/async/run/nesteddef.flags b/test/async/jvm/nesteddef.flags similarity index 100% rename from test/async/run/nesteddef.flags rename to test/async/jvm/nesteddef.flags diff --git a/test/async/run/nesteddef.scala b/test/async/jvm/nesteddef.scala similarity index 100% rename from test/async/run/nesteddef.scala rename to test/async/jvm/nesteddef.scala diff --git a/test/async/run/noawait.flags b/test/async/jvm/noawait.flags similarity index 100% rename from test/async/run/noawait.flags rename to test/async/jvm/noawait.flags diff --git a/test/async/run/noawait.scala b/test/async/jvm/noawait.scala similarity index 100% rename from test/async/run/noawait.scala rename to test/async/jvm/noawait.scala diff --git a/test/async/run/stackoverflow.flags b/test/async/jvm/stackoverflow.flags similarity index 100% rename from test/async/run/stackoverflow.flags rename to test/async/jvm/stackoverflow.flags diff --git a/test/async/run/stackoverflow.scala b/test/async/jvm/stackoverflow.scala similarity index 100% rename from test/async/run/stackoverflow.scala rename to test/async/jvm/stackoverflow.scala diff --git a/test/async/run/syncOptimization.flags b/test/async/jvm/syncOptimization.flags similarity index 100% rename from test/async/run/syncOptimization.flags rename to test/async/jvm/syncOptimization.flags diff --git a/test/async/run/syncOptimization.scala b/test/async/jvm/syncOptimization.scala similarity index 100% rename from test/async/run/syncOptimization.scala rename to test/async/jvm/syncOptimization.scala diff --git a/test/async/run/toughtype.flags b/test/async/jvm/toughtype.flags similarity index 100% rename from test/async/run/toughtype.flags rename to test/async/jvm/toughtype.flags diff --git a/test/async/run/toughtype.scala b/test/async/jvm/toughtype.scala similarity index 100% rename from test/async/run/toughtype.scala rename to test/async/jvm/toughtype.scala From f9d7952d601a7841ede91539458d133c824cd10a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 4 Jun 2020 15:04:22 +1000 Subject: [PATCH 83/94] Add a Scala.js compatible smoke test. --- .../scala/tools/partest/async/OptionDsl.scala | 52 ++++++++++++++ test/async/run/smoketest.flags | 1 + test/async/run/smoketest.scala | 68 +++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/partest-extras/scala/tools/partest/async/OptionDsl.scala create mode 100644 test/async/run/smoketest.flags create mode 100644 test/async/run/smoketest.scala diff --git a/src/partest-extras/scala/tools/partest/async/OptionDsl.scala b/src/partest-extras/scala/tools/partest/async/OptionDsl.scala new file mode 100644 index 000000000000..2319c8f67ff1 --- /dev/null +++ b/src/partest-extras/scala/tools/partest/async/OptionDsl.scala @@ -0,0 +1,52 @@ +package scala.tools.partest +package async + +import scala.annotation.compileTimeOnly +import scala.language.experimental.macros +import scala.reflect.macros.blackbox + +object OptionAwait { + def optionally[T](body: T): Option[T] = macro impl + @compileTimeOnly("[async] `value` must be enclosed in `optionally`") + def value[T](option: Option[T]): T = ??? + def impl(c: blackbox.Context)(body: c.Tree): c.Tree = { + import c.universe._ + val awaitSym = typeOf[OptionAwait.type].decl(TermName("value")) + 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.OptionStateMachine { + ${mark(q"""override def apply(tr$$async: _root_.scala.Option[_root_.scala.AnyRef]) = ${body}""")} + } + new $name().start().asInstanceOf[${c.macroApplication.tpe}] + """ + } +} + +abstract class OptionStateMachine extends AsyncStateMachine[Option[AnyRef], Option[AnyRef]] { + var result$async: Option[AnyRef] = _ + + // FSM translated method + def apply(tr$async: Option[AnyRef]): Unit + + // Required methods + protected var state$async: Int = 0 + protected def completeFailure(t: Throwable): Unit = throw t + protected def completeSuccess(value: AnyRef): Unit = result$async = Some(value) + protected def onComplete(f: Option[AnyRef]): Unit = ??? + protected def getCompleted(f: Option[AnyRef]): Option[AnyRef] = { + f + } + protected def tryGet(tr: Option[AnyRef]): AnyRef = tr match { + case Some(value) => + value.asInstanceOf[AnyRef] + case None => + result$async = None + this // sentinel value to indicate the dispatch loop should exit. + } + def start(): Option[AnyRef] = { + apply(None) + result$async + } +} + diff --git a/test/async/run/smoketest.flags b/test/async/run/smoketest.flags new file mode 100644 index 000000000000..decae4aaa064 --- /dev/null +++ b/test/async/run/smoketest.flags @@ -0,0 +1 @@ +-Xasync \ No newline at end of file diff --git a/test/async/run/smoketest.scala b/test/async/run/smoketest.scala new file mode 100644 index 000000000000..713256b7fd04 --- /dev/null +++ b/test/async/run/smoketest.scala @@ -0,0 +1,68 @@ +import scala.tools.partest.async.OptionAwait._ +import org.junit.Assert._ + +// Scala.js compatible test suite for -Xasync that doesn't use Scala futures +object Test { + def main(args: Array[String]): Unit = { + testBasic() + testWhile() + testWhileNested() + ifExpression() + testPatternCascade() + } + + private def testBasic() = { + assertEquals(Some(3), optionally(value(Some(1)) + value(Some(2)))) + } + + private def testWhile() = { + assertEquals(Some((0 until 10).sum), optionally { + var i = 0 + var z = 0 + while (i < 10) { + z += value(Some(i)) + i += 1 + } + z + }) + } + + private def testWhileNested() = { + assertEquals(Some((0 until 10).sum + 10), optionally { + var i = 0 + var z = 0 + while (i < 10) { + z += value(Some(i)) + var j = 0 + while (j < value(Some(1))) { + z += value(Some(1)) + j += 1 + } + i += 1 + } + z + }) + } + + private def ifExpression() = { + assertEquals(Some(42), optionally { + if (if (value(Some(true))) value(Some(true)) else false) { + value(Some(if (value(Some(true))) 42 else ???)) + } else { + ??? + } + }) + } + + private def testPatternCascade() = { + assertEquals(Some(true), optionally { + (Some("x"), Option.empty[String]) match { + case (Some("y"), _) => ??? + case (Some("x"), Some(x)) if value(Some(true)) => ??? + case (Some("x"), None) if value(Some(false)) => ??? + case (Some("x"), None) if value(Some(true)) => true + case _ => ??? + } + }) + } +} \ No newline at end of file From 215f173444ab48f53766490ab17f22c19bd2340a Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 4 Jun 2020 15:22:34 +1000 Subject: [PATCH 84/94] Move some async test cases to partest --- .../async/CompletableFutureAwait.scala | 8 +- .../tools/partest}/async/OutputAwait.scala | 6 +- test/async/jvm/completable-future.flags | 1 + test/async/jvm/completable-future.scala | 19 + test/async/run/booleans.flags | 1 + test/async/run/booleans.scala | 19 + test/async/run/edge-cases.flags | 1 + test/async/run/edge-cases.scala | 327 +++++++++ test/async/run/output.flags | 1 + test/async/run/output.scala | 15 + .../nsc/async/AnnotationDrivenAsync.scala | 642 ------------------ 11 files changed, 389 insertions(+), 651 deletions(-) rename {test/junit/scala/tools/nsc => src/partest-extras/scala/tools/partest}/async/CompletableFutureAwait.scala (93%) rename {test/junit/scala/tools/nsc => src/partest-extras/scala/tools/partest}/async/OutputAwait.scala (94%) create mode 100644 test/async/jvm/completable-future.flags create mode 100644 test/async/jvm/completable-future.scala create mode 100644 test/async/run/booleans.flags create mode 100644 test/async/run/booleans.scala create mode 100644 test/async/run/edge-cases.flags create mode 100644 test/async/run/edge-cases.scala create mode 100644 test/async/run/output.flags create mode 100644 test/async/run/output.scala diff --git a/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala similarity index 93% rename from test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala rename to src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala index 13cb807fb712..5c6471428b30 100644 --- a/test/junit/scala/tools/nsc/async/CompletableFutureAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala @@ -1,15 +1,13 @@ -package scala.tools.nsc -package async +package scala.tools.partest.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.language.experimental.macros 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 { @@ -22,7 +20,7 @@ object CompletableFutureAwait { 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) { + final class $name extends _root_.scala.tools.partest.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}] diff --git a/test/junit/scala/tools/nsc/async/OutputAwait.scala b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala similarity index 94% rename from test/junit/scala/tools/nsc/async/OutputAwait.scala rename to src/partest-extras/scala/tools/partest/async/OutputAwait.scala index f3b46977a07b..7e45f251a815 100644 --- a/test/junit/scala/tools/nsc/async/OutputAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala @@ -1,5 +1,4 @@ -package scala.tools.nsc -package async +package scala.tools.partest.async import scala.annotation.compileTimeOnly import scala.collection.immutable.HashMap @@ -7,7 +6,6 @@ import scala.collection.mutable import scala.language.experimental.macros import scala.reflect.macros.blackbox import scala.tools.nsc.transform.async.StateAssigner -import scala.tools.partest.async.AsyncStateMachine object OutputAwait { def writing[T](body: T): Output[T] = macro impl @@ -19,7 +17,7 @@ object OutputAwait { 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.OutputStateMachine { + final class $name extends _root_.scala.tools.partest.async.OutputStateMachine { ${mark(q"""override def apply(tr$$async: _root_.scala.Option[_root_.scala.AnyRef]) = ${body}""")} } new $name().start().asInstanceOf[${c.macroApplication.tpe}] diff --git a/test/async/jvm/completable-future.flags b/test/async/jvm/completable-future.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/jvm/completable-future.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/jvm/completable-future.scala b/test/async/jvm/completable-future.scala new file mode 100644 index 000000000000..fdc7614ff7f3 --- /dev/null +++ b/test/async/jvm/completable-future.scala @@ -0,0 +1,19 @@ +import java.util.concurrent._ +import scala.tools.partest.async.CompletableFutureAwait._ + +object Test { + val pool = java.util.concurrent.Executors.newWorkStealingPool() + def f1 = CompletableFuture.supplyAsync(() => 1, pool) + + def main(args: Array[String]): Unit = { + val f = async(pool) { + var i = 0 + while (i < 100) { + i += await(f1) + } + i + } + val result = f.get() + assert(result == 100, result) + } +} \ No newline at end of file diff --git a/test/async/run/booleans.flags b/test/async/run/booleans.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/booleans.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/booleans.scala b/test/async/run/booleans.scala new file mode 100644 index 000000000000..22be02b6dfbf --- /dev/null +++ b/test/async/run/booleans.scala @@ -0,0 +1,19 @@ +import scala.tools.partest.async.OptionAwait._ +import org.junit.Assert._ + +object Test { + var counter = 0 + def ordered(i: Int, b: Boolean): Boolean = { assert(counter == i, (counter, i)); counter += 1; b } + def f[T](t: T) = Option(t) + def main(args: Array[String]): Unit = optionally { + counter = 0; assert(!(ordered(0, false) && value(f(ordered(-1, true))))) + counter = 0; assert(!(ordered(0, false) && value(f(ordered(-1, false))))) + counter = 0; assert( (ordered(0, true) && value(f(ordered( 1, true))))) + counter = 0; assert(!(ordered(0, true) && value(f(ordered( 1, false))))) + counter = 0; assert( (ordered(0, false) || value(f(ordered( 1, true))))) + counter = 0; assert(!(ordered(0, false) || value(f(ordered( 1, false))))) + counter = 0; assert( (ordered(0, true) || value(f(ordered(-1, false))))) + counter = 0; assert( (ordered(0, true) || value(f(ordered(-1, true))))) + () + } +} diff --git a/test/async/run/edge-cases.flags b/test/async/run/edge-cases.flags new file mode 100644 index 000000000000..decae4aaa064 --- /dev/null +++ b/test/async/run/edge-cases.flags @@ -0,0 +1 @@ +-Xasync \ No newline at end of file diff --git a/test/async/run/edge-cases.scala b/test/async/run/edge-cases.scala new file mode 100644 index 000000000000..0e833bdd26c7 --- /dev/null +++ b/test/async/run/edge-cases.scala @@ -0,0 +1,327 @@ +import scala.tools.partest.async.OptionAwait._ +import org.junit.Assert._ + +// Scala.js compatible test suite for -Xasync that doesn't use Scala futures +object Test { + def main(args: Array[String]): Unit = { + patternTailPosition() + mixedBagNPE() + patternTailPositionMatchEndCast() + awaitTyped() + avoidLiftingTryIntoExpression() + avoidLiftingTryIntoExpression2() + avoidWhileExprPosition() + whileExpr1() + genericUnitTypedAssert() + unitTypedValAwaitingWhileRhs() + nestedBlock() + matchWithIf() + testMatchBig() + testLiftedLazyVal() + testWhile1() + testWhile2() + testCaseClassLifting() + testNothingTypedExpr() + testLambdaLiftClash() + } + + private def patternTailPosition() = { + assertEquals(Some("foo"), optionally { + { + value(Some(1)) + "foo" match { + case x if "".isEmpty => x + } + }: AnyRef + }) + } + + def mixedBagNPE(): Unit = { + class A { + def f = Some(this) + } + val data = List(("0", "0")) + + assertEquals(Some(()), optionally { + val s1 = value(new A().f) + s1.toString + val s2 = value(s1.f) + s2.toString + val it = data.iterator + while (it.hasNext) { + val v = it.next() + v match { + case (x, y) => + "".isEmpty + val r1 = value(s2.f).toString + val r2 = value(s2.f).toString + (r1, r2) + val it = List("").iterator + while (it.hasNext) { + val v = it.next() + val r = value(s2.f).equals(v) + } + } + } + }) + } + + def patternTailPositionMatchEndCast(): Unit = { + trait A1 + trait B1 + trait B2 + trait Z1 + class C1 extends B1 with A1 with Z1 + class C2 extends A1 with B2 with Z1 + class C2A extends C2 + class C2B extends C2 + assertEquals(Some(null), optionally[Z1] { + val result = if ("foo".isEmpty) { + value(Option(1)) + null: Z1 + } else { + (0: Any) match { + case 0 => + value(Option(1)) + null: C2 with Z1 + case _ => + null: C1 with Z1 + } + } + result + }) + } + + def awaitTyped(): Unit = { + assertEquals(Some("msg: 0"), optionally { + (("msg: " + value(Option(0))): String).toString + }) + } + + def avoidLiftingTryIntoExpression(): Unit = { + assertEquals(Some(("body1", "body2")), optionally { + //var info = "" + var info = try { + "body1" + } catch { + case _: Throwable => "fallback" + } + var info1 = "" + info1 = try { + "body2" + } catch { + case _: Throwable => "fallback" + } + value(Option(0)) + (info, info1) + }) + } + + def avoidLiftingTryIntoExpression2(): Unit = { + assertEquals(Some("body"), optionally { + value(Some(1)) + try { + "body" + } catch { + case _: Throwable => + "catch" + } + }) + } + def avoidWhileExprPosition(): Unit = { + assertEquals(Some("result"), optionally { + if ("".isEmpty) { + () + } else { + var continue = true + while (continue) { + continue = value(Option(false)) + } + } + value(Option("result")) + }) + } + def whileExpr1(): Unit = { + var continue = true + + def test = optionally { + (value(test1), value(test2)) + } + + def test1 = optionally { + while (continue) { + continue = false + value(Option(())) + } + value(Option("result1")) + } + + def test2 = optionally { + value(Option("")) + while (continue) { + continue = false + value(Option(())) + } + value(Option("result2")) + } + + assertEquals(Some(("result1", "result2")), optionally { + (value(test1), value(test2)) + }) + } + + def genericUnitTypedAssert(): Unit = { + def finish[T](t: T): T = t + + assertEquals(Some(()), optionally { + finish(this match { case _ if "".isEmpty => (); case _ => value(Option(())) }) + }) + } + + def unitTypedValAwaitingWhileRhs(): Unit = { + def finish[T](t: T): T = t + + var continue = true + assertEquals(Some("result"), optionally { + val x = while (continue) { + value(Option(())) + continue = false + () + } + "result" + }) + } + + def nestedBlock(): Unit = { + def condition = true + + assertEquals(Some(()), optionally { + if (condition) { + toString + if (condition) { + scala.runtime.BoxedUnit.UNIT + } else { + ({ + if (condition) value(Option("")); scala.runtime.BoxedUnit.UNIT + }: scala.runtime.BoxedUnit) + } + } else { + identity(()) + } + }) + } + + def matchWithIf(): Unit = { + def finish[T](t: T): T = t + + def condition = true + + def scrut: Some[AnyRef] = Some("") + + assertEquals(Some(("a", "then")), optionally { + scrut match { + case Some(_) => + val a = "a" + val x = if (condition) "then" else { + value(Option("")); "else" + } + identity((a, x)) + } + }) + } + + def testMatchBig(): Unit = { + assertEquals(Some(5), optionally { + val x: Option[Either[Object, (String, String)]] = Some(Right(("a", "b"))) + x match { + case Some(Left(_)) => 1 + case Some(Right(("a", "c"))) => 2 + case Some(Right(("a", "e"))) => 3 + case Some(Right(("a", x))) if "ab".isEmpty => 4 + case Some(Right(("a", "b"))) => value(Option(5)) + case Some(Right((y, x))) if x == y => 6 + case Some(Right((_, _))) => value(Option(7)) + case None => 8 + } + }) + } + + def testLiftedLazyVal(): Unit = { + assertEquals(Some((1, 1, -1)), optionally { + var i = 0; + var z = 0 + lazy val foo = { + def ii = i; z = -1; ii + }; + value(Option(1)) + value(Option(2)); + i += 1; + (foo, foo, z) + }) + } + + def testWhile1(): Unit = { + assertEquals(Some(100), optionally { + var sum = 0 + var i = 0 + while (i < 5) { + var j = 0 + while (j < 5) { + sum += value(Some(i)) * value(Some(j)) + j += 1 + } + i += 1 + } + sum + }) + } + + def testWhile2(): Unit = { + assertEquals(Some(10), optionally { + var sum = 0 + var i = 0 + while (i < 5) { + sum += value(Option(i)) + i += 1 + } + sum + }) + } + + def testCaseClassLifting(): Unit = { + assertEquals(Some("bob"), optionally { + trait Base { def base = 0} + value(Option(0)) + case class Person(name: String) extends Base + val fut = Option("bob") + val x = Person(value(fut)) + x.base + assert(Person.getClass.getName == classOf[Person].getName + "$", (Person.getClass.getName, classOf[Person].getName)) + x.name + }) + } + + def testNothingTypedExpr(): Unit = { + try { + optionally { if ("".isEmpty) {value(Option("")); throw new RuntimeException("boo!")} else ??? } + assert(false) + } catch { + case re: RuntimeException => assert(re.getMessage == "boo!") + case _: Throwable => assert(false) + } + } + + // If async and lambdalift phase both use the compilation units FreshNameCreator, we get foo$1 and foo$2, no clash! + def testLambdaLiftClash(): Unit = { + assertEquals(Some(42), optionally { + def foo = 42 + value(Option("")); // so that the preceding def will be lifted to foo$N + + { + // lambdalift will later lift this to foo$N. + def foo = 43 + foo + }; + foo + }) + } +} diff --git a/test/async/run/output.flags b/test/async/run/output.flags new file mode 100644 index 000000000000..decae4aaa064 --- /dev/null +++ b/test/async/run/output.flags @@ -0,0 +1 @@ +-Xasync \ No newline at end of file diff --git a/test/async/run/output.scala b/test/async/run/output.scala new file mode 100644 index 000000000000..fab8152c2b32 --- /dev/null +++ b/test/async/run/output.scala @@ -0,0 +1,15 @@ +import scala.tools.partest.async.{OutputAwait, Output} +import scala.collection.immutable +import OutputAwait._ + +object Test { + def v1 = Output("v1", ("line" -> "1")) + def v2 = Output("v2", ("line" -> "2"), ("foo", "bar")) + def test: Output[String] = writing { + value(v1) + value(v2) + } + def main(args: Array[String]): Unit = { + val result = test + assert(result == Output(Some("v1v2"), immutable.HashMap("line" -> Vector("1", "2"), "foo" -> Vector("bar"))), result) + } +} diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 267b75d63847..9ee1c395edee 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -20,44 +20,6 @@ import scala.tools.nsc.transform.TypingTransformers import scala.tools.partest.async.AsyncStateMachine class AnnotationDrivenAsync { - @Test - def testBasicScalaConcurrent(): 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 test1: 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 patternTailPosition(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test = async { - | { - | await(f(1)) - | "foo" match { - | case x if "".isEmpty => x - | } - | }: AnyRef - | } - |} - |""".stripMargin - assertEquals("foo", run(code)) - } - @Test @Ignore // TODO XASYNC def testBoxedUnitNotImplemented(): Unit = { @@ -98,431 +60,6 @@ class AnnotationDrivenAsync { assertEquals((), run(code)) } - @Test - def testMixedBagNPE(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.successful - |class A { - | def f = successful(this) - |} - |object Test { - | val data = List(("0", "0")) - | def test = async { - | val s1 = await(new A().f) - | s1.toString - | val s2 = await(s1.f) - | s2.toString - | val it = data.iterator - | while (it.hasNext) { - | val v = it.next() - | v match { - | case (x, y) => - | "".isEmpty - | val r1 = await(s2.f).toString - | val r2 = await(s2.f).toString - | (r1, r2) - | val it = List("").iterator - | while (it.hasNext) { - | val v = it.next() - | val r = await(s2.f).equals(v) - | } - | } - | } - | } - |} - |""".stripMargin - assertEquals((), run(code)) - } - - @Test - def patternTailPositionMatchEndCast(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - |trait A1 - |trait B1 - |trait B2 - |trait Z1 - |class C1 extends B1 with A1 with Z1 - |class C2 extends A1 with B2 with Z1 - |class C2A extends C2 - |class C2B extends C2 - |object Test { - | def test = async[Z1] { - | val result = if ("foo".isEmpty) { - | await(f(1)) - | null : Z1 - | } else { - | (0: Any) match { - | case 0 => - | await(f(1)) - | null: C2 with Z1 - | case _ => - | null: C1 with Z1 - | } - | } - | result - | } - |} - |""".stripMargin - assertEquals(null, run(code)) - } - - @Test - def awaitTyped(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test = async {(("msg: " + await(f(0))): String).toString} - |} - |""".stripMargin - assertEquals("msg: 0", run(code)) - } - - @Test - def avoidLiftingTryIntoExpression(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test = async { - | //var info = "" - | var info = try { - | "body1" - | } catch { - | case _: Throwable => "fallback" - | } - | var info1 = "" - | info1 = try { - | "body2" - | } catch { - | case _: Throwable => "fallback" - | } - | - | await(f(0)) - | (info, info1) - | } - |} - |""".stripMargin - assertEquals(("body1", "body2"), run(code)) - } - - @Test - def avoidLiftingTryIntoExpression2(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test = async { - | await(f(1)) - | try { - | "body" - | } catch { - | case _: Throwable => - | "catch" - | } - | } - |} - |""".stripMargin - assertEquals("body", run(code)) - } - - @Test - def avoidWhileExprPosition(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test = async { - | if ("".isEmpty) { - | () - | } else { - | var continue = true - | while (continue) { - | continue = await(f(false)) - | } - | } - | await(f("result")) - | } - |} - |""".stripMargin - assertEquals("result", run(code)) - } - - @Test - def whileExpr1(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | var continue = true - | def test = async { (await(test1), await(test2)) } - | def test1 = async { - | while(continue) { - | continue = false - | await(f(())) - | } - | await(f("result1")) - | } - | def test2 = async { - | await(f("")) - | while(continue) { - | continue = false - | await(f(())) - | } - | await(f("result2")) - | } - |} - |""".stripMargin - assertEquals(("result1", "result2"), run(code)) - } - - @Test - def genericUnitTypedAssert(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def finish[T](t: T): T = t - | def test = async { - | finish(this match { case _ if "".isEmpty => (); case _ => await(f(())) }) - | } - |} - |""".stripMargin - assertEquals((), run(code)) - } - - @Test - def unitTypedValAwaitingWhileRhs(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def finish[T](t: T): T = t - | var continue = true - | def test = async { - | val x = while(continue) { - | await(f(())) - | continue = false - | () - | } - | "result" - | } - |} - |""".stripMargin - assertEquals("result", run(code)) - } - - @Test - def nestedBlock(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def finish[T](t: T): T = t - | var continue = true - | def condition = true - | def test = async { - | if (condition) { - | toString - | if (condition) { - | scala.runtime.BoxedUnit.UNIT - | } else { - | ( { if (condition) await(f("")) ; scala.runtime.BoxedUnit.UNIT } : scala.runtime.BoxedUnit ) - | } - | } else { - | identity(()) - | } - | } - |} - |""".stripMargin - assertEquals((), run(code)) - } - - @Test - def matchWithIf(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def finish[T](t: T): T = t - | def condition = true - | def scrut: Some[AnyRef] = Some("") - | def test = async { - | scrut match { - | case Some(_) => - | val a = "a" - | val x = if (condition) "then" else { await(f("")); "else" } - | identity((a, x)) - | } - | } - |} - |""".stripMargin - assertEquals(("a", "then"), run(code)) - } - - @Test - def testBooleanAndOr(): Unit = { - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | var counter = 0 - | def ordered(i: Int, b: Boolean): Boolean = { assert(counter == i, (counter, i)); counter += 1; b } - | def test: Future[Any] = async { - | counter = 0; assert(!(ordered(0, false) && await(f(ordered(-1, true))))) - | counter = 0; assert(!(ordered(0, false) && await(f(ordered(-1, false))))) - | counter = 0; assert( (ordered(0, true) && await(f(ordered( 1, true))))) - | counter = 0; assert(!(ordered(0, true) && await(f(ordered( 1, false))))) - | counter = 0; assert( (ordered(0, false) || await(f(ordered( 1, true))))) - | counter = 0; assert(!(ordered(0, false) || await(f(ordered( 1, false))))) - | counter = 0; assert( (ordered(0, true) || await(f(ordered(-1, false))))) - | counter = 0; assert( (ordered(0, true) || await(f(ordered(-1, true))))) - | () - | } - |} - |""".stripMargin - assertEquals((), 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.tools.partest.async.Async.{async, await} - | - | - |object Test { - | def test: Future[Int] = async { - | val x: Option[Either[Object, (String, String)]] = Some(Right(("a", "b"))) - | x match { - | case Some(Left(_)) => 1 - | case Some(Right(("a", "c"))) => 2 - | case Some(Right(("a", "e"))) => 3 - | case Some(Right(("a", x))) if "ab".isEmpty => 4 - | case Some(Right(("a", "b"))) => await(f(5)) - | case Some(Right((y, x))) if x == y => 6 - | case Some(Right((_, _))) => await(f(7)) - | case None => 8 - | } - | } - | def f(x: Int): Future[Int] = Future.successful(x) - |} - |""".stripMargin - assertEquals(5, run(code)) - } - @Test - def testMatchSmall(): 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 { - | val x: Option[Either[Object, (String, String)]] = Some(Right(("a", "b"))) - | (x: @unchecked) match { - | case Some(Right(("a", "b"))) => await(f(5)) - | case None => 8 - | } - | } - | def f(x: Int): Future[Int] = Future.successful(x) - |} - |""".stripMargin - assertEquals(5, run(code)) - } - - @Test def testBasicScalaConcurrentCapture(): Unit = { val code = @@ -538,99 +75,6 @@ class AnnotationDrivenAsync { assertEquals(("init_updated", 1, 2), run(code)) } - @Test - def testLiftedLazyVal(): 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, 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) } - | def f(x: Int): Future[Int] = Future.successful(x) - |} - |""".stripMargin - assertEquals((1, 1, -1), run(code)) - } - - @Test - def testWhile1(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - | - |object Test { - | def p[T](t: T): T = {println(t); t } - | def test: Future[Int] = async { - | var sum = 0 - | var i = 0 - | while (i < 5) { - | var j = 0 - | while (j < 5) { - | sum += await(f(i)) * await(f(j)) - | j += 1 - | } - | i += 1 - | } - | sum - | } - | def f(x: Int): Future[Int] = Future.successful(x) - |} - |""".stripMargin - assertEquals(100, run(code)) - } - - @Test - def testWhile2(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - | - |object Test { - | def p[T](t: T): T = {println(t); t } - | def test: Future[Int] = async { - | var sum = 0 - | var i = 0 - | while (i < 5) { - | sum += await(f(i)) - | i += 1 - | } - | sum - | } - | def f(x: Int): Future[Int] = Future.successful(x) - |} - |""".stripMargin - assertEquals(10, run(code)) - } - - @Test - def testCaseClassLifting(): Unit = { - // Note: this emits a warning under -Ydebug (which we sometimes manually set below in the compiler setup) - // https://github.com/scala/scala/pull/8750 will fix this. - val code = - """import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - import scala.tools.partest.async.Async.{async, await} - import Future.{successful => f} - object Test { - def test = async { - { - trait Base { def base = 0} - await(f(0)) - case class Person(name: String) extends Base - val fut = f("bob") - val x = Person(await(fut)) - x.base - assert(Person.getClass.getName == classOf[Person].getName + "$", (Person.getClass.getName, classOf[Person].getName)) - x.name - } - } - } - """ - assertEquals("bob", run(code)) - } - @Test def testScalaConcurrentAsyncNested(): Unit = { val code = @@ -798,48 +242,6 @@ 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)) - } - - @Test - def testNothingTypedExpr(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test: Future[Throwable] = async { if ("".isEmpty) {await(f("")); throw new RuntimeException("boo!")} else ??? }.failed - |} - |""".stripMargin - run(code) match { - case re: RuntimeException => assert(re.getMessage == "boo!") - case _ => Assert.fail() - } - } - @Test def testByNameOwner(): Unit = { val result = run( """ @@ -962,50 +364,6 @@ class AnnotationDrivenAsync { assertEquals(classOf[Array[String]], result.getClass) } - @Test def testLambdaLiftClash(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.partest.async.Async.{async, await} - |import Future.{successful => f} - | - |object Test { - | def test: Future[Int] = async { - | def foo = 42 - | await(f("")); // so that the preceding def will be lifted to foo$N - | - | { - | // lambdalift will later lift this to foo$N. - | def foo = 43 - | foo - | }; - | foo - | } - |} - |""".stripMargin - // If async and lambdalift phase both use the compilation units FreshNameCreator, we get foo$1 and foo$2, no clash! - assertEquals(42, run(code)) - } - - @Test def testOutputMonad(): Unit = { - val code = - """ - |import scala.concurrent._, duration.Duration, ExecutionContext.Implicits.global - |import scala.tools.nsc.async._ - |import OutputAwait._ - | - |object Test { - | def v1 = Output("v1", ("line" -> "1")) - | def v2 = Output("v2", ("line" -> "2"), ("foo", "bar")) - | def test: Output[String] = writing { - | value(v1) + value(v2) - | } - |} - |""".stripMargin - // If async and lambdalift phase both use the compilation units FreshNameCreator, we get foo$1 and foo$2, no clash! - assertEquals("Output(Some(v1v2),Map(line -> Vector(1, 2), foo -> Vector(bar)))", run(code).toString) - } - // Handy to debug the compiler or to collect code coverage statistics in IntelliJ. @Test @Ignore From 5455445ce4e080a999e109451002fccbbfc42759 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 8 Jun 2020 15:18:29 +1000 Subject: [PATCH 85/94] Tweak names in generated code - Remove '_' and line number from state machine classs names. - Rename `state$async` to `state`. --- src/partest-extras/scala/tools/partest/async/Async.scala | 6 ++++-- .../scala/tools/partest/async/AsyncStateMachine.scala | 4 ++-- .../tools/partest/async/CompletableFutureAwait.scala | 6 ++++-- .../scala/tools/partest/async/OptionDsl.scala | 6 ++++-- .../scala/tools/partest/async/OutputAwait.scala | 6 ++++-- src/reflect/scala/reflect/internal/StdNames.scala | 2 +- .../scala/tools/nsc/async/AnnotationDrivenAsync.scala | 8 ++++---- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/partest-extras/scala/tools/partest/async/Async.scala b/src/partest-extras/scala/tools/partest/async/Async.scala index 25ff026d6c53..e1c1b9d643e6 100644 --- a/src/partest-extras/scala/tools/partest/async/Async.scala +++ b/src/partest-extras/scala/tools/partest/async/Async.scala @@ -32,7 +32,7 @@ object Async { def mark(t: DefDef): Tree = { c.internal.markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty) } - val name = TypeName("stateMachine$async_" + body.pos.line) + val name = TypeName("stateMachine$async") 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}""")} @@ -51,7 +51,9 @@ abstract class AsyncAsMacroStateMachine(execContext: ExecutionContext) extends A def apply(tr$async: Try[AnyRef]): Unit // Required methods - protected var state$async: Int = StateAssigner.Initial + private[this] var state$async: Int = 0 + protected def state: Int = state$async + protected def state_=(s: Int): Unit = state$async = s // scala-async accidentally started catching NonFatal exceptions in: // https://github.com/scala/scala-async/commit/e3ff0382ae4e015fc69da8335450718951714982#diff-136ab0b6ecaee5d240cd109e2b17ccb2R411 diff --git a/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala b/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala index 2f71ca701392..245ad96385a9 100644 --- a/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala +++ b/src/partest-extras/scala/tools/partest/async/AsyncStateMachine.scala @@ -15,9 +15,9 @@ 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 + protected def state_=(i: Int): Unit /** Retrieve the current value of the state variable */ - protected def state$async: Int + protected def state: Int /** Complete the state machine with the given failure. */ protected def completeFailure(t: Throwable): Unit /** Complete the state machine with the given value. */ diff --git a/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala index 5c6471428b30..7320ffeeda9d 100644 --- a/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala @@ -18,7 +18,7 @@ object CompletableFutureAwait { 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) + val name = TypeName("stateMachine$async") q""" final class $name extends _root_.scala.tools.partest.async.CompletableFutureStateMachine($executor) { ${mark(q"""override def apply(tr$$async: _root_.scala.util.Try[_root_.scala.AnyRef]) = ${body}""")} @@ -45,7 +45,9 @@ abstract class CompletableFutureStateMachine(executor: Executor) extends AsyncSt def apply(tr$async: Try[AnyRef]): Unit // Required methods - protected var state$async: Int = StateAssigner.Initial + private[this] var state$async: Int = 0 + protected def state: Int = state$async + protected def state_=(s: Int): Unit = state$async = s 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) diff --git a/src/partest-extras/scala/tools/partest/async/OptionDsl.scala b/src/partest-extras/scala/tools/partest/async/OptionDsl.scala index 2319c8f67ff1..f1359e23b4e2 100644 --- a/src/partest-extras/scala/tools/partest/async/OptionDsl.scala +++ b/src/partest-extras/scala/tools/partest/async/OptionDsl.scala @@ -13,7 +13,7 @@ object OptionAwait { import c.universe._ val awaitSym = typeOf[OptionAwait.type].decl(TermName("value")) def mark(t: DefDef): Tree = c.internal.markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty) - val name = TypeName("stateMachine$$async_" + body.pos.line) + val name = TypeName("stateMachine$async") q""" final class $name extends _root_.scala.tools.partest.async.OptionStateMachine { ${mark(q"""override def apply(tr$$async: _root_.scala.Option[_root_.scala.AnyRef]) = ${body}""")} @@ -30,7 +30,9 @@ abstract class OptionStateMachine extends AsyncStateMachine[Option[AnyRef], Opti def apply(tr$async: Option[AnyRef]): Unit // Required methods - protected var state$async: Int = 0 + private[this] var state$async: Int = 0 + protected def state: Int = state$async + protected def state_=(s: Int): Unit = state$async = s protected def completeFailure(t: Throwable): Unit = throw t protected def completeSuccess(value: AnyRef): Unit = result$async = Some(value) protected def onComplete(f: Option[AnyRef]): Unit = ??? diff --git a/src/partest-extras/scala/tools/partest/async/OutputAwait.scala b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala index 7e45f251a815..69507178a1ac 100644 --- a/src/partest-extras/scala/tools/partest/async/OutputAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala @@ -15,7 +15,7 @@ object OutputAwait { import c.universe._ val awaitSym = typeOf[OutputAwait.type].decl(TermName("value")) def mark(t: DefDef): Tree = c.internal.markForAsyncTransform(c.internal.enclosingOwner, t, awaitSym, Map.empty) - val name = TypeName("stateMachine$$async_" + body.pos.line) + val name = TypeName("stateMachine$async") q""" final class $name extends _root_.scala.tools.partest.async.OutputStateMachine { ${mark(q"""override def apply(tr$$async: _root_.scala.Option[_root_.scala.AnyRef]) = ${body}""")} @@ -54,7 +54,9 @@ abstract class OutputStateMachine extends AsyncStateMachine[Output[AnyRef], Opti def apply(tr$async: Option[AnyRef]): Unit // Required methods - protected var state$async: Int = StateAssigner.Initial + private[this] var state$async: Int = 0 + protected def state: Int = state$async + protected def state_=(s: Int): Unit = state$async = s protected def completeFailure(t: Throwable): Unit = throw t protected def completeSuccess(value: AnyRef): Unit = result$async = Output(Some(value), written) protected def onComplete(f: Output[AnyRef]): Unit = ??? diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 7acb250fedc0..8d7871614912 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -841,7 +841,7 @@ trait StdNames { val awaitable : NameType = "awaitable$async" val completed : NameType = "completed$async" val stateMachine : NameType = "stateMachine$async" - val state : NameType = "state$async" + val state : NameType = "state" val tr : NameType = "tr$async" val t : NameType = "throwable$async" diff --git a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala index 9ee1c395edee..346f6c701364 100644 --- a/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala +++ b/test/junit/scala/tools/nsc/async/AnnotationDrivenAsync.scala @@ -494,7 +494,7 @@ abstract class AnnotationDrivenAsyncPlugin extends Plugin { val applyMethod = 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 name = TypeName("stateMachine$async") val wrapped = q""" class $name extends _root_.scala.tools.nsc.async.CustomFutureStateMachine { @@ -542,9 +542,9 @@ final class customAsync extends StaticAnnotation 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]); - private[this] var _state = 0 - protected def state$async: Int = _state - protected def state$async_=(i: Int) = _state = i + private[this] var state$async: Int = 0 + protected def state: Int = state$async + protected def state_=(s: Int): Unit = state$async = s def apply(tr$async: R[AnyRef]): Unit type F[A] = CustomFuture[A] From db3ea69f74143bddaf2d8d880f58a8ba47b15f82 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 9 Jun 2020 15:54:56 +1000 Subject: [PATCH 86/94] Avoid exotic use of Labels to gain Scala.js compat --- .../nsc/transform/async/ExprBuilder.scala | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 6345acfef6b7..94d66ccf2d53 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -22,9 +22,28 @@ trait ExprBuilder extends TransformUtils { private def stateAssigner = currentTransformState.stateAssigner private def labelDefStates = currentTransformState.labelDefStates + private object replaceResidualJumpsWithStateTransitions extends Transformer { + override def transform(tree: Tree): Tree = { + // TODO: This is only needed for Scala.js compatibility. + // See https://github.com/scala/scala/pull/8816#issuecomment-640725321 + // Perhaps make it conditional? + // if (global.currentRun.phaseNamed("jscode") != NoPhase) tree else + + super.transform(tree) match { + case ap @ Apply(i @ Ident(_), Nil) if isCaseLabel(i.symbol) || isMatchEndLabel(i.symbol) => + currentTransformState.labelDefStates.get(i.symbol) match { + case Some(state) => + Block(StateTransitionStyle.UpdateAndContinue.trees(state, new StateSet), typed(literalUnit)).setType(definitions.UnitTpe) + case None => ap + } + case tree => tree + } + } + } final class AsyncState(var stats: List[Tree], val state: Int, var nextStates: Array[Int], val isEmpty: Boolean) { - def mkHandlerCaseForState: CaseDef = - CaseDef(Literal(Constant(state)), EmptyTree, adaptToUnit(stats)) + def mkHandlerCaseForState: CaseDef = { + replaceResidualJumpsWithStateTransitions.transform(CaseDef(Literal(Constant(state)), EmptyTree, adaptToUnit(stats))).asInstanceOf[CaseDef] + } private def mkToString = s"AsyncState #$state, next = ${nextStates.toList}" override def toString: String = mkToString //+ " (was: " + initToString + ")" @@ -602,6 +621,7 @@ trait ExprBuilder extends TransformUtils { } } } + /** Update the state variable and jump to the the while loop that encloses the state machine. */ case object UpdateAndContinue extends StateTransitionStyle { def trees(nextState: Int, stateSet: StateSet): List[Tree] = { From 923e57b7bb47aba5f19887f6be7c5225b318eb1f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 10 Jun 2020 17:32:56 +1000 Subject: [PATCH 87/94] Add missing source file headers --- .../tools/partest/async/CompletableFutureAwait.scala | 12 ++++++++++++ .../scala/tools/partest/async/OptionDsl.scala | 12 ++++++++++++ .../scala/tools/partest/async/OutputAwait.scala | 12 ++++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala index 7320ffeeda9d..7b1b4532a119 100644 --- a/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala @@ -1,3 +1,15 @@ +/* + * 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 diff --git a/src/partest-extras/scala/tools/partest/async/OptionDsl.scala b/src/partest-extras/scala/tools/partest/async/OptionDsl.scala index f1359e23b4e2..0964c0740d55 100644 --- a/src/partest-extras/scala/tools/partest/async/OptionDsl.scala +++ b/src/partest-extras/scala/tools/partest/async/OptionDsl.scala @@ -1,3 +1,15 @@ +/* + * 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 package async diff --git a/src/partest-extras/scala/tools/partest/async/OutputAwait.scala b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala index 69507178a1ac..ca7657d199fa 100644 --- a/src/partest-extras/scala/tools/partest/async/OutputAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala @@ -1,3 +1,15 @@ +/* + * 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 scala.annotation.compileTimeOnly From b76a0ec4a140c5d0dc47e006cd7d697c196857fb Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 11 Jun 2020 14:20:38 +1000 Subject: [PATCH 88/94] Fix an edge case for value classes --- .../nsc/transform/async/AsyncPhase.scala | 3 ++- test/async/run/value-class.flags | 1 + test/async/run/value-class.scala | 22 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/async/run/value-class.flags create mode 100644 test/async/run/value-class.scala diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 54cb5ddcc8a8..806a54027e0d 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -45,8 +45,9 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran 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)) + // Wrap in `{ expr: Any }` to force value class boxing before calling `completeSuccess`, see test/async/run/value-class.scala deriveDefDef(method) { rhs => - Block(rhs.updateAttachment(SuppressPureExpressionWarning), Literal(Constant(()))) + Block(Typed(rhs.updateAttachment(SuppressPureExpressionWarning), TypeTree(definitions.AnyTpe)), Literal(Constant(()))) }.updateAttachment(ChangeOwnerAttachment(owner)) } diff --git a/test/async/run/value-class.flags b/test/async/run/value-class.flags new file mode 100644 index 000000000000..decae4aaa064 --- /dev/null +++ b/test/async/run/value-class.flags @@ -0,0 +1 @@ +-Xasync \ No newline at end of file diff --git a/test/async/run/value-class.scala b/test/async/run/value-class.scala new file mode 100644 index 000000000000..d0cc3820c521 --- /dev/null +++ b/test/async/run/value-class.scala @@ -0,0 +1,22 @@ +import scala.tools.partest.async.OptionAwait._ +import org.junit.Assert._ + +object Test { + def main(args: Array[String]): Unit = { + valueClass() + } + + trait T extends Any + class VC(val a: String) extends AnyVal with T + def VC(a: String) = new VC(a) + private def valueClass() = { + val t: T = optionally[T]{ + value(Some(())) + VC("") + }.get + } + def normal(): T = { + + VC("") + } +} From c94951ea8b8d4ef8b7d4d9384b47f1816530d956 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 12 Jun 2020 10:28:42 +1000 Subject: [PATCH 89/94] Fix an edge case for while in expr position --- .../tools/nsc/transform/async/AnfTransform.scala | 12 +++++++++++- test/async/run/edge-cases.scala | 14 ++++++++++++++ test/async/run/value-class.scala | 4 ---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 2dd59c0bafcd..2b1afa242ca2 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -123,7 +123,17 @@ private[async] trait AnfTransform extends TransformUtils { // However, we let `onTail` add the expr to `currentStats` (that was more efficient than using `ts.dropRight(1).foreach(addToStats)`) // Compensate by removing it from the buffer and returning the expr. - currentStats.remove(currentStats.size - 1) + // If the expr it itself a unit-typed LabelDef, move it to the stats and leave a Unit expression in its place + // to make life easier for transformMatchOrIf + currentStats.remove(currentStats.size - 1) match { + case ld: LabelDef if ld.tpe.typeSymbol == definitions.BoxedUnitClass => + currentStats += ld + literalBoxedUnit + case ld: LabelDef if ld.tpe.typeSymbol == definitions.UnitClass => + currentStats += ld + literalUnit + case expr => expr + } case ValDef(mods, name, tpt, rhs) => atOwner(tree.symbol) { // Capture current cursor of a non-empty `stats` buffer so we can efficiently restrict the diff --git a/test/async/run/edge-cases.scala b/test/async/run/edge-cases.scala index 0e833bdd26c7..37d76e8f9eb1 100644 --- a/test/async/run/edge-cases.scala +++ b/test/async/run/edge-cases.scala @@ -23,6 +23,7 @@ object Test { testCaseClassLifting() testNothingTypedExpr() testLambdaLiftClash() + testWhileExprInIf() } private def patternTailPosition() = { @@ -324,4 +325,17 @@ object Test { foo }) } + + def testWhileExprInIf(): Unit = { + val t: Any = optionally { + if ("".isEmpty) { + () + } else { + val it = Nil.iterator + while (it.hasNext) { + value(Some(it.next())) + } + } + }.get + } } diff --git a/test/async/run/value-class.scala b/test/async/run/value-class.scala index d0cc3820c521..7d8ff1a2467f 100644 --- a/test/async/run/value-class.scala +++ b/test/async/run/value-class.scala @@ -15,8 +15,4 @@ object Test { VC("") }.get } - def normal(): T = { - - VC("") - } } From 7f22119a95b4e72713a04a707d8fd11c5d64b2b0 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 15 Jun 2020 10:18:53 +1000 Subject: [PATCH 90/94] Rework async expression wrapping Wrap the async expression in `locally { ... }` to force value class boxing. This seems to work better than `{ ... }: Any`, which ends up with `Function` trees typed with `Any` which violoates an assumption in `delambdafy`. --- .../scala/tools/nsc/transform/UnCurry.scala | 2 +- .../nsc/transform/async/AsyncAnalysis.scala | 5 ++++- .../tools/nsc/transform/async/AsyncPhase.scala | 4 ++-- .../scala/reflect/internal/Definitions.scala | 1 + .../scala/reflect/internal/StdNames.scala | 3 +++ src/reflect/scala/reflect/internal/Trees.scala | 9 ++++++++- test/async/run/lambda.flags | 1 + test/async/run/lambda.scala | 16 ++++++++++++++++ 8 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 test/async/run/lambda.flags create mode 100644 test/async/run/lambda.scala diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ae360eb75715..ec1f14ebdf02 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -408,7 +408,7 @@ abstract class UnCurry extends InfoTransform /* Transform tree `t` to { def f = t; f } where `f` is a fresh name */ def liftTree(tree: Tree) = { debuglog("lifting tree at: " + (tree.pos)) - val sym = currentOwner.newMethod(unit.freshTermName("liftedTree"), tree.pos) + val sym = currentOwner.newMethod(unit.freshTermName(nme.LIFTED_TREE), tree.pos, Flag.ARTIFACT) sym.setInfo(MethodType(List(), tree.tpe)) tree.changeOwner(currentOwner, sym) localTyper.typedPos(tree.pos)(Block( diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index d593876380a5..3ce724817552 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -40,7 +40,10 @@ trait AsyncAnalysis extends TransformUtils { } override def nestedMethod(defDef: DefDef): Unit = { - reportUnsupportedAwait(defDef, "nested method") + if (defDef.symbol.isArtifact && defDef.name.startsWith(nme.LIFTED_TREE)) + reportUnsupportedAwait(defDef, "try/catch") + else + reportUnsupportedAwait(defDef, "nested method") } override def byNameArgument(arg: Tree): Unit = { diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 806a54027e0d..f2f089b21028 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -47,7 +47,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran method.updateAttachment(new AsyncAttachment(awaitMethod, postAnfTransform, stateDiagram)) // Wrap in `{ expr: Any }` to force value class boxing before calling `completeSuccess`, see test/async/run/value-class.scala deriveDefDef(method) { rhs => - Block(Typed(rhs.updateAttachment(SuppressPureExpressionWarning), TypeTree(definitions.AnyTpe)), Literal(Constant(()))) + Block(Apply(gen.mkAttributedRef(definitions.Predef_locally), rhs :: Nil), Literal(Constant(()))) }.updateAttachment(ChangeOwnerAttachment(owner)) } @@ -116,7 +116,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran 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) + case blk@Block(Apply(qual, body :: Nil) :: Nil, Literal(Constant(()))) => body } atOwner(dd, dd.symbol) { diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 4964a4e579fe..9045e0534ca8 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -378,6 +378,7 @@ trait Definitions extends api.StandardDefinitions { lazy val PredefModule = requiredModule[scala.Predef.type] def Predef_wrapArray(tp: Type) = getMemberMethod(PredefModule, wrapArrayMethodName(tp)) def Predef_??? = getMemberMethod(PredefModule, nme.???) + def Predef_locally = getMemberMethod(PredefModule, nme.locally) def isPredefMemberNamed(sym: Symbol, name: Name) = ( (sym.name == name) && (sym.owner == PredefModule.moduleClass) ) diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 8d7871614912..d1ecc376ea1f 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -130,6 +130,7 @@ trait StdNames { val NESTED_IN_LAMBDA: String = NESTED_IN + DELAMBDAFY_LAMBDA_CLASS_NAME.toString.replace("$", "") val NON_LOCAL_RETURN_KEY_STRING: String = "nonLocalReturnKey" + val LIFTED_TREE: String = "liftedTree" /** * Ensures that name mangling does not accidentally make a class respond `true` to any of @@ -745,6 +746,7 @@ trait StdNames { val lang: NameType = "lang" val length: NameType = "length" val lengthCompare: NameType = "lengthCompare" + val locally: NameType = "locally" val longHash: NameType = "longHash" val macroContext : NameType = "c" val main: NameType = "main" @@ -1126,6 +1128,7 @@ trait StdNames { val reflParamsCacheName: NameType = "reflParams$Cache" val reflMethodName: NameType = "reflMethod$Method" val argument: NameType = "" + val liftedTree: String = "liftedTree" } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index 439c3679fe82..f5aae5010beb 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -478,7 +478,14 @@ trait Trees extends api.Trees { case class ArrayValue(elemtpt: Tree, elems: List[Tree]) extends TermTree case class Function(vparams: List[ValDef], body: Tree) - extends SymTree with TermTree with FunctionApi + extends SymTree with TermTree with FunctionApi { + + override def tpe_=(tp: Type): Unit = { + if (!definitions.isFunctionType(tp)) + new Throwable().printStackTrace() + super.setType(tp) + } + } object Function extends FunctionExtractor case class Assign(lhs: Tree, rhs: Tree) diff --git a/test/async/run/lambda.flags b/test/async/run/lambda.flags new file mode 100644 index 000000000000..e071b133c07a --- /dev/null +++ b/test/async/run/lambda.flags @@ -0,0 +1 @@ +-Xasync diff --git a/test/async/run/lambda.scala b/test/async/run/lambda.scala new file mode 100644 index 000000000000..51d3449a6b87 --- /dev/null +++ b/test/async/run/lambda.scala @@ -0,0 +1,16 @@ +import scala.tools.partest.async.OptionAwait._ +//import org.junit.Assert._ + +object Test { + def main(args: Array[String]): Unit = { + lambda() + } + + private def lambda() = { + val l = optionally{ + value(Some(1)) + (a: String) => a.length + }.get + //assertEquals(1, l("a")) + } +} From 230684dd5ef771d54017c3ee170ad7f6a75533f6 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Mon, 15 Jun 2020 10:34:52 +1000 Subject: [PATCH 91/94] Avoid generating dead code for terminal state --- .../tools/nsc/transform/async/ExprBuilder.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 94d66ccf2d53..74b1d98197e0 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -119,8 +119,8 @@ trait ExprBuilder extends TransformUtils { def build(nextState: Int, style: StateTransitionStyle): AsyncState = { assert(!built) built = true - // Record whether this state was free of meaningful stats (exclkuding unit literals which creep in after - // the ANF and state maching transforms and the state transition code added bekow. + // Record whether this state was free of meaningful stats (excluding unit literals which creep in after + // the ANF and state machine transforms and the state transition code added below. // // Empty stats that have a single successor state will be eliminated in `filterStates`. val isEmpty = !style.isInstanceOf[StateTransitionStyle.UpdateAndAwait] && stats.forall(isLiteralUnit) @@ -179,8 +179,10 @@ trait ExprBuilder extends TransformUtils { private var building = true def build: List[AsyncState] = { - try statesMap.values.toList - finally building = false + val result = + try statesMap.values.toList + finally building = false + result } def outerIterator: Iterator[AsyncBlockBuilder] = Iterator.iterate(this)(_.outer.orNull).takeWhile(_ ne null) @@ -439,7 +441,7 @@ trait ExprBuilder extends TransformUtils { dotBuilder.toString } - lazy val asyncStates: List[AsyncState] = filterStates(blockBuilder.build) + lazy val asyncStates: List[AsyncState] = filterStates(blockBuilder.build) // drop the terminal state which contains no code /** * Builds the definition of the `apply(tr: Try)` method. @@ -448,9 +450,10 @@ trait ExprBuilder extends TransformUtils { 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)))) + val asyncStatesInit = asyncStates.init // drop the terminal state which has no code. val body = typed(Match(stateMemberRef, - asyncStates.map(_.mkHandlerCaseForState) ++ + asyncStatesInit.map(_.mkHandlerCaseForState) ++ List(CaseDef(Ident(nme.WILDCARD), EmptyTree, throww)))) From 0e27334b09e0f6a0750d2492bf686db51f5392b0 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 12 Jun 2020 15:30:34 +0200 Subject: [PATCH 92/94] remove old ThicketTransformer --- .../nsc/transform/async/TransformUtils.scala | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala index 23bb2616b3a7..06b515538efc 100644 --- a/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala +++ b/src/compiler/scala/tools/nsc/transform/async/TransformUtils.scala @@ -107,69 +107,6 @@ private[async] trait TransformUtils extends AsyncTransformStates { } } - private object ThicketAttachment - abstract class ThicketTransformer(initLocalTyper: analyzer.Typer) extends TypingTransformer(initLocalTyper) { - private def expandThicket(t: Tree): List[Tree] = t match { - case Block(stats, expr) if t.attachments.containsElement(ThicketAttachment) => - stats :+ expr - case _ => t :: Nil - } - - def apply(tree: Tree): List[Tree] = expandThicket(transform(tree)) - - protected def Thicket(stats: List[Tree], expr: Tree): Tree = { - Block(stats, expr).updateAttachment(ThicketAttachment) - } - protected def Thicket(block: Block): Tree = { - block.updateAttachment(ThicketAttachment) - } - - override def transform(tree: Tree): Tree = tree match { - case Block(stats, expr) => - val transformedStats = transformTrees(stats) - val transformedExpr = transform(expr) - def expandStats(expanded: mutable.ListBuffer[Tree]) = transformedStats.foreach { - case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => - expanded ++= stats - expanded += expr - case t => - expanded += t - } - def expandExpr(expanded: mutable.ListBuffer[Tree]): Tree = transformedExpr match { - case blk @ Block(stats, expr) if blk.attachments.containsElement(ThicketAttachment) => - expanded ++= stats - expr - case t => - t - } - - if (stats eq transformedStats) { - if (expr eq transformedExpr) tree - else { - val expanded = new mutable.ListBuffer[Tree] - expanded ++= transformedStats - val expr1 = expandExpr(expanded) - if (expanded.isEmpty) - treeCopy.Block(tree, transformedStats, expr1) - else - treeCopy.Block(tree, expanded.toList, expr1) - } - } else { - val expanded = new mutable.ListBuffer[Tree] - if (expr eq transformedExpr) { - expandStats(expanded) - treeCopy.Block(tree, expanded.toList, expr) - } else { - expandStats(expanded) - val expr1 = expandExpr(expanded) - treeCopy.Block(tree, expanded.toList, expr1) - } - } - case _ => - super.transform(tree) - } - } - /** Descends into the regions of the tree that are subject to the * translation to a state machine by `async`. When a nested template, * function, or by-name argument is encountered, the descent stops, From ae91a3ca236df18eb304179e52013129c6b51227 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 17 Jun 2020 15:36:23 +1000 Subject: [PATCH 93/94] Address review comments (remove dead/debug code, +comments) --- src/compiler/scala/tools/nsc/Global.scala | 5 ++--- .../scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala | 1 - src/compiler/scala/tools/nsc/transform/UnCurry.scala | 1 - .../tools/nsc/transform/async/AsyncAnalysis.scala | 1 - .../scala/tools/nsc/transform/async/AsyncPhase.scala | 1 + .../scala/tools/nsc/typechecker/Implicits.scala | 5 +---- src/compiler/scala/tools/reflect/FastTrack.scala | 3 --- .../scala/tools/partest/async/Async.scala | 1 - .../tools/partest/async/CompletableFutureAwait.scala | 1 - .../scala/tools/partest/async/OutputAwait.scala | 1 - src/reflect/scala/reflect/api/Internals.scala | 11 +++++++++++ src/reflect/scala/reflect/internal/Phase.scala | 1 - .../scala/reflect/internal/StdAttachments.scala | 2 -- src/reflect/scala/reflect/internal/TreeInfo.scala | 1 - src/reflect/scala/reflect/internal/Trees.scala | 10 +--------- .../scala/reflect/runtime/JavaUniverseForce.scala | 1 - 16 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 74b96d2f09c1..cc15208bc866 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -579,7 +579,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) object async extends { val global: Global.this.type = Global.this val runsAfter = List("posterasure") - val runsRightAfter = Some("posterasure") + val runsRightAfter = None } with AsyncPhase // phaseName = "lambdalift" @@ -1013,7 +1013,7 @@ class Global(var currentSettings: Settings, reporter0: Reporter) definitions.isDefinitionsInitialized && rootMirror.isMirrorInitialized ) - override def isPastTyper = globalPhase != null && isPast(currentRun.typerPhase) + override def isPastTyper = isPast(currentRun.typerPhase) def isPast(phase: Phase) = ( (curRun ne null) && isGlobalInitialized // defense against init order issues @@ -1350,7 +1350,6 @@ 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/backend/jvm/opt/BoxUnbox.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala index 1311257b69ca..b5bd0546c2a5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala @@ -21,7 +21,6 @@ import scala.tools.asm.Opcodes._ import scala.tools.asm.Type import scala.tools.asm.tree._ import scala.tools.nsc.backend.jvm.BTypes.InternalName -import scala.tools.nsc.backend.jvm.analysis.BackendUtils import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ abstract class BoxUnbox { diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ec1f14ebdf02..4aa269385c4c 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -20,7 +20,6 @@ import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.reflect.internal.util.ListOfNil import PartialFunction.cond -import scala.reflect.NameTransformer /* */ /** - uncurry all symbol and tree types (@see UnCurryPhase) -- this includes normalizing all proper types. diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala index 3ce724817552..2fae40c492e1 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncAnalysis.scala @@ -13,7 +13,6 @@ package scala.tools.nsc.transform.async import scala.collection.mutable.ListBuffer -import scala.reflect.NameTransformer trait AsyncAnalysis extends TransformUtils { import global._ diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index f2f089b21028..7a0deeaf2de2 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -35,6 +35,7 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran /** * Mark the given method as requiring an async transform. + * Refer to documentation in the public API that forwards to this method in src/reflect/scala/reflect/api/Internals.scala */ final def markForAsyncTransform(owner: Symbol, method: DefDef, awaitMethod: Symbol, config: Map[String, AnyRef]): DefDef = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 864ba4c55a98..c2322dc5cb7c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -327,10 +327,7 @@ trait Implicits { */ def memberWildcardType(name: Name, tp: Type) = { val result = refinedType(List(WildcardType), NoSymbol) - val owner = result.typeSymbol orElse { // TODO async (when after erasure?)... - val clazz = NoSymbol.newRefinementClass(NoPosition) - clazz setInfo RefinedType(Nil, newScope, clazz) - } + val owner = result.typeSymbol (name match { case x: TermName => owner.newMethod(x) case x: TypeName => owner.newAbstractType(x) diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 014d7308191d..3bdd34be6e01 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -44,9 +44,6 @@ class FastTrack[MacrosAndAnalyzer <: Macros with Analyzer](val macros: MacrosAnd private def makeWhitebox(sym: Symbol)(pf: PartialFunction[Applied, MacroContext => Tree]) = sym -> new FastTrackEntry(pf, isBlackbox = false) - private def makeBlackBoxIfExists(sym_pf: (Symbol, PartialFunction[Applied, MacroContext => Tree])) = - sym_pf match { case (sym, _) if !sym.exists => Map.empty case (sym, pf) => Map(makeBlackbox(sym)(pf))} - final class FastTrackEntry(pf: PartialFunction[Applied, MacroContext => Tree], val isBlackbox: Boolean) extends (MacroArgs => Any) { def validate(tree: Tree) = pf isDefinedAt Applied(tree) def apply(margs: MacroArgs): margs.c.Expr[Nothing] = { diff --git a/src/partest-extras/scala/tools/partest/async/Async.scala b/src/partest-extras/scala/tools/partest/async/Async.scala index e1c1b9d643e6..3fa64a92f845 100644 --- a/src/partest-extras/scala/tools/partest/async/Async.scala +++ b/src/partest-extras/scala/tools/partest/async/Async.scala @@ -18,7 +18,6 @@ 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 { diff --git a/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala index 7b1b4532a119..4327722b01e9 100644 --- a/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/CompletableFutureAwait.scala @@ -19,7 +19,6 @@ import java.util.function.BiConsumer import scala.annotation.compileTimeOnly import scala.language.experimental.macros import scala.reflect.macros.blackbox -import scala.tools.nsc.transform.async.StateAssigner import scala.util.{Failure, Success, Try} object CompletableFutureAwait { diff --git a/src/partest-extras/scala/tools/partest/async/OutputAwait.scala b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala index ca7657d199fa..a026d9ba9a04 100644 --- a/src/partest-extras/scala/tools/partest/async/OutputAwait.scala +++ b/src/partest-extras/scala/tools/partest/async/OutputAwait.scala @@ -17,7 +17,6 @@ import scala.collection.immutable.HashMap import scala.collection.mutable import scala.language.experimental.macros import scala.reflect.macros.blackbox -import scala.tools.nsc.transform.async.StateAssigner object OutputAwait { def writing[T](body: T): Output[T] = macro impl diff --git a/src/reflect/scala/reflect/api/Internals.scala b/src/reflect/scala/reflect/api/Internals.scala index b52d84103904..cfd853e70213 100644 --- a/src/reflect/scala/reflect/api/Internals.scala +++ b/src/reflect/scala/reflect/api/Internals.scala @@ -374,6 +374,17 @@ trait Internals { self: Universe => */ def boundedWildcardType(bounds: TypeBounds): BoundedWildcardType + /** Mark the given `DefDef` for later processing by the `async` phase of the compiler + * + * @param owner current owner the owner of the call site being transformed into an async state machine + * @param method A method of the form `def $name($paramName: $ParamType): $T = $CODE`, where calls to `$CODE` + * `awaitSymbol` in `$CODE` mark continuation points. + * @param awaitSymbol The `await` method, of a typically of a type like `[T](Future[T): T` + * @param config Untyped channel for additional configuration parameters. This currently allows + * - "postAnfTransform" : A function from `Block => Block` + * - "stateDiagram" : A function from `(Symbol, Tree) => Option[String => Unit]` that can + * opt to receive a .dot diagram of the state machine. + */ 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 */ diff --git a/src/reflect/scala/reflect/internal/Phase.scala b/src/reflect/scala/reflect/internal/Phase.scala index c0b4cdcb33b6..f6cf8dd5d938 100644 --- a/src/reflect/scala/reflect/internal/Phase.scala +++ b/src/reflect/scala/reflect/internal/Phase.scala @@ -64,7 +64,6 @@ 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/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 0ae72e6abf53..a44626c1e7a7 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -122,6 +122,4 @@ trait StdAttachments { // When typing a Def with this attachment, change the owner of its RHS from origalOwner to the symbol of the Def case class ChangeOwnerAttachment(originalOwner: Symbol) - - case object SuppressPureExpressionWarning extends PlainAttachment } diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index bd289932ac6c..0fa233dd6eb8 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -232,7 +232,6 @@ abstract class TreeInfo { ( !tree.isErrorTyped && (isExprSafeToInline(tree) || isWarnableRefTree) && isWarnableSymbol - && !tree.hasAttachment[SuppressPureExpressionWarning.type] ) } diff --git a/src/reflect/scala/reflect/internal/Trees.scala b/src/reflect/scala/reflect/internal/Trees.scala index f5aae5010beb..71d0ff0579b4 100644 --- a/src/reflect/scala/reflect/internal/Trees.scala +++ b/src/reflect/scala/reflect/internal/Trees.scala @@ -478,14 +478,7 @@ trait Trees extends api.Trees { case class ArrayValue(elemtpt: Tree, elems: List[Tree]) extends TermTree case class Function(vparams: List[ValDef], body: Tree) - extends SymTree with TermTree with FunctionApi { - - override def tpe_=(tp: Type): Unit = { - if (!definitions.isFunctionType(tp)) - new Throwable().printStackTrace() - super.setType(tp) - } - } + extends SymTree with TermTree with FunctionApi object Function extends FunctionExtractor case class Assign(lhs: Tree, rhs: Tree) @@ -1538,7 +1531,6 @@ trait Trees extends api.Trees { } class ChangeOwnerTraverser(val oldowner: Symbol, val newowner: Symbol) extends Traverser { - assert(newowner != NoSymbol, oldowner) final def change(sym: Symbol) = { if (sym != NoSymbol && sym.owner == oldowner) { sym.owner = newowner diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 93e8bfd0839b..d3f27147dfe9 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -64,7 +64,6 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.TypeParamVarargsAttachment this.KnownDirectSubclassesCalled this.ChangeOwnerAttachment - this.SuppressPureExpressionWarning this.noPrint this.typeDebug // inaccessible: this.posAssigner From 89f48d23a8afc924403727e8bfd89017ab59869f Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Thu, 18 Jun 2020 15:14:56 +1000 Subject: [PATCH 94/94] Refactor detection of ill nested await calls We can defer the check until the current location of the "fallback" check without sacrificing the specific error messages. --- .../nsc/transform/async/AnfTransform.scala | 52 +++++++++++-------- .../nsc/transform/async/AsyncPhase.scala | 3 +- .../nsc/transform/async/ExprBuilder.scala | 9 +--- 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala index 2b1afa242ca2..9489de6464b3 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AnfTransform.scala @@ -51,26 +51,26 @@ private[async] trait AnfTransform extends TransformUtils { curTree = tree val treeContainsAwait = containsAwait(tree) tree match { - case _: ClassDef | _: ModuleDef | _: Function | _: DefDef => + case _: ClassDef | _: ModuleDef | _: Function | _: DefDef => tree - case _ if !treeContainsAwait => + case _ if !treeContainsAwait => tree - case Apply(sel @ Select(fun, _), arg :: Nil) if isBooleanAnd(sel.symbol) && containsAwait(arg) => + case Apply(sel@Select(fun, _), arg :: Nil) if isBooleanAnd(sel.symbol) && containsAwait(arg) => transform(treeCopy.If(tree, fun, arg, literalBool(false))) - case Apply(sel @ Select(fun, _), arg :: Nil) if isBooleanOr(sel.symbol) && containsAwait(arg) => + case Apply(sel@Select(fun, _), arg :: Nil) if isBooleanOr(sel.symbol) && containsAwait(arg) => transform(treeCopy.If(tree, fun, literalBool(true), arg)) - case Apply(fun, args) => - val lastAwaitArgIndex: Int = args.lastIndexWhere(containsAwait) - val simpleFun = transform(fun) - var i = 0 - val argExprss = map2(args, fun.symbol.paramss.head) { (arg: Tree, param: Symbol) => + case Apply(fun, args) => + val lastAwaitArgIndex: RunId = args.lastIndexWhere(containsAwait) + val simpleFun = transform(fun) + var i = 0 + val argExprss = map2(args, fun.symbol.paramss.head) { (arg: Tree, param: Symbol) => transform(arg) match { case expr1 => - val argName = param.name.toTermName + val argName = param.name.toTermName // No need to extract the argument into a val if is non-side-effecting or if we are beyond the final // argument containing an `await` calls. val elideVal = treeInfo.isExprSafeToInline(expr1) || lastAwaitArgIndex < 0 || i > lastAwaitArgIndex || !treeContainsAwait - val result = if (elideVal) { + val result = if (elideVal) { localTyper.typed(expr1, arg.tpe) // Adapt () to BoxedUnit } else { if (isUnitType(expr1.tpe)) { @@ -86,11 +86,11 @@ private[async] trait AnfTransform extends TransformUtils { result } } - val simpleApply = treeCopy.Apply(tree, simpleFun, argExprss) + val simpleApply = treeCopy.Apply(tree, simpleFun, argExprss) simpleApply.attachments.remove[ContainsAwait.type] if (isAwait(fun)) { val valDef = defineVal(transformState.name.await(), treeCopy.Apply(tree, fun, argExprss), tree.pos) - val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe) + val ref = gen.mkAttributedStableRef(valDef.symbol).setType(tree.tpe) currentStats += valDef atPos(tree.pos)(ref) } else { @@ -119,7 +119,7 @@ private[async] trait AnfTransform extends TransformUtils { eliminateMatchEndLabelParameter(tree.pos, ts).foreach(t => flattenBlock(t)(currentStats += _)), onTail = (ts: List[Tree]) => ts.foreach(t => flattenBlock(t)(currentStats += _)) - ) + ) // However, we let `onTail` add the expr to `currentStats` (that was more efficient than using `ts.dropRight(1).foreach(addToStats)`) // Compensate by removing it from the buffer and returning the expr. @@ -129,10 +129,10 @@ private[async] trait AnfTransform extends TransformUtils { case ld: LabelDef if ld.tpe.typeSymbol == definitions.BoxedUnitClass => currentStats += ld literalBoxedUnit - case ld: LabelDef if ld.tpe.typeSymbol == definitions.UnitClass => + case ld: LabelDef if ld.tpe.typeSymbol == definitions.UnitClass => currentStats += ld literalUnit - case expr => expr + case expr => expr } case ValDef(mods, name, tpt, rhs) => atOwner(tree.symbol) { @@ -161,7 +161,7 @@ private[async] trait AnfTransform extends TransformUtils { case If(cond, thenp, elsep) => val needsResultVar = (containsAwait(thenp) || containsAwait(elsep)) transformMatchOrIf(tree, needsResultVar, transformState.name.ifRes) { varSym => - val condExpr = transform(cond) + val condExpr = transform(cond) val thenBlock = transformNewControlFlowBlock(thenp) val elseBlock = transformNewControlFlowBlock(elsep) treeCopy.If(tree, condExpr, pushAssignmentIntoExpr(varSym, thenBlock), pushAssignmentIntoExpr(varSym, elseBlock)) @@ -170,7 +170,7 @@ private[async] trait AnfTransform extends TransformUtils { case Match(scrut, cases) => val needResultVar = cases.exists(containsAwait) transformMatchOrIf(tree, needResultVar, transformState.name.matchRes) { varSym => - val scrutExpr = transform(scrut) + val scrutExpr = transform(scrut) val casesWithAssign = cases map { case cd@CaseDef(pat, guard, body) => assignUnitType(treeCopy.CaseDef(cd, pat, transformNewControlFlowBlock(guard), pushAssignmentIntoExpr(varSym, transformNewControlFlowBlock(body)))) @@ -178,11 +178,21 @@ private[async] trait AnfTransform extends TransformUtils { treeCopy.Match(tree, scrutExpr, casesWithAssign) } - case ld @ LabelDef(name, params, rhs) => + case ld@LabelDef(name, params, rhs) => treeCopy.LabelDef(tree, name, params, transformNewControlFlowBlock(rhs)) - case t @ Typed(expr, tpt) => + case t@Typed(expr, tpt) => transform(expr).setType(t.tpe) - case _ => + case Try(body, catches, finalizer) => + // This gets reported in ExprBuilder as an unsupported use of await. We still need to + // have _some_ non-default transform here make all cases in test/async/neg/ill-nested-await.check pass. + // + // TODO Create a result variable for try expression. + // Model exceptional control flow in ExprBuilder and remove this restriction. + treeCopy.Try(tree, + transformNewControlFlowBlock(body), + catches.mapConserve(cd => transformNewControlFlowBlock(cd).asInstanceOf[CaseDef]), + transformNewControlFlowBlock(finalizer)) + case _ => super.transform(tree) } } diff --git a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala index 7a0deeaf2de2..2de9f7120b4e 100644 --- a/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala +++ b/src/compiler/scala/tools/nsc/transform/async/AsyncPhase.scala @@ -16,7 +16,7 @@ import scala.collection.mutable import scala.tools.nsc.transform.{Transform, TypingTransformers} import scala.reflect.internal.util.SourceFile -abstract class AsyncPhase extends Transform with TypingTransformers with AnfTransform with AsyncAnalysis with Lifter with LiveVariables { +abstract class AsyncPhase extends Transform with TypingTransformers with AnfTransform with Lifter with LiveVariables { self => import global._ @@ -146,7 +146,6 @@ abstract class AsyncPhase extends Transform with TypingTransformers with AnfTran // We mark whether each sub-tree of `asyncBody` that do or do not contain an await in thus pre-processing pass. // The ANF transform can then efficiently query this to selectively transform the tree. markContainsAwait(asyncBody) - reportUnsupportedAwaits(asyncBody) // Transform to A-normal form: // - no await calls in qualifiers or arguments, diff --git a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala index 74b1d98197e0..59949526eb56 100644 --- a/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala +++ b/src/compiler/scala/tools/nsc/transform/async/ExprBuilder.scala @@ -15,8 +15,7 @@ package scala.tools.nsc.transform.async import scala.collection.mutable import scala.collection.mutable.ListBuffer -trait ExprBuilder extends TransformUtils { - +trait ExprBuilder extends TransformUtils with AsyncAnalysis { import global._ private def stateAssigner = currentTransformState.stateAssigner @@ -347,11 +346,7 @@ trait ExprBuilder extends TransformUtils { } 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 _ => - } + reportUnsupportedAwaits(tree) } /** Copy these states into the current block builder's async stats updating the open state builder's