-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #748 from dotty-staging/add/non-local/returns
Implement non-local returns
- Loading branch information
Showing
11 changed files
with
250 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package dotty.tools.dotc | ||
package transform | ||
|
||
import TreeTransforms._ | ||
import core.DenotTransformers._ | ||
import core.Symbols._ | ||
import core.Contexts._ | ||
import core.Types._ | ||
import core.Flags._ | ||
import core.Decorators._ | ||
import NonLocalReturns._ | ||
|
||
/** Lifts try's that might be executed on non-empty expression stacks | ||
* to their own methods. I.e. | ||
* | ||
* try body catch handler | ||
* | ||
* is lifted to | ||
* | ||
* { def liftedTree$n() = try body catch handler; liftedTree$n() } | ||
*/ | ||
class LiftTry extends MiniPhase with IdentityDenotTransformer { thisTransform => | ||
import ast.tpd._ | ||
|
||
/** the following two members override abstract members in Transform */ | ||
val phaseName: String = "liftTry" | ||
|
||
val treeTransform = new Transform(needLift = false) | ||
val liftingTransform = new Transform(needLift = true) | ||
|
||
class Transform(needLift: Boolean) extends TreeTransform { | ||
def phase = thisTransform | ||
|
||
override def prepareForApply(tree: Apply)(implicit ctx: Context) = | ||
if (tree.fun.symbol.is(Label)) this | ||
else liftingTransform | ||
|
||
override def prepareForValDef(tree: ValDef)(implicit ctx: Context) = | ||
if (!tree.symbol.exists || | ||
tree.symbol.isSelfSym || | ||
tree.symbol.owner == ctx.owner.enclosingMethod) this | ||
else liftingTransform | ||
|
||
override def prepareForAssign(tree: Assign)(implicit ctx: Context) = | ||
if (tree.lhs.symbol.maybeOwner == ctx.owner.enclosingMethod) this | ||
else liftingTransform | ||
|
||
override def prepareForReturn(tree: Return)(implicit ctx: Context) = | ||
if (!isNonLocalReturn(tree)) this | ||
else liftingTransform | ||
|
||
override def prepareForTemplate(tree: Template)(implicit ctx: Context) = | ||
treeTransform | ||
|
||
override def transformTry(tree: Try)(implicit ctx: Context, info: TransformerInfo): Tree = | ||
if (needLift) { | ||
ctx.debuglog(i"lifting tree at ${tree.pos}, current owner = ${ctx.owner}") | ||
val fn = ctx.newSymbol( | ||
ctx.owner, ctx.freshName("liftedTree").toTermName, Synthetic | Method, | ||
MethodType(Nil, tree.tpe), coord = tree.pos) | ||
tree.changeOwnerAfter(ctx.owner, fn, thisTransform) | ||
Block(DefDef(fn, tree) :: Nil, ref(fn).appliedToNone) | ||
} | ||
else tree | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package dotty.tools.dotc | ||
package transform | ||
|
||
import core._ | ||
import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._, Phases._ | ||
import TreeTransforms._ | ||
import ast.Trees._ | ||
import collection.mutable | ||
|
||
object NonLocalReturns { | ||
import ast.tpd._ | ||
def isNonLocalReturn(ret: Return)(implicit ctx: Context) = | ||
ret.from.symbol != ctx.owner.enclosingMethod || ctx.owner.is(Lazy) | ||
} | ||
|
||
/** Implement non-local returns using NonLocalReturnControl exceptions. | ||
*/ | ||
class NonLocalReturns extends MiniPhaseTransform { thisTransformer => | ||
override def phaseName = "nonLocalReturns" | ||
|
||
import NonLocalReturns._ | ||
import ast.tpd._ | ||
|
||
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimByName]) | ||
|
||
private def ensureConforms(tree: Tree, pt: Type)(implicit ctx: Context) = | ||
if (tree.tpe <:< pt) tree | ||
else Erasure.Boxing.adaptToType(tree, pt) | ||
|
||
/** The type of a non-local return expression with given argument type */ | ||
private def nonLocalReturnExceptionType(argtype: Type)(implicit ctx: Context) = | ||
defn.NonLocalReturnControlClass.typeRef.appliedTo(argtype) | ||
|
||
/** A hashmap from method symbols to non-local return keys */ | ||
private val nonLocalReturnKeys = mutable.Map[Symbol, TermSymbol]() | ||
|
||
/** Return non-local return key for given method */ | ||
private def nonLocalReturnKey(meth: Symbol)(implicit ctx: Context) = | ||
nonLocalReturnKeys.getOrElseUpdate(meth, | ||
ctx.newSymbol( | ||
meth, ctx.freshName("nonLocalReturnKey").toTermName, Synthetic, defn.ObjectType, coord = meth.pos)) | ||
|
||
/** Generate a non-local return throw with given return expression from given method. | ||
* I.e. for the method's non-local return key, generate: | ||
* | ||
* throw new NonLocalReturnControl(key, expr) | ||
* todo: maybe clone a pre-existing exception instead? | ||
* (but what to do about exceptions that miss their targets?) | ||
*/ | ||
private def nonLocalReturnThrow(expr: Tree, meth: Symbol)(implicit ctx: Context) = | ||
Throw( | ||
New( | ||
defn.NonLocalReturnControlClass.typeRef, | ||
ref(nonLocalReturnKey(meth)) :: expr.ensureConforms(defn.ObjectType) :: Nil)) | ||
|
||
/** Transform (body, key) to: | ||
* | ||
* { | ||
* val key = new Object() | ||
* try { | ||
* body | ||
* } catch { | ||
* case ex: NonLocalReturnControl => | ||
* if (ex.key().eq(key)) ex.value().asInstanceOf[T] | ||
* else throw ex | ||
* } | ||
* } | ||
*/ | ||
private def nonLocalReturnTry(body: Tree, key: TermSymbol, meth: Symbol)(implicit ctx: Context) = { | ||
val keyDef = ValDef(key, New(defn.ObjectType, Nil)) | ||
val nonLocalReturnControl = defn.NonLocalReturnControlClass.typeRef | ||
val ex = ctx.newSymbol(meth, nme.ex, EmptyFlags, nonLocalReturnControl, coord = body.pos) | ||
val pat = BindTyped(ex, nonLocalReturnControl) | ||
val rhs = If( | ||
ref(ex).select(nme.key).appliedToNone.select(nme.eq).appliedTo(ref(key)), | ||
ref(ex).select(nme.value).ensureConforms(meth.info.finalResultType), | ||
Throw(ref(ex))) | ||
val catches = CaseDef(pat, EmptyTree, rhs) :: Nil | ||
val tryCatch = Try(body, catches, EmptyTree) | ||
Block(keyDef :: Nil, tryCatch) | ||
} | ||
|
||
override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree = | ||
nonLocalReturnKeys.remove(tree.symbol) match { | ||
case Some(key) => cpy.DefDef(tree)(rhs = nonLocalReturnTry(tree.rhs, key, tree.symbol)) | ||
case _ => tree | ||
} | ||
|
||
override def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo): Tree = | ||
if (isNonLocalReturn(tree)) nonLocalReturnThrow(tree.expr, tree.from.symbol).withPos(tree.pos) | ||
else tree | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
object Test { | ||
|
||
def raise(x: Int) = { throw new Exception(s"$x"); 0 } | ||
def handle: Throwable => Int = { case ex: Exception => ex.getMessage().toInt } | ||
|
||
val x = try raise(1) catch handle | ||
|
||
def foo(x: Int) = { | ||
val y = try raise(x) catch handle | ||
y | ||
} | ||
|
||
foo(try 3 catch handle) | ||
|
||
def main(args: Array[String]) = { | ||
assert(x == 1) | ||
assert(foo(2) == 2) | ||
assert(foo(try raise(3) catch handle) == 3) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
object Test { | ||
|
||
def foo(xs: List[Int]): Int = { | ||
xs.foreach(x => return x) | ||
0 | ||
} | ||
|
||
def bar(xs: List[Int]): Int = { | ||
lazy val y = if (xs.isEmpty) return -1 else xs.head | ||
y | ||
} | ||
|
||
def baz(x: Int): Int = | ||
byName { return -2; 3 } | ||
|
||
def byName(x: => Int): Int = x | ||
|
||
def bam(): Int = { // no non-local return needed here | ||
val foo = { | ||
return -3 | ||
3 | ||
} | ||
foo | ||
} | ||
|
||
def main(args: Array[String]) = { | ||
assert(foo(List(1, 2, 3)) == 1) | ||
assert(bar(Nil) == -1) | ||
assert(baz(3) == -2) | ||
assert(bam() == -3) | ||
} | ||
} |