Skip to content

Commit

Permalink
Fix scala#7142: Detect scope extrusions in macros and run
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed Nov 1, 2019
1 parent 9e7ec22 commit b8ee2a6
Show file tree
Hide file tree
Showing 21 changed files with 165 additions and 1 deletion.
36 changes: 35 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Splicer.scala
Expand Up @@ -44,7 +44,9 @@ object Splicer {
try {
// Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree
val interpretedExpr = interpreter.interpret[scala.quoted.QuoteContext => scala.quoted.Expr[Any]](tree)
interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(QuoteContext())))
val interpretedTree = interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(QuoteContext())))
checkFreeVariables(interpretedTree)
interpretedTree
}
catch {
case ex: CompilationUnit.SuspendException =>
Expand All @@ -63,6 +65,38 @@ object Splicer {
}
}

def checkFreeVariables(tree: Tree)(given ctx: Context): Unit =
println()
println(tree.show)
println(tree)
println(freeVariables(tree))
println()
println(tree.symbol.exists)
println(!tree.symbol.is(Package))
println(!tree.symbol.owner.isStaticOwner)
println(!ctx.owner.ownersIterator.contains(tree.symbol.owner))
println()
println(ctx.owner.ownersIterator.toList)
println(tree.symbol)
println(tree.symbol.owner)
println(tree.symbol.owner.isLocalDummy)
println(tree.symbol.owner.is(Local))
println()
println()
freeVariables(tree).foreach(x => ctx.error(em"$x was used outside the scope where it was defined", x.symbol.sourcePos))

private def freeVariables(tree: Tree)(given ctx: Context): List[Tree] =
def isFreeVariable(sym: Symbol): Boolean =
sym.exists && !sym.is(Package) && !sym.owner.isStaticOwner && !ctx.owner.ownersIterator.contains(sym.owner)
new TreeAccumulator[List[Tree]] {
private[this] val local = collection.mutable.HashSet.empty[Symbol]
def apply(x: List[Tree], tree: Tree)(given ctx: Context): List[Tree] = tree match {
case tree: Ident if local(tree.symbol) => tree :: x
case _ => foldOver(x, tree)
}
}.apply(Nil, tree)


/** Check that the Tree can be spliced. `${'{xyz}}` becomes `xyz`
* and for `$xyz` the tree of `xyz` is interpreted for which the
* resulting expression is returned as a `Tree`
Expand Down
2 changes: 2 additions & 0 deletions staging/src/scala/quoted/staging/QuoteCompiler.scala
Expand Up @@ -68,6 +68,8 @@ private class QuoteCompiler extends Compiler {
val qctx = dotty.tools.dotc.quoted.QuoteContext()
val quoted = PickledQuotes.quotedExprToTree(exprUnit.exprBuilder.apply(qctx))(ctx.withOwner(meth))

dotty.tools.dotc.transform.Splicer.checkFreeVariables(quoted)

getLiteral(quoted) match {
case Some(value) =>
result = Right(value)
Expand Down
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ (x: Int) => ${ v = '{x}; v } } // error
'{$q($v)}
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
8 changes: 8 additions & 0 deletions tests/neg-macros/i7142b/Macro_1.scala
@@ -0,0 +1,8 @@
package macros
import scala.quoted._
import scala.util.control.NonLocalReturns._

def oops(given QuoteContext): Expr[Int] =
returning('{ { (x: Int) => ${ throwReturn('x) }} apply 0 }) // error

inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142b/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142c/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ val x: Int = 8; ${ v = '{x}; v } } // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142c/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142d/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ ??? match { case x => ${ v = '{x}; v } } } // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142d/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142e/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ def x: Int = 8; ${ v = '{x}; v } } // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142e/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142f/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ def f(x: Int): Int = ${ v = '{x}; v } } // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142f/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142g/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ class Foo(x: Int) { ${ v = 'x; '{} } }} // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142g/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142i/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{0};
val q = '{ class Foo { val x = 3; ${ v = 'x; '{} } }} // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142i/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
9 changes: 9 additions & 0 deletions tests/neg-macros/i7142j/Macro_1.scala
@@ -0,0 +1,9 @@
package macros
import scala.quoted._

def oops(given QuoteContext) = {
var v = '{};
val q = '{ var x: Int = 8; ${ v = '{x = 9}; v } } // error
v
}
inline def test = ${oops}
4 changes: 4 additions & 0 deletions tests/neg-macros/i7142j/Test_2.scala
@@ -0,0 +1,4 @@

object Test {
macros.test
}
12 changes: 12 additions & 0 deletions tests/run-staging/i7142.scala
@@ -0,0 +1,12 @@
import scala.quoted._
import scala.quoted.staging._
import scala.util.control.NonLocalReturns._

object Test {
given Toolbox = Toolbox.make(getClass.getClassLoader)
def main(args: Array[String]): Unit =
try run {returning('{ { (x: Int) => ${ throwReturn('x) }} apply 0 })}
catch {
case ex: dotty.tools.dotc.reporting.diagnostic.messages.Error if ex.getMessage == "x was used outside the scope where it was defined" => // OK
}
}

0 comments on commit b8ee2a6

Please sign in to comment.