Skip to content

Commit 6b3b5be

Browse files
authored
Merge pull request #6623 from dotty-staging/retire-non-local-returns
Deprecate nonlocal returns
2 parents 2147606 + 7f51a17 commit 6b3b5be

File tree

10 files changed

+135
-47
lines changed

10 files changed

+135
-47
lines changed

compiler/src/dotty/tools/dotc/classpath/VirtualDirectoryClassPath.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ case class VirtualDirectoryClassPath(dir: VirtualDirectory) extends ClassPath wi
1313
// From AbstractFileClassLoader
1414
private final def lookupPath(base: AbstractFile)(pathParts: Seq[String], directory: Boolean): AbstractFile = {
1515
var file: AbstractFile = base
16-
for (dirPart <- pathParts.init) {
16+
val dirParts = pathParts.init.toIterator
17+
while (dirParts.hasNext) {
18+
val dirPart = dirParts.next
1719
file = file.lookupName(dirPart, directory = true)
1820
if (file == null)
1921
return null
2022
}
21-
2223
file.lookupName(pathParts.last, directory = directory)
2324
}
2425

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -471,13 +471,10 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
471471
if entries(n).exists
472472
} yield poly.paramRefs(n)
473473

474-
def forallParams(p: TypeParamRef => Boolean): Boolean = {
475-
boundsMap.foreachBinding { (poly, entries) =>
476-
for (i <- 0 until paramCount(entries))
477-
if (isBounds(entries(i)) && !p(poly.paramRefs(i))) return false
474+
def forallParams(p: TypeParamRef => Boolean): Boolean =
475+
boundsMap.forallBinding { (poly, entries) =>
476+
!0.until(paramCount(entries)).exists(i => isBounds(entries(i)) && !p(poly.paramRefs(i)))
478477
}
479-
true
480-
}
481478

482479
def foreachParam(p: (TypeLambda, Int) => Unit): Unit =
483480
boundsMap.foreachBinding { (poly, entries) =>

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
9292
override def toTextRef(tp: SingletonType): Text = controlled {
9393
tp match {
9494
case tp: ThisType if !printDebug =>
95-
if (tp.cls.isAnonymousClass) return keywordStr("this")
96-
if (tp.cls.is(ModuleClass)) return fullNameString(tp.cls.sourceModule)
95+
if (tp.cls.isAnonymousClass) keywordStr("this")
96+
if (tp.cls.is(ModuleClass)) fullNameString(tp.cls.sourceModule)
97+
else super.toTextRef(tp)
9798
case _ =>
99+
super.toTextRef(tp)
98100
}
99-
super.toTextRef(tp)
100101
}
101102

102103
override def toTextPrefix(tp: Type): Text = controlled {
@@ -105,15 +106,15 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
105106
else if (homogenizedView) isEmptyPrefix(sym) // drop <root> and anonymous classes, but not scala, Predef.
106107
else isOmittablePrefix(sym)
107108
tp match {
108-
case tp: ThisType =>
109-
if (isOmittable(tp.cls)) return ""
109+
case tp: ThisType if isOmittable(tp.cls) =>
110+
""
110111
case tp @ TermRef(pre, _) =>
111112
val sym = tp.symbol
112-
if (sym.isPackageObject && !homogenizedView) return toTextPrefix(pre)
113-
if (isOmittable(sym)) return ""
114-
case _ =>
113+
if (sym.isPackageObject && !homogenizedView) toTextPrefix(pre)
114+
else if (isOmittable(sym)) ""
115+
else super.toTextPrefix(tp)
116+
case _ => super.toTextPrefix(tp)
115117
}
116-
super.toTextPrefix(tp)
117118
}
118119

119120
override protected def toTextParents(parents: List[Type]): Text =
@@ -180,69 +181,69 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
180181
homogenize(tp) match {
181182
case tp @ AppliedType(tycon, args) =>
182183
val cls = tycon.typeSymbol
183-
if (tycon.isRepeatedParam) return toTextLocal(args.head) ~ "*"
184-
if (defn.isFunctionClass(cls)) return toTextFunction(args, cls.name.isImplicitFunction, cls.name.isErasedFunction)
185-
if (tp.tupleArity >= 2 && !printDebug) return toTextTuple(tp.tupleElementTypes)
186-
if (isInfixType(tp)) {
184+
if (tycon.isRepeatedParam) toTextLocal(args.head) ~ "*"
185+
else if (defn.isFunctionClass(cls)) toTextFunction(args, cls.name.isImplicitFunction, cls.name.isErasedFunction)
186+
else if (tp.tupleArity >= 2 && !printDebug) toTextTuple(tp.tupleElementTypes)
187+
else if (isInfixType(tp)) {
187188
val l :: r :: Nil = args
188189
val opName = tyconName(tycon)
189-
190-
return toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
190+
toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) }
191191
}
192+
else super.toText(tp)
192193

193194
// Since RefinedPrinter, unlike PlainPrinter, can output right-associative type-operators, we must override handling
194195
// of AndType and OrType to account for associativity
195196
case AndType(tp1, tp2) =>
196-
return toTextInfixType(tpnme.raw.AMP, tp1, tp2) { toText(tpnme.raw.AMP) }
197+
toTextInfixType(tpnme.raw.AMP, tp1, tp2) { toText(tpnme.raw.AMP) }
197198
case OrType(tp1, tp2) =>
198-
return toTextInfixType(tpnme.raw.BAR, tp1, tp2) { toText(tpnme.raw.BAR) }
199-
199+
toTextInfixType(tpnme.raw.BAR, tp1, tp2) { toText(tpnme.raw.BAR) }
200200
case EtaExpansion(tycon) if !printDebug =>
201-
return toText(tycon)
201+
toText(tycon)
202202
case tp: RefinedType if defn.isFunctionType(tp) =>
203-
return toTextDependentFunction(tp.refinedInfo.asInstanceOf[MethodType])
203+
toTextDependentFunction(tp.refinedInfo.asInstanceOf[MethodType])
204204
case tp: TypeRef =>
205205
if (tp.symbol.isAnonymousClass && !ctx.settings.uniqid.value)
206-
return toText(tp.info)
207-
if (tp.symbol.is(Param))
206+
toText(tp.info)
207+
else if (tp.symbol.is(Param))
208208
tp.prefix match {
209209
case pre: ThisType if pre.cls == tp.symbol.owner =>
210-
return nameString(tp.symbol)
211-
case _ =>
210+
nameString(tp.symbol)
211+
case _ => super.toText(tp)
212212
}
213+
else super.toText(tp)
213214
case tp: ExprType =>
214-
return exprToText(tp)
215+
exprToText(tp)
215216
case ErasedValueType(tycon, underlying) =>
216-
return "ErasedValueType(" ~ toText(tycon) ~ ", " ~ toText(underlying) ~ ")"
217+
"ErasedValueType(" ~ toText(tycon) ~ ", " ~ toText(underlying) ~ ")"
217218
case tp: ClassInfo =>
218-
return toTextParents(tp.parents) ~~ "{...}"
219+
toTextParents(tp.parents) ~~ "{...}"
219220
case JavaArrayType(elemtp) =>
220-
return toText(elemtp) ~ "[]"
221+
toText(elemtp) ~ "[]"
221222
case tp: AnnotatedType if homogenizedView =>
222223
// Positions of annotations in types are not serialized
223224
// (they don't need to because we keep the original type tree with
224225
// the original annotation anyway. Therefore, there will always be
225226
// one version of the annotation tree that has the correct positions).
226-
return withoutPos(super.toText(tp))
227+
withoutPos(super.toText(tp))
227228
case tp: SelectionProto =>
228-
return "?{ " ~ toText(tp.name) ~
229+
"?{ " ~ toText(tp.name) ~
229230
(Str(" ") provided !tp.name.toSimpleName.last.isLetterOrDigit) ~
230231
": " ~ toText(tp.memberProto) ~ " }"
231232
case tp: ViewProto =>
232-
return toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType)
233+
toText(tp.argType) ~ " ?=>? " ~ toText(tp.resultType)
233234
case tp @ FunProto(args, resultType) =>
234235
val argsText = args match {
235236
case dummyTreeOfType(tp) :: Nil if !(tp isRef defn.NullClass) => "null: " ~ toText(tp)
236237
case _ => toTextGlobal(args, ", ")
237238
}
238-
return "[applied to " ~ (Str("given ") provided tp.isContextualMethod) ~ (Str("erased ") provided tp.isErasedMethod) ~ "(" ~ argsText ~ ") returning " ~ toText(resultType) ~ "]"
239+
"[applied to " ~ (Str("given ") provided tp.isContextualMethod) ~ (Str("erased ") provided tp.isErasedMethod) ~ "(" ~ argsText ~ ") returning " ~ toText(resultType) ~ "]"
239240
case IgnoredProto(ignored) =>
240-
return "?" ~ (("(ignored: " ~ toText(ignored) ~ ")") provided printDebug)
241+
"?" ~ (("(ignored: " ~ toText(ignored) ~ ")") provided printDebug)
241242
case tp @ PolyProto(targs, resType) =>
242-
return "[applied to [" ~ toTextGlobal(targs, ", ") ~ "] returning " ~ toText(resType)
243+
"[applied to [" ~ toTextGlobal(targs, ", ") ~ "] returning " ~ toText(resType)
243244
case _ =>
245+
super.toText(tp)
244246
}
245-
super.toText(tp)
246247
}
247248

