Skip to content

Commit 8c8e33d

Browse files
authored
Merge pull request #7007 from dotty-staging/fix-#4730
Fix #4730,#6992: Detect scope extrusions in `quoted.run` and fail-fast
2 parents 848a1cd + b35936d commit 8c8e33d

File tree

14 files changed

+111
-21
lines changed

14 files changed

+111
-21
lines changed

compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import dotty.tools.dotc.core.Types._
1414
import dotty.tools.dotc.core.tasty.TreePickler.Hole
1515
import dotty.tools.dotc.core.tasty.{PositionPickler, TastyPickler, TastyPrinter, TastyString}
1616
import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode
17+
import dotty.tools.dotc.quoted.ToolboxImpl
1718
import dotty.tools.dotc.tastyreflect.ReflectionImpl
1819

1920
import scala.internal.quoted._
@@ -35,12 +36,18 @@ object PickledQuotes {
3536
}
3637

3738
/** Transform the expression into its fully spliced Tree */
38-
def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree =
39-
healOwner(expr.asInstanceOf[TastyTreeExpr[Tree]].tree)
39+
def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = {
40+
val expr1 = expr.asInstanceOf[TastyTreeExpr[Tree]]
41+
ToolboxImpl.checkScopeId(expr1.scopeId)
42+
healOwner(expr1.tree)
43+
}
4044

4145
/** Transform the expression into its fully spliced TypeTree */
42-
def quotedTypeToTree(expr: quoted.Type[_])(implicit ctx: Context): Tree =
43-
healOwner(expr.asInstanceOf[TreeType[Tree]].typeTree)
46+
def quotedTypeToTree(tpe: quoted.Type[_])(implicit ctx: Context): Tree = {
47+
val tpe1 = tpe.asInstanceOf[TreeType[Tree]]
48+
ToolboxImpl.checkScopeId(tpe1.scopeId)
49+
healOwner(tpe1.typeTree)
50+
}
4451

4552
private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() {
4653
override def apply(tp: Type): Type = {

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import scala.collection.mutable.ListBuffer
3131
import scala.collection.mutable
3232
import config.Printers.pickling
3333
import core.quoted.PickledQuotes
34+
import dotty.tools.dotc.quoted.ToolboxImpl
3435

3536
import scala.quoted
3637
import scala.internal.quoted.{TastyTreeExpr, TreeType}
@@ -1273,8 +1274,8 @@ class TreeUnpickler(reader: TastyReader,
12731274
val args = until(end)(readTerm())
12741275
val splice = splices(idx)
12751276
def wrap(arg: Tree) =
1276-
if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg)
1277-
else new TreeType(arg)
1277+
if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg, ToolboxImpl.scopeId)
1278+
else new TreeType(arg, ToolboxImpl.scopeId)
12781279
val reifiedArgs = args.map(wrap)
12791280
val filled = if (isType) {
12801281
val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs)

compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package dotty.tools.dotc.quoted
22

3+
import dotty.tools.dotc.core.Contexts.Context
4+
35
import scala.quoted._
46

57
/** Default runners for quoted expressions */
@@ -15,10 +17,30 @@ object ToolboxImpl {
1517

1618
private[this] val driver: QuoteDriver = new QuoteDriver(appClassloader)
1719

20+
private[this] var running = false
21+
1822
def run[T](exprBuilder: QuoteContext => Expr[T]): T = synchronized {
19-
driver.run(exprBuilder, settings)
23+
try {
24+
if (running) // detected nested run
25+
throw new scala.quoted.Toolbox.RunScopeException()
26+
running = true
27+
driver.run(exprBuilder, settings)
28+
} finally {
29+
running = false
30+
}
2031
}
32+
}
2133

34+
type ScopeId = Int
35+
36+
private[dotty] def checkScopeId(id: ScopeId) given Context: Unit = {
37+
if (id != scopeId)
38+
throw new Toolbox.RunScopeException
2239
}
2340

41+
// TODO Explore more fine grained scope ids.
42+
// This id can only differentiate scope extrusion from one compiler instance to another.
43+
private[dotty] def scopeId given Context: ScopeId =
44+
the[Context].outersIterator.toList.last.hashCode()
45+
2446
}

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
3434
//
3535

3636
def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledExprArgs): scala.quoted.Expr[_] =
37-
new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args))
37+
new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args), compilerId)
3838

3939
def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledTypeArgs): scala.quoted.Type[_] =
40-
new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args))
40+
new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args), compilerId)
4141

4242
//
4343
// CONTEXT
@@ -1752,7 +1752,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
17521752
tpd.Closure(closureMethod, tss => etaExpand(new tpd.TreeOps(term).appliedToArgs(tss.head)))
17531753
case _ => term
17541754
}
1755-
new scala.internal.quoted.TastyTreeExpr(etaExpand(self))
1755+
new scala.internal.quoted.TastyTreeExpr(etaExpand(self), compilerId)
17561756
}
17571757

17581758
/** Checked cast to a `quoted.Expr[U]` */
@@ -1773,7 +1773,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
17731773
/** Convert `Type` to an `quoted.Type[_]` */
17741774
def QuotedType_seal(self: Type) given (ctx: Context): scala.quoted.Type[_] = {
17751775
val dummySpan = ctx.owner.span // FIXME
1776-
new scala.internal.quoted.TreeType(tpd.TypeTree(self).withSpan(dummySpan))
1776+
new scala.internal.quoted.TreeType(tpd.TypeTree(self).withSpan(dummySpan), compilerId)
17771777
}
17781778

