summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/tools/nsc/transform/UnCurry.scala36
-rw-r--r--test/files/run/bug3855.scala18
2 files changed, 53 insertions, 1 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
index 85db55472d..b7a70bdf4b 100644
--- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala
+++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala
@@ -704,17 +704,51 @@ abstract class UnCurry extends InfoTransform with TypingTransformers {
/** Set of mutable local variables that are free in some inner method. */
private val freeMutableVars: mutable.Set[Symbol] = new mutable.HashSet
+ /** PP: There is apparently some degree of overlap between the CAPTURED
+ * flag and the role being filled here. I think this is how this was able
+ * to go for so long looking only at DefDef and Ident nodes, as bugs
+ * would only emerge under more complicated conditions such as #3855.
+ * I'll try to figure it all out, but if someone who already knows the
+ * whole story wants to fill it in, that too would be great.
+ */
private val freeLocalsTraverser = new Traverser {
var currentMethod: Symbol = NoSymbol
+ var maybeEscaping = false
+
+ def withEscaping(body: => Unit) {
+ val savedEscaping = maybeEscaping
+ maybeEscaping = true
+ body
+ maybeEscaping = savedEscaping
+ }
+
override def traverse(tree: Tree) = tree match {
case DefDef(_, _, _, _, _, _) =>
val lastMethod = currentMethod
currentMethod = tree.symbol
super.traverse(tree)
currentMethod = lastMethod
+ /** A method call with a by-name parameter represents escape. */
+ case Apply(fn, args) if fn.symbol.paramss.nonEmpty =>
+ traverse(fn)
+ (fn.symbol.paramss.head zip args) foreach {
+ case (param, arg) =>
+ if (param.tpe != null && param.tpe.typeSymbol == ByNameParamClass)
+ withEscaping(traverse(arg))
+ else
+ traverse(arg)
+ }
+ /** The rhs of a closure represents escape. */
+ case Function(vparams, body) =>
+ vparams foreach traverse
+ withEscaping(traverse(body))
+
+ /** The appearance of an ident outside the method where it was defined or
+ * anytime maybeEscaping is true implies escape.
+ */
case Ident(_) =>
val sym = tree.symbol
- if (sym.isVariable && sym.owner.isMethod && sym.owner != currentMethod)
+ if (sym.isVariable && sym.owner.isMethod && (maybeEscaping || sym.owner != currentMethod))
freeMutableVars += sym
case _ =>
super.traverse(tree)
diff --git a/test/files/run/bug3855.scala b/test/files/run/bug3855.scala
new file mode 100644
index 0000000000..32dfb1e23d
--- /dev/null
+++ b/test/files/run/bug3855.scala
@@ -0,0 +1,18 @@
+object Test {
+ def byval[A](a: => A) = a
+ def closure[A](f: () => A) = f()
+
+ def f1(s: String) = {
+ var n = try { s.toInt } catch { case _ => 1 }
+ byval(n)
+ }
+ def f2(s: String) = {
+ var n = try { s.toInt } catch { case _ => 1 }
+ closure(() => n)
+ }
+
+ def main(args: Array[String]) = {
+ val sum = f1("12") + f2("the witch is dead")
+ assert(sum == 13)
+ }
+}