248249
protected def exprToText(tp: ExprType): Text =

compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class NonLocalReturns extends MiniPhase {
8686
}
8787

8888
override def transformReturn(tree: Return)(implicit ctx: Context): Tree =
89-
if (isNonLocalReturn(tree)) nonLocalReturnThrow(tree.expr, tree.from.symbol).withSpan(tree.span)
90-
else tree
89+
if (isNonLocalReturn(tree)) {
90+
if (!ctx.scala2Mode)
91+
ctx.strictWarning("Non local returns are deprecated; use scala.util.control.NonLocalReturns instead", tree.sourcePos)
92+
nonLocalReturnThrow(tree.expr, tree.from.symbol).withSpan(tree.span)
93+
} else tree
9194
}

compiler/src/dotty/tools/dotc/transform/Splicer.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,16 @@ object Splicer {
355355
// Interpret `foo(j = x, i = y)` which it is expanded to
356356
// `val j$1 = x; val i$1 = y; foo(i = y, j = x)`
357357
case Block(stats, expr) =>
358+
var unexpected: Option[Result] = None
358359
val newEnv = stats.foldLeft(env)((accEnv, stat) => stat match {
359360
case stat: ValDef if stat.symbol.is(Synthetic) =>
360361
accEnv.updated(stat.name, interpretTree(stat.rhs)(accEnv))
361-
case stat => return unexpectedTree(stat)
362+
case stat =>
363+
if (unexpected.isEmpty)
364+
unexpected = Some(unexpectedTree(stat))
365+
accEnv
362366
})
363-
interpretTree(expr)(newEnv)
367+
unexpected.getOrElse(interpretTree(expr)(newEnv))
364368
case NamedArg(_, arg) => interpretTree(arg)
365369

366370
case Inlined(_, Nil, expansion) => interpretTree(expansion)

compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ abstract class SimpleIdentityMap[K <: AnyRef, +V >: Null <: AnyRef] extends (K =
1313
def contains(k: K): Boolean = apply(k) != null
1414
def mapValuesNow[V1 >: V <: AnyRef](f: (K, V1) => V1): SimpleIdentityMap[K, V1]
1515
def foreachBinding(f: (K, V) => Unit): Unit
16+
def forallBinding(f: (K, V) => Boolean): Boolean
1617
def map2[T](f: (K, V) => T): List[T] = {
1718
val buf = new ListBuffer[T]
1819
foreachBinding((k, v) => buf += f(k, v))
@@ -37,6 +38,7 @@ object SimpleIdentityMap {
3738
def updated[V1 >: Null <: AnyRef](k: AnyRef, v: V1) = new Map1(k, v)
3839
def mapValuesNow[V1 >: Null <: AnyRef](f: (AnyRef, V1) => V1) = this
3940
def foreachBinding(f: (AnyRef, Null) => Unit) = ()
41+
def forallBinding(f: (AnyRef, Null) => Boolean) = true
4042
}
4143

4244
def Empty[K <: AnyRef]: SimpleIdentityMap[K, Null] = myEmpty.asInstanceOf[SimpleIdentityMap[K, Null]]
@@ -57,6 +59,7 @@ object SimpleIdentityMap {
5759
if (v1 eq w1) this else new Map1(k1, w1)
5860
}
5961
def foreachBinding(f: (K, V) => Unit): Unit = f(k1, v1)
62+
def forallBinding(f: (K, V) => Boolean): Boolean = f(k1, v1)
6063
}
6164

6265
class Map2[K <: AnyRef, +V >: Null <: AnyRef] (k1: K, v1: V, k2: K, v2: V) extends SimpleIdentityMap[K, V] {
@@ -79,6 +82,7 @@ object SimpleIdentityMap {
7982
else new Map2(k1, w1, k2, w2)
8083
}
8184
def foreachBinding(f: (K, V) => Unit): Unit = { f(k1, v1); f(k2, v2) }
85+
def forallBinding(f: (K, V) => Boolean): Boolean = f(k1, v1) && f(k2, v2)
8286
}
8387

8488
class Map3[K <: AnyRef, +V >: Null <: AnyRef] (k1: K, v1: V, k2: K, v2: V, k3: K, v3: V) extends SimpleIdentityMap[K, V] {
@@ -104,6 +108,7 @@ object SimpleIdentityMap {
104108
else new Map3(k1, w1, k2, w2, k3, w3)
105109
}
106110
def foreachBinding(f: (K, V) => Unit): Unit = { f(k1, v1); f(k2, v2); f(k3, v3) }
111+
def forallBinding(f: (K, V) => Boolean): Boolean = f(k1, v1) && f(k2, v2) && f(k3, v3)
107112
}
108113

109114
class Map4[K <: AnyRef, +V >: Null <: AnyRef] (k1: K, v1: V, k2: K, v2: V, k3: K, v3: V, k4: K, v4: V) extends SimpleIdentityMap[K, V] {
@@ -132,6 +137,7 @@ object SimpleIdentityMap {
132137
else new Map4(k1, w1, k2, w2, k3, w3, k4, w4)
133138
}
134139
def foreachBinding(f: (K, V) => Unit): Unit = { f(k1, v1); f(k2, v2); f(k3, v3); f(k4, v4) }
140+
def forallBinding(f: (K, V) => Boolean): Boolean = f(k1, v1) && f(k2, v2) && f(k3, v3) && f(k4, v4)
135141
}
136142

137143
class MapMore[K <: AnyRef, +V >: Null <: AnyRef](bindings: Array[AnyRef]) extends SimpleIdentityMap[K, V] {
@@ -223,5 +229,15 @@ object SimpleIdentityMap {
223229
i += 2
224230
}
225231
}
232+
233+
def forallBinding(f: (K, V) => Boolean): Boolean = {
234+
var i = 0
235+
while (i < bindings.length) {
236+
if (!f(key(i), value(i)))
237+
return false
238+
i += 2
239+
}
240+
return true
241+
}
226242
}
227243
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
layout: doc-page
3+
title: Deprecated: Nonlocal Returns
4+
---
5+
6+
Returning from nested anonymous functions has been deprecated. Nonlocal returns are implemented by throwing and catching `scala.runtime.NonLocalReturnException`-s. This is rarely what is intendant by the programmer. It can be problematic because of the hidden performance cost of throwing and catching exceptions. Furthermore, it is a leaky implementation: a catch-all exception handler can intercept a `NonLocalReturnException`.
7+
8+
A drop-in library replacement is provided in `scala.util.control.NonLocalReturns`:
9+
10+
```scala
11+
import scala.util.control.NonLocalReturns._
12+
13+
returning { ... throwReturn(x) ... }
14+
```

docs/sidebar.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ sidebar:
159159
url: docs/reference/dropped-features/auto-apply.html
160160
- title: Weak Conformance
161161
url: docs/reference/dropped-features/weak-conformance.html
162+
- title: Nonlocal Returns
163+
url: docs/reference/dropped-features/nonlocal-returns.html
162164
- title: Contributing
163165
subsection:
164166
- title: Getting Started
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package scala.util.control
2+
3+
/** Library implementation of nonlocal return.
4+
*
5+
* Usage:
6+
*
7+
* import scala.util.control.NonLocalReturns._
8+
*
9+
* returning { ... throwReturn(x) ... }
10+
*/
11+
object NonLocalReturns {
12+
class ReturnThrowable[T] extends ControlThrowable {
13+
private var myResult: T = _
14+
def throwReturn(result: T): Nothing = {
15+
myResult = result
16+
throw this
17+
}
18+
def result: T = myResult
19+
}
20+
21+
/** Performs a nonlocal return by throwing an exception. */
22+
def throwReturn[T](result: T) given (returner: ReturnThrowable[T]): Nothing =
23+
returner.throwReturn(result)
24+
25+
/** Enable nonlocal returns in `op`. */
26+
def returning[T](op: given ReturnThrowable[T] => T): T = {
27+
val returner = new ReturnThrowable[T]
28+
try op given returner
29+
catch {
30+
case ex: ReturnThrowable[T] =>
31+
if (ex.eq(returner)) ex.result else throw ex
32+
}
33+
}
34+
}

tests/run/nonlocal-return.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import scala.util.control.NonLocalReturns._
2+
3+
object Test {
4+
def has(xs: List[Int], elem: Int) =
5+
returning {
6+
for (x <- xs)
7+
if (x == elem) throwReturn(true)
8+
false
9+
}
10+
11+
def main(arg: Array[String]): Unit = {
12+
assert(has(1 :: 2 :: Nil, 1))
13+
assert(has(1 :: 2 :: Nil, 2))
14+
assert(!has(1 :: 2 :: Nil, 3))
15+
}
16+
}

0 commit comments

Comments
 (0)