17791779
//
@@ -1934,4 +1934,6 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend
19341934
private def withDefaultPos[T <: Tree](fn: given Context => T) given (ctx: Context): T =
19351935
(fn given ctx.withSource(rootPosition.source)).withSpan(rootPosition.span)
19361936

1937+
private def compilerId: Int = rootContext.outersIterator.toList.last.hashCode()
1938+
19371939
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import dotty.tools.repl.AbstractFileClassLoader
2525

2626
import scala.reflect.ClassTag
2727

28-
import dotty.tools.dotc.quoted.QuoteContext
28+
import dotty.tools.dotc.quoted.{QuoteContext, ToolboxImpl}
2929

3030
/** Utility class to splice quoted expressions */
3131
object Splicer {
@@ -251,10 +251,10 @@ object Splicer {
251251
}
252252

253253
private def interpretQuote(tree: Tree)(implicit env: Env): Object =
254-
new scala.internal.quoted.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span))
254+
new scala.internal.quoted.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span), ToolboxImpl.scopeId)
255255

256256
private def interpretTypeQuote(tree: Tree)(implicit env: Env): Object =
257-
new scala.internal.quoted.TreeType(tree)
257+
new scala.internal.quoted.TreeType(tree, ToolboxImpl.scopeId)
258258

259259
private def interpretLiteral(value: Any)(implicit env: Env): Object =
260260
value.asInstanceOf[Object]

library/src/scala/quoted/Expr.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ package quoted {
7979
package internal {
8080
package quoted {
8181

82-
import scala.quoted.{Expr, QuoteContext}
82+
import scala.quoted.Expr
8383

8484
/** An Expr backed by a tree. Only the current compiler trees are allowed.
8585
*
@@ -88,7 +88,7 @@ package internal {
8888
*
8989
* May contain references to code defined outside this TastyTreeExpr instance.
9090
*/
91-
final class TastyTreeExpr[Tree](val tree: Tree) extends Expr[Any] {
91+
final class TastyTreeExpr[Tree](val tree: Tree, val scopeId: Int) extends Expr[Any] {
9292
override def toString: String = s"Expr(<tasty tree>)"
9393
}
9494

library/src/scala/quoted/Toolbox.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,7 @@ object Toolbox {
5656
}
5757

5858
class ToolboxNotFoundException(msg: String, cause: ClassNotFoundException) extends Exception(msg, cause)
59+
60+
class RunScopeException extends Exception("Cannot call `scala.quoted.run(...)` within a macro or another `run(...)`")
61+
5962
}

library/src/scala/quoted/Type.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ package internal {
7070
package quoted {
7171

7272
/** An Type backed by a tree */
73-
final class TreeType[Tree](val typeTree: Tree) extends scala.quoted.Type[Any] {
73+
final class TreeType[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quoted.Type[Any] {
7474
override def toString: String = s"Type(<tasty tree>)"
7575
}
7676

library/src/scala/quoted/package.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ package object quoted {
1515
*
1616
* This method should not be called in a context where there is already has a `QuoteContext`
1717
* such as within a `run` or a `withQuoteContext`.
18-
*
19-
* May throw a FreeVariableError on expressions that came from a macro.
2018
*/
2119
def run[T](expr: given QuoteContext => Expr[T]) given (toolbox: Toolbox): T = toolbox.run(expr given _)
2220

tests/run-macros/i6992/Macro_1.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
import scala.quoted._, scala.quoted.matching._
3+
import delegate scala.quoted._
4+
5+
delegate for Toolbox = Toolbox.make(getClass.getClassLoader)
6+
7+
object macros {
8+
inline def mcr(x: => Any): Any = ${mcrImpl('x)}
9+
10+
class Foo { val x = 10 }
11+
12+
def mcrImpl(body: Expr[Any]) given (ctx: QuoteContext): Expr[Any] = {
13+
import ctx.tasty._
14+
try {
15+
body match {
16+
case '{$x: Foo} => run(x).x.toExpr
17+
}
18+
} catch {
19+
case _: scala.quoted.Toolbox.RunScopeException =>
20+
'{"OK"}
21+
}
22+
}
23+
}

tests/run-macros/i6992/Test_2.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import macros._
2+
3+
object Test {
4+
val foo = new Foo
5+
6+
def main(args: Array[String]) = {
7+
println(mcr {foo})
8+
}
9+
}

tests/run-with-compiler/i4730.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import scala.quoted._
2+
3+
object Test {
4+
implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader)
5+
def ret given QuoteContext: Expr[Int => Int] = '{ (x: Int) =>
6+
${
7+
val z = run('{x + 1}) // throws a RunScopeException
8+
z.toExpr
9+
}
10+
}
11+
def main(args: Array[String]): Unit = {
12+
try {
13+
run(ret).apply(10)
14+
throw new Exception
15+
} catch {
16+
case ex: scala.quoted.Toolbox.RunScopeException =>
17+
// ok
18+
}
19+
}
20+
}

tests/run-with-compiler/i6754.check

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
foo
2-
bar

tests/run-with-compiler/i6754.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ object Test {
1010
println("foo")
1111
run(x)
1212
}
13-
run(y)
13+
try {
14+
run(y)
15+
throw new Exception
16+
} catch {
17+
case ex: java.lang.reflect.InvocationTargetException =>
18+
assert(ex.getTargetException.isInstanceOf[scala.quoted.Toolbox.RunScopeException])
19+
}
1420
}
1521
}

0 commit comments

Comments
 (0)