Skip to content

Commit 9eb55f1

Browse files
committed
Merge pull request #748 from dotty-staging/add/non-local/returns
Implement non-local returns
2 parents 07e24e8 + 694aabd commit 9eb55f1

File tree

11 files changed

+250
-23
lines changed

11 files changed

+250
-23
lines changed

src/dotty/tools/dotc/Compiler.scala

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Compiler {
4848
new ExtensionMethods,
4949
new ExpandSAMs,
5050
new TailRec,
51+
new LiftTry,
5152
new ClassOf),
5253
List(new PatternMatcher,
5354
new ExplicitOuter,
@@ -68,6 +69,7 @@ class Compiler {
6869
new LazyVals,
6970
new Memoize,
7071
new LinkScala2ImplClasses,
72+
new NonLocalReturns,
7173
new CapturedVars, // capturedVars has a transformUnit: no phases should introduce local mutable vars here
7274
new Constructors,
7375
new FunctionalInterfaces,

src/dotty/tools/dotc/ast/tpd.scala

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import config.Printers._
1313
import typer.Mode
1414
import collection.mutable
1515
import typer.ErrorReporting._
16+
import transform.Erasure
1617

1718
import scala.annotation.tailrec
1819

@@ -161,6 +162,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
161162
def Bind(sym: TermSymbol, body: Tree)(implicit ctx: Context): Bind =
162163
ta.assignType(untpd.Bind(sym.name, body), sym)
163164

165+
/** A pattern corresponding to `sym: tpe` */
166+
def BindTyped(sym: TermSymbol, tpe: Type)(implicit ctx: Context): Bind =
167+
Bind(sym, Typed(Underscore(tpe), TypeTree(tpe)))
168+
164169
def Alternative(trees: List[Tree])(implicit ctx: Context): Alternative =
165170
ta.assignType(untpd.Alternative(trees), trees)
166171

@@ -733,9 +738,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
733738
tree.select(defn.Any_asInstanceOf).appliedToType(tp)
734739
}
735740

736-
/** `tree.asInstanceOf[tp]` unless tree's type already conforms to `tp` */
741+
/** `tree.asInstanceOf[tp]` (or its box/unbox/cast equivalent when after
742+
* erasure and value and non-value types are mixed),
743+
* unless tree's type already conforms to `tp`.
744+
*/
737745
def ensureConforms(tp: Type)(implicit ctx: Context): Tree =
738-
if (tree.tpe <:< tp) tree else asInstance(tp)
746+
if (tree.tpe <:< tp) tree
747+
else if (!ctx.erasedTypes) asInstance(tp)
748+
else Erasure.Boxing.adaptToType(tree, tp)
739749

740750
/** If inititializer tree is `_', the default value of its type,
741751
* otherwise the tree itself.

src/dotty/tools/dotc/core/Contexts.scala

+18-19
Original file line numberDiff line numberDiff line change
@@ -177,26 +177,25 @@ object Contexts {
177177
/** The new implicit references that are introduced by this scope */
178178
private var implicitsCache: ContextualImplicits = null
179179
def implicits: ContextualImplicits = {
180-
if (implicitsCache == null ) {
181-
val outerImplicits =
182-
if (isImportContext && importInfo.hiddenRoot.exists)
183-
outer.implicits exclude importInfo.hiddenRoot
184-
else
185-
outer.implicits
186-
try
187-
implicitsCache = {
188-
val implicitRefs: List[TermRef] =
189-
if (isClassDefContext) owner.thisType.implicitMembers
190-
else if (isImportContext) importInfo.importedImplicits
191-
else if (isNonEmptyScopeContext) scope.implicitDecls
192-
else Nil
193-
if (implicitRefs.isEmpty) outerImplicits
194-
else new ContextualImplicits(implicitRefs, outerImplicits)(this)
195-
}
196-
catch {
197-
case ex: CyclicReference => implicitsCache = outerImplicits
180+
if (implicitsCache == null )
181+
implicitsCache = {
182+
val implicitRefs: List[TermRef] =
183+
if (isClassDefContext)
184+
try owner.thisType.implicitMembers
185+
catch {
186+
case ex: CyclicReference => Nil
187+
}
188+
else if (isImportContext) importInfo.importedImplicits
189+
else if (isNonEmptyScopeContext) scope.implicitDecls
190+
else Nil
191+
val outerImplicits =
192+
if (isImportContext && importInfo.hiddenRoot.exists)
193+
outer.implicits exclude importInfo.hiddenRoot
194+
else
195+
outer.implicits
196+
if (implicitRefs.isEmpty) outerImplicits
197+
else new ContextualImplicits(implicitRefs, outerImplicits)(this)
198198
}
199-
}
200199
implicitsCache
201200
}
202201

src/dotty/tools/dotc/core/Definitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ class Definitions {
326326
lazy val Product_productArity = ProductClass.requiredMethod(nme.productArity)
327327
lazy val Product_productPrefix = ProductClass.requiredMethod(nme.productPrefix)
328328
lazy val LanguageModuleClass = ctx.requiredModule("dotty.language").moduleClass.asClass
329+
lazy val NonLocalReturnControlClass = ctx.requiredClass("scala.runtime.NonLocalReturnControl")
329330

330331
// Annotation base classes
331332
lazy val AnnotationClass = ctx.requiredClass("scala.annotation.Annotation")

src/dotty/tools/dotc/core/SymDenotations.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,11 @@ object SymDenotations {
299299
/** The expanded name of this denotation. */
300300
final def expandedName(implicit ctx: Context) =
301301
if (is(ExpandedName) || isConstructor) name
302-
else name.expandedName(initial.asSymDenotation.owner)
302+
else {
303+
def legalize(name: Name): Name = // JVM method names may not contain `<' or `>' characters
304+
if (is(Method)) name.replace('<', '(').replace('>', ')') else name
305+
legalize(name.expandedName(initial.asSymDenotation.owner))
306+
}
303307
// need to use initial owner to disambiguate, as multiple private symbols with the same name
304308
// might have been moved from different origins into the same class
305309

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import TreeTransforms._
5+
import core.DenotTransformers._
6+
import core.Symbols._
7+
import core.Contexts._
8+
import core.Types._
9+
import core.Flags._
10+
import core.Decorators._
11+
import NonLocalReturns._
12+
13+
/** Lifts try's that might be executed on non-empty expression stacks
14+
* to their own methods. I.e.
15+
*
16+
* try body catch handler
17+
*
18+
* is lifted to
19+
*
20+
* { def liftedTree$n() = try body catch handler; liftedTree$n() }
21+
*/
22+
class LiftTry extends MiniPhase with IdentityDenotTransformer { thisTransform =>
23+
import ast.tpd._
24+
25+
/** the following two members override abstract members in Transform */
26+
val phaseName: String = "liftTry"
27+
28+
val treeTransform = new Transform(needLift = false)
29+
val liftingTransform = new Transform(needLift = true)
30+
31+
class Transform(needLift: Boolean) extends TreeTransform {
32+
def phase = thisTransform
33+
34+
override def prepareForApply(tree: Apply)(implicit ctx: Context) =
35+
if (tree.fun.symbol.is(Label)) this
36+
else liftingTransform
37+
38+
override def prepareForValDef(tree: ValDef)(implicit ctx: Context) =
39+
if (!tree.symbol.exists ||
40+
tree.symbol.isSelfSym ||
41+
tree.symbol.owner == ctx.owner.enclosingMethod) this
42+
else liftingTransform
43+
44+
override def prepareForAssign(tree: Assign)(implicit ctx: Context) =
45+
if (tree.lhs.symbol.maybeOwner == ctx.owner.enclosingMethod) this
46+
else liftingTransform
47+
48+
override def prepareForReturn(tree: Return)(implicit ctx: Context) =
49+
if (!isNonLocalReturn(tree)) this
50+
else liftingTransform
51+
52+
override def prepareForTemplate(tree: Template)(implicit ctx: Context) =
53+
treeTransform
54+
55+
override def transformTry(tree: Try)(implicit ctx: Context, info: TransformerInfo): Tree =
56+
if (needLift) {
57+
ctx.debuglog(i"lifting tree at ${tree.pos}, current owner = ${ctx.owner}")
58+
val fn = ctx.newSymbol(
59+
ctx.owner, ctx.freshName("liftedTree").toTermName, Synthetic | Method,
60+
MethodType(Nil, tree.tpe), coord = tree.pos)
61+
tree.changeOwnerAfter(ctx.owner, fn, thisTransform)
62+
Block(DefDef(fn, tree) :: Nil, ref(fn).appliedToNone)
63+
}
64+
else tree
65+
}
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import Contexts._, Symbols._, Types._, Flags._, Decorators._, StdNames._, Constants._, Phases._
6+
import TreeTransforms._
7+
import ast.Trees._
8+
import collection.mutable
9+
10+
object NonLocalReturns {
11+
import ast.tpd._
12+
def isNonLocalReturn(ret: Return)(implicit ctx: Context) =
13+
ret.from.symbol != ctx.owner.enclosingMethod || ctx.owner.is(Lazy)
14+
}
15+
16+
/** Implement non-local returns using NonLocalReturnControl exceptions.
17+
*/
18+
class NonLocalReturns extends MiniPhaseTransform { thisTransformer =>
19+
override def phaseName = "nonLocalReturns"
20+
21+
import NonLocalReturns._
22+
import ast.tpd._
23+
24+
override def runsAfter: Set[Class[_ <: Phase]] = Set(classOf[ElimByName])
25+
26+
private def ensureConforms(tree: Tree, pt: Type)(implicit ctx: Context) =
27+
if (tree.tpe <:< pt) tree
28+
else Erasure.Boxing.adaptToType(tree, pt)
29+
30+
/** The type of a non-local return expression with given argument type */
31+
private def nonLocalReturnExceptionType(argtype: Type)(implicit ctx: Context) =
32+
defn.NonLocalReturnControlClass.typeRef.appliedTo(argtype)
33+
34+
/** A hashmap from method symbols to non-local return keys */
35+
private val nonLocalReturnKeys = mutable.Map[Symbol, TermSymbol]()
36+
37+
/** Return non-local return key for given method */
38+
private def nonLocalReturnKey(meth: Symbol)(implicit ctx: Context) =
39+
nonLocalReturnKeys.getOrElseUpdate(meth,
40+
ctx.newSymbol(
41+
meth, ctx.freshName("nonLocalReturnKey").toTermName, Synthetic, defn.ObjectType, coord = meth.pos))
42+
43+
/** Generate a non-local return throw with given return expression from given method.
44+
* I.e. for the method's non-local return key, generate:
45+
*
46+
* throw new NonLocalReturnControl(key, expr)
47+
* todo: maybe clone a pre-existing exception instead?
48+
* (but what to do about exceptions that miss their targets?)
49+
*/
50+
private def nonLocalReturnThrow(expr: Tree, meth: Symbol)(implicit ctx: Context) =
51+
Throw(
52+
New(
53+
defn.NonLocalReturnControlClass.typeRef,
54+
ref(nonLocalReturnKey(meth)) :: expr.ensureConforms(defn.ObjectType) :: Nil))
55+
56+
/** Transform (body, key) to:
57+
*
58+
* {
59+
* val key = new Object()
60+
* try {
61+
* body
62+
* } catch {
63+
* case ex: NonLocalReturnControl =>
64+
* if (ex.key().eq(key)) ex.value().asInstanceOf[T]
65+
* else throw ex
66+
* }
67+
* }
68+
*/
69+
private def nonLocalReturnTry(body: Tree, key: TermSymbol, meth: Symbol)(implicit ctx: Context) = {
70+
val keyDef = ValDef(key, New(defn.ObjectType, Nil))
71+
val nonLocalReturnControl = defn.NonLocalReturnControlClass.typeRef
72+
val ex = ctx.newSymbol(meth, nme.ex, EmptyFlags, nonLocalReturnControl, coord = body.pos)
73+
val pat = BindTyped(ex, nonLocalReturnControl)
74+
val rhs = If(
75+
ref(ex).select(nme.key).appliedToNone.select(nme.eq).appliedTo(ref(key)),
76+
ref(ex).select(nme.value).ensureConforms(meth.info.finalResultType),
77+
Throw(ref(ex)))
78+
val catches = CaseDef(pat, EmptyTree, rhs) :: Nil
79+
val tryCatch = Try(body, catches, EmptyTree)
80+
Block(keyDef :: Nil, tryCatch)
81+
}
82+
83+
override def transformDefDef(tree: DefDef)(implicit ctx: Context, info: TransformerInfo): Tree =
84+
nonLocalReturnKeys.remove(tree.symbol) match {
85+
case Some(key) => cpy.DefDef(tree)(rhs = nonLocalReturnTry(tree.rhs, key, tree.symbol))
86+
case _ => tree
87+
}
88+
89+
override def transformReturn(tree: Return)(implicit ctx: Context, info: TransformerInfo): Tree =
90+
if (isNonLocalReturn(tree)) nonLocalReturnThrow(tree.expr, tree.from.symbol).withPos(tree.pos)
91+
else tree
92+
}

test/dotc/tests.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class tests extends CompilerTest {
115115
@Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12)
116116

117117
val negTailcallDir = negDir + "tailcall/"
118-
@Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b", xerrors = 6)
118+
@Test def neg_tailcall_t1672b = compileFile(negTailcallDir, "t1672b", xerrors = 5)
119119
@Test def neg_tailcall_t3275 = compileFile(negTailcallDir, "t3275", xerrors = 1)
120120
@Test def neg_tailcall_t6574 = compileFile(negTailcallDir, "t6574", xerrors = 2)
121121
@Test def neg_tailcall = compileFile(negTailcallDir, "tailrec", xerrors = 7)
File renamed without changes.

tests/run/liftedTry.scala

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
object Test {
2+
3+
def raise(x: Int) = { throw new Exception(s"$x"); 0 }
4+
def handle: Throwable => Int = { case ex: Exception => ex.getMessage().toInt }
5+
6+
val x = try raise(1) catch handle
7+
8+
def foo(x: Int) = {
9+
val y = try raise(x) catch handle
10+
y
11+
}
12+
13+
foo(try 3 catch handle)
14+
15+
def main(args: Array[String]) = {
16+
assert(x == 1)
17+
assert(foo(2) == 2)
18+
assert(foo(try raise(3) catch handle) == 3)
19+
}
20+
}
21+

tests/run/nonLocalReturns.scala

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
object Test {
2+
3+
def foo(xs: List[Int]): Int = {
4+
xs.foreach(x => return x)
5+
0
6+
}
7+
8+
def bar(xs: List[Int]): Int = {
9+
lazy val y = if (xs.isEmpty) return -1 else xs.head
10+
y
11+
}
12+
13+
def baz(x: Int): Int =
14+
byName { return -2; 3 }
15+
16+
def byName(x: => Int): Int = x
17+
18+
def bam(): Int = { // no non-local return needed here
19+
val foo = {
20+
return -3
21+
3
22+
}
23+
foo
24+
}
25+
26+
def main(args: Array[String]) = {
27+
assert(foo(List(1, 2, 3)) == 1)
28+
assert(bar(Nil) == -1)
29+
assert(baz(3) == -2)
30+
assert(bam() == -3)
31+
}
32+
}

0 commit comments

Comments
 (0)