diff options
author | Jason Zaugg <jzaugg@gmail.com> | 2013-07-02 15:55:34 +0200 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2013-07-03 10:04:55 +0200 |
commit | 82232ec47effb4a6b67b3a0792e1c7600e2d31b7 (patch) | |
tree | ed9925418aa0a631d1d25fd1be30f5d508e81b24 /src/main/scala/scala/async/TransformUtils.scala | |
parent | d63b63f536aafa494c70835526174be1987050de (diff) | |
download | scala-async-82232ec47effb4a6b67b3a0792e1c7600e2d31b7.tar.gz scala-async-82232ec47effb4a6b67b3a0792e1c7600e2d31b7.tar.bz2 scala-async-82232ec47effb4a6b67b3a0792e1c7600e2d31b7.zip |
An overdue overhaul of macro internals.
- Avoid reset + retypecheck, instead hang onto the original types/symbols
- Eliminated duplication between AsyncDefinitionUseAnalyzer and ExprBuilder
- Instead, decide what do lift *after* running ExprBuilder
- Account for transitive references local classes/objects and lift them
as needed.
- Make the execution context an regular implicit parameter of the macro
- Fixes interaction with existential skolems and singleton types
Fixes #6, #13, #16, #17, #19, #21.
Diffstat (limited to 'src/main/scala/scala/async/TransformUtils.scala')
-rw-r--r-- | src/main/scala/scala/async/TransformUtils.scala | 391 |
1 files changed, 130 insertions, 261 deletions
diff --git a/src/main/scala/scala/async/TransformUtils.scala b/src/main/scala/scala/async/TransformUtils.scala index ebd546f..33dd21d 100644 --- a/src/main/scala/scala/async/TransformUtils.scala +++ b/src/main/scala/scala/async/TransformUtils.scala @@ -5,115 +5,40 @@ package scala.async import scala.reflect.macros.Context import reflect.ClassTag +import scala.reflect.macros.runtime.AbortMacroException /** * Utilities used in both `ExprBuilder` and `AnfTransform`. */ -private[async] final case class TransformUtils[C <: Context](c: C) { +private[async] trait TransformUtils { + self: AsyncMacro => - import c.universe._ + import global._ object name { - def suffix(string: String) = string + "$async" - - def suffixedName(prefix: String) = newTermName(suffix(prefix)) - - val state = suffixedName("state") - val result = suffixedName("result") - val resume = suffixedName("resume") - val execContext = suffixedName("execContext") - val stateMachine = newTermName(fresh("stateMachine")) - val stateMachineT = stateMachine.toTypeName + val resume = newTermName("resume") val apply = newTermName("apply") - val applyOrElse = newTermName("applyOrElse") - val tr = newTermName("tr") val matchRes = "matchres" val ifRes = "ifres" val await = "await" val bindSuffix = "$bind" - def fresh(name: TermName): TermName = newTermName(fresh(name.toString)) + val state = newTermName("state") + val result = newTermName("result") + val execContext = newTermName("execContext") + val stateMachine = newTermName(fresh("stateMachine")) + val stateMachineT = stateMachine.toTypeName + val tr = newTermName("tr") + val t = newTermName("throwable") - def fresh(name: String): String = if (name.toString.contains("$")) name else c.fresh("" + name + "$") - } + def fresh(name: TermName): TermName = newTermName(fresh(name.toString)) - def defaultValue(tpe: Type): Literal = { - val defaultValue: Any = - if (tpe <:< definitions.BooleanTpe) false - else if (definitions.ScalaNumericValueClasses.exists(tpe <:< _.toType)) 0 - else if (tpe <:< definitions.AnyValTpe) 0 - else null - Literal(Constant(defaultValue)) + def fresh(name: String): String = if (name.toString.contains("$")) name else currentUnit.freshTermName("" + name + "$").toString } def isAwait(fun: Tree) = fun.symbol == defn.Async_await - /** Replace all `Ident` nodes referring to one of the keys n `renameMap` with a node - * referring to the corresponding new name - */ - def substituteNames(tree: Tree, renameMap: Map[Symbol, Name]): Tree = { - val renamer = new Transformer { - override def transform(tree: Tree) = tree match { - case Ident(_) => (renameMap get tree.symbol).fold(tree)(Ident(_)) - case tt: TypeTree if tt.original != EmptyTree && tt.original != null => - // We also have to apply our renaming transform on originals of TypeTrees. - // TODO 2.10.1 Can we find a cleaner way? - val symTab = c.universe.asInstanceOf[reflect.internal.SymbolTable] - val tt1 = tt.asInstanceOf[symTab.TypeTree] - tt1.setOriginal(transform(tt.original).asInstanceOf[symTab.Tree]) - super.transform(tree) - case _ => super.transform(tree) - } - } - renamer.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, - * and `nestedClass` etc are invoked. - */ - trait AsyncTraverser extends Traverser { - def nestedClass(classDef: ClassDef) { - } - - def nestedModule(module: ModuleDef) { - } - - def nestedMethod(module: DefDef) { - } - - def byNameArgument(arg: Tree) { - } - - def function(function: Function) { - } - - def patMatFunction(tree: Match) { - } - - override def traverse(tree: Tree) { - tree match { - 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 Applied(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) - } - } - } - private lazy val Boolean_ShortCircuits: Set[Symbol] = { import definitions.BooleanClass def BooleanTermMember(name: String) = BooleanClass.typeSignature.member(newTermName(name).encodedName) @@ -122,57 +47,30 @@ private[async] final case class TransformUtils[C <: Context](c: C) { Set(Boolean_&&, Boolean_||) } - def isByName(fun: Tree): ((Int, Int) => Boolean) = { + private def isByName(fun: Tree): ((Int, Int) => Boolean) = { if (Boolean_ShortCircuits contains fun.symbol) (i, j) => true else { - val symtab = c.universe.asInstanceOf[reflect.internal.SymbolTable] - val paramss = fun.tpe.asInstanceOf[symtab.Type].paramss + val paramss = fun.tpe.paramss val byNamess = paramss.map(_.map(_.isByNameParam)) (i, j) => util.Try(byNamess(i)(j)).getOrElse(false) } } - def argName(fun: Tree): ((Int, Int) => String) = { - val symtab = c.universe.asInstanceOf[reflect.internal.SymbolTable] - val paramss = fun.tpe.asInstanceOf[symtab.Type].paramss + private def argName(fun: Tree): ((Int, Int) => String) = { + val paramss = fun.tpe.paramss val namess = paramss.map(_.map(_.name.toString)) (i, j) => util.Try(namess(i)(j)).getOrElse(s"arg_${i}_${j}") } - object Applied { - val symtab = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable] - object treeInfo extends { - val global: symtab.type = symtab - } with reflect.internal.TreeInfo - - def unapply(tree: Tree): Some[(Tree, List[Tree], List[List[Tree]])] = { - val treeInfo.Applied(core, targs, argss) = tree.asInstanceOf[symtab.Tree] - Some((core.asInstanceOf[Tree], targs.asInstanceOf[List[Tree]], argss.asInstanceOf[List[List[Tree]]])) - } - } - - def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { - case Block(stats, expr) => (stats, expr) - case _ => (List(tree), Literal(Constant(()))) - } - - def mkVarDefTree(resultType: Type, resultName: TermName): c.Tree = { - ValDef(Modifiers(Flag.MUTABLE), resultName, TypeTree(resultType), defaultValue(resultType)) - } - - 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), c.literalUnit.tree)) - } - - def applied(className: String, types: List[Type]): AppliedTypeTree = - AppliedTypeTree(Ident(c.mirror.staticClass(className)), types.map(TypeTree(_))) + def Expr[A: WeakTypeTag](t: Tree) = global.Expr[A](rootMirror, new FixedMirrorTreeCreator(rootMirror, t)) object defn { def mkList_apply[A](args: List[Expr[A]]): Expr[List[A]] = { - c.Expr(Apply(Ident(definitions.List_apply), args.map(_.tree))) + Expr(Apply(Ident(definitions.List_apply), args.map(_.tree))) } - def mkList_contains[A](self: Expr[List[A]])(elem: Expr[Any]) = reify(self.splice.contains(elem.splice)) + def mkList_contains[A](self: Expr[List[A]])(elem: Expr[Any]) = reify { + self.splice.contains(elem.splice) + } def mkFunction_apply[A, B](self: Expr[Function1[A, B]])(arg: Expr[A]) = reify { self.splice.apply(arg.splice) @@ -186,146 +84,17 @@ private[async] final case class TransformUtils[C <: Context](c: C) { self.splice.get } - val Try_get = methodSym(reify((null: scala.util.Try[Any]).get)) - val Try_isFailure = methodSym(reify((null: scala.util.Try[Any]).isFailure)) - - val TryClass = c.mirror.staticClass("scala.util.Try") + val TryClass = rootMirror.staticClass("scala.util.Try") + val Try_get = TryClass.typeSignature.member(newTermName("get")).ensuring(_ != NoSymbol) + val Try_isFailure = TryClass.typeSignature.member(newTermName("isFailure")).ensuring(_ != NoSymbol) val TryAnyType = appliedType(TryClass.toType, List(definitions.AnyTpe)) - val NonFatalClass = c.mirror.staticModule("scala.util.control.NonFatal") - - private def asyncMember(name: String) = { - val asyncMod = c.mirror.staticClass("scala.async.AsyncBase") - val tpe = asyncMod.asType.toType - tpe.member(newTermName(name)).ensuring(_ != NoSymbol) - } - - val Async_await = asyncMember("await") - } - - /** `termSym( (_: Foo).bar(null: A, null: B)` will return the symbol of `bar`, after overload resolution. */ - private def methodSym(apply: c.Expr[Any]): Symbol = { - val tree2: Tree = c.typeCheck(apply.tree) - tree2.collect { - case s: SymTree if s.symbol.isMethod => s.symbol - }.headOption.getOrElse(sys.error(s"Unable to find a method symbol in ${apply.tree}")) - } - - /** - * Using [[scala.reflect.api.Trees.TreeCopier]] copies more than we would like: - * we don't want to copy types and symbols to the new trees in some cases. - * - * Instead, we just copy positions and attachments. - */ - def attachCopy[T <: Tree](orig: Tree)(tree: T): tree.type = { - tree.setPos(orig.pos) - for (att <- orig.attachments.all) - tree.updateAttachment[Any](att)(ClassTag.apply[Any](att.getClass)) - tree - } - - def resetInternalAttrs(tree: Tree, internalSyms: List[Symbol]) = - new ResetInternalAttrs(internalSyms.toSet).transform(tree) - - /** - * Adaptation of [[scala.reflect.internal.Trees.ResetAttrs]] - * - * A transformer which resets symbol and tpe fields of all nodes in a given tree, - * with special treatment of: - * `TypeTree` nodes: are replaced by their original if it exists, otherwise tpe field is reset - * to empty if it started out empty or refers to local symbols (which are erased). - * `TypeApply` nodes: are deleted if type arguments end up reverted to empty - * - * `This` and `Ident` nodes referring to an external symbol are ''not'' reset. - */ - private final class ResetInternalAttrs(internalSyms: Set[Symbol]) extends Transformer { - - import language.existentials - - override def transform(tree: Tree): Tree = super.transform { - def isExternal = tree.symbol != NoSymbol && !internalSyms(tree.symbol) - - tree match { - case tpt: TypeTree => resetTypeTree(tpt) - case TypeApply(fn, args) - if args map transform exists (_.isEmpty) => transform(fn) - case EmptyTree => tree - case (_: Ident | _: This) if isExternal => tree // #35 Don't reset the symbol of Ident/This bound outside of the async block - case _ => resetTree(tree) - } - } - - private def resetTypeTree(tpt: TypeTree): Tree = { - if (tpt.original != null) - transform(tpt.original) - else if (tpt.tpe != null && tpt.asInstanceOf[symtab.TypeTree forSome {val symtab: reflect.internal.SymbolTable}].wasEmpty) { - val dupl = tpt.duplicate - dupl.tpe = null - dupl - } - else tpt - } - - private def resetTree(tree: Tree): Tree = { - val hasSymbol: Boolean = { - val reflectInternalTree = tree.asInstanceOf[symtab.Tree forSome {val symtab: reflect.internal.SymbolTable}] - reflectInternalTree.hasSymbol - } - val dupl = tree.duplicate - if (hasSymbol) - dupl.symbol = NoSymbol - dupl.tpe = null - dupl - } - } - - /** - * Replaces expressions of the form `{ new $anon extends PartialFunction[A, B] { ... ; def applyOrElse[..](...) = ... match <cases> }` - * with `Match(EmptyTree, cases`. - * - * This reverses the transformation performed in `Typers`, and works around non-idempotency of typechecking such trees. - */ - // TODO Reference JIRA issue. - final def restorePatternMatchingFunctions(tree: Tree) = - RestorePatternMatchingFunctions transform tree - - private object RestorePatternMatchingFunctions extends Transformer { - - import language.existentials - val DefaultCaseName: TermName = "defaultCase$" - - override def transform(tree: Tree): Tree = { - val SYNTHETIC = (1 << 21).toLong.asInstanceOf[FlagSet] - def isSynthetic(cd: ClassDef) = cd.mods hasFlag SYNTHETIC - - /** Is this pattern node a synthetic catch-all case, added during PartialFuction synthesis before we know - * whether the user provided cases are exhaustive. */ - def isSyntheticDefaultCase(cdef: CaseDef) = cdef match { - case CaseDef(Bind(DefaultCaseName, _), EmptyTree, _) => true - case _ => false - } - tree match { - case Block( - (cd@ClassDef(_, _, _, Template(_, _, body))) :: Nil, - Apply(Select(New(a), nme.CONSTRUCTOR), Nil)) if isSynthetic(cd) => - val restored = (body collectFirst { - case DefDef(_, /*name.apply | */ name.applyOrElse, _, _, _, Match(_, cases)) => - val nonSyntheticCases = cases.takeWhile(cdef => !isSyntheticDefaultCase(cdef)) - val transformedCases = super.transformStats(nonSyntheticCases, currentOwner).asInstanceOf[List[CaseDef]] - Match(EmptyTree, transformedCases) - }).getOrElse(c.abort(tree.pos, s"Internal Error: Unable to find original pattern matching cases in: $body")) - restored - case t => super.transform(t) - } - } + val NonFatalClass = rootMirror.staticModule("scala.util.control.NonFatal") + val AsyncClass = rootMirror.staticClass("scala.async.AsyncBase") + val Async_await = AsyncClass.typeSignature.member(newTermName("await")).ensuring(_ != NoSymbol) } def isSafeToInline(tree: Tree) = { - val symtab = c.universe.asInstanceOf[scala.reflect.internal.SymbolTable] - object treeInfo extends { - val global: symtab.type = symtab - } with reflect.internal.TreeInfo - val castTree = tree.asInstanceOf[symtab.Tree] - treeInfo.isExprSafeToInline(castTree) + treeInfo.isExprSafeToInline(tree) } /** Map a list of arguments to: @@ -371,4 +140,104 @@ private[async] final case class TransformUtils[C <: Context](c: C) { } }.unzip } + + + def statsAndExpr(tree: Tree): (List[Tree], Tree) = tree match { + case Block(stats, expr) => (stats, expr) + case _ => (List(tree), Literal(Constant(()))) + } + + 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(())))) + } + + def applied(className: String, types: List[Type]): AppliedTypeTree = + AppliedTypeTree(Ident(rootMirror.staticClass(className)), types.map(TypeTree(_))) + + /** 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) { + } + + def nestedModule(module: ModuleDef) { + } + + def nestedMethod(module: DefDef) { + } + + def byNameArgument(arg: Tree) { + } + + def function(function: Function) { + } + + def patMatFunction(tree: Match) { + } + + override def traverse(tree: Tree) { + tree match { + 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 treeInfo.Applied(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 abort(pos: Position, msg: String) = throw new AbortMacroException(pos, msg) + + abstract class MacroTypingTransformer extends TypingTransformer(callSiteTyper.context.unit) { + currentOwner = callSiteTyper.context.owner + + def currOwner: Symbol = currentOwner + + localTyper = global.analyzer.newTyper(callSiteTyper.context.make(unit = callSiteTyper.context.unit)) + } + + def transformAt(tree: Tree)(f: PartialFunction[Tree, (analyzer.Context => Tree)]) = { + object trans extends MacroTypingTransformer { + override def transform(tree: Tree): Tree = { + if (f.isDefinedAt(tree)) { + f(tree)(localTyper.context) + } else super.transform(tree) + } + } + trans.transform(tree) + } + + def changeOwner(tree: Tree, oldOwner: Symbol, newOwner: Symbol): tree.type = { + new ChangeOwnerAndModuleClassTraverser(oldOwner, newOwner).traverse(tree) + tree + } + + class ChangeOwnerAndModuleClassTraverser(oldowner: Symbol, newowner: Symbol) + extends ChangeOwnerTraverser(oldowner, newowner) { + + override def traverse(tree: Tree) { + tree match { + case _: DefTree => change(tree.symbol.moduleClass) + case _ => + } + super.traverse(tree) + } + } + + def toMultiMap[A, B](as: Iterable[(A, B)]): Map[A, List[B]] = + as.toList.groupBy(_._1).mapValues(_.map(_._2).toList).toMap } |