diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index fbac02bebd8c..91f26147452d 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -14,6 +14,7 @@ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.tasty.TreePickler.Hole import dotty.tools.dotc.core.tasty.{PositionPickler, TastyPickler, TastyPrinter, TastyString} import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode +import dotty.tools.dotc.quoted.ToolboxImpl import dotty.tools.dotc.tastyreflect.ReflectionImpl import scala.internal.quoted._ @@ -35,12 +36,18 @@ object PickledQuotes { } /** Transform the expression into its fully spliced Tree */ - def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = - healOwner(expr.asInstanceOf[TastyTreeExpr[Tree]].tree) + def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = { + val expr1 = expr.asInstanceOf[TastyTreeExpr[Tree]] + ToolboxImpl.checkScopeId(expr1.scopeId) + healOwner(expr1.tree) + } /** Transform the expression into its fully spliced TypeTree */ - def quotedTypeToTree(expr: quoted.Type[_])(implicit ctx: Context): Tree = - healOwner(expr.asInstanceOf[TreeType[Tree]].typeTree) + def quotedTypeToTree(tpe: quoted.Type[_])(implicit ctx: Context): Tree = { + val tpe1 = tpe.asInstanceOf[TreeType[Tree]] + ToolboxImpl.checkScopeId(tpe1.scopeId) + healOwner(tpe1.typeTree) + } private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() { override def apply(tp: Type): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f729c48ec9c0..259ac00b8cce 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -31,6 +31,7 @@ import scala.collection.mutable.ListBuffer import scala.collection.mutable import config.Printers.pickling import core.quoted.PickledQuotes +import dotty.tools.dotc.quoted.ToolboxImpl import scala.quoted import scala.internal.quoted.{TastyTreeExpr, TreeType} @@ -1273,8 +1274,8 @@ class TreeUnpickler(reader: TastyReader, val args = until(end)(readTerm()) val splice = splices(idx) def wrap(arg: Tree) = - if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg) - else new TreeType(arg) + if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg, ToolboxImpl.scopeId) + else new TreeType(arg, ToolboxImpl.scopeId) val reifiedArgs = args.map(wrap) val filled = if (isType) { val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs) diff --git a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala index 9e2f934040ea..5443aeba3f15 100644 --- a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala @@ -1,5 +1,7 @@ package dotty.tools.dotc.quoted +import dotty.tools.dotc.core.Contexts.Context + import scala.quoted._ /** Default runners for quoted expressions */ @@ -15,10 +17,30 @@ object ToolboxImpl { private[this] val driver: QuoteDriver = new QuoteDriver(appClassloader) + private[this] var running = false + def run[T](exprBuilder: QuoteContext => Expr[T]): T = synchronized { - driver.run(exprBuilder, settings) + try { + if (running) // detected nested run + throw new scala.quoted.Toolbox.RunScopeException() + running = true + driver.run(exprBuilder, settings) + } finally { + running = false + } } + } + type ScopeId = Int + + private[dotty] def checkScopeId(id: ScopeId) given Context: Unit = { + if (id != scopeId) + throw new Toolbox.RunScopeException } + // TODO Explore more fine grained scope ids. + // This id can only differentiate scope extrusion from one compiler instance to another. + private[dotty] def scopeId given Context: ScopeId = + the[Context].outersIterator.toList.last.hashCode() + } diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index b04fcc45bf04..8eb0bf628cce 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -34,10 +34,10 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend // def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledExprArgs): scala.quoted.Expr[_] = - new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args)) + new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args), compilerId) def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledTypeArgs): scala.quoted.Type[_] = - new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args)) + new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args), compilerId) // // CONTEXT @@ -1752,7 +1752,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend tpd.Closure(closureMethod, tss => etaExpand(new tpd.TreeOps(term).appliedToArgs(tss.head))) case _ => term } - new scala.internal.quoted.TastyTreeExpr(etaExpand(self)) + new scala.internal.quoted.TastyTreeExpr(etaExpand(self), compilerId) } /** Checked cast to a `quoted.Expr[U]` */ @@ -1773,7 +1773,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend /** Convert `Type` to an `quoted.Type[_]` */ def QuotedType_seal(self: Type) given (ctx: Context): scala.quoted.Type[_] = { val dummySpan = ctx.owner.span // FIXME - new scala.internal.quoted.TreeType(tpd.TypeTree(self).withSpan(dummySpan)) + new scala.internal.quoted.TreeType(tpd.TypeTree(self).withSpan(dummySpan), compilerId) } // @@ -1934,4 +1934,6 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend private def withDefaultPos[T <: Tree](fn: given Context => T) given (ctx: Context): T = (fn given ctx.withSource(rootPosition.source)).withSpan(rootPosition.span) + private def compilerId: Int = rootContext.outersIterator.toList.last.hashCode() + } diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index f213fa0bbd98..8ce3420db72b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -25,7 +25,7 @@ import dotty.tools.repl.AbstractFileClassLoader import scala.reflect.ClassTag -import dotty.tools.dotc.quoted.QuoteContext +import dotty.tools.dotc.quoted.{QuoteContext, ToolboxImpl} /** Utility class to splice quoted expressions */ object Splicer { @@ -251,10 +251,10 @@ object Splicer { } private def interpretQuote(tree: Tree)(implicit env: Env): Object = - new scala.internal.quoted.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span)) + new scala.internal.quoted.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span), ToolboxImpl.scopeId) private def interpretTypeQuote(tree: Tree)(implicit env: Env): Object = - new scala.internal.quoted.TreeType(tree) + new scala.internal.quoted.TreeType(tree, ToolboxImpl.scopeId) private def interpretLiteral(value: Any)(implicit env: Env): Object = value.asInstanceOf[Object] diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index 786065499432..9cc2f624647d 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -79,7 +79,7 @@ package quoted { package internal { package quoted { - import scala.quoted.{Expr, QuoteContext} + import scala.quoted.Expr /** An Expr backed by a tree. Only the current compiler trees are allowed. * @@ -88,7 +88,7 @@ package internal { * * May contain references to code defined outside this TastyTreeExpr instance. */ - final class TastyTreeExpr[Tree](val tree: Tree) extends Expr[Any] { + final class TastyTreeExpr[Tree](val tree: Tree, val scopeId: Int) extends Expr[Any] { override def toString: String = s"Expr()" } diff --git a/library/src/scala/quoted/Toolbox.scala b/library/src/scala/quoted/Toolbox.scala index 42fdb2d895fa..e0e24c82f856 100644 --- a/library/src/scala/quoted/Toolbox.scala +++ b/library/src/scala/quoted/Toolbox.scala @@ -56,4 +56,7 @@ object Toolbox { } class ToolboxNotFoundException(msg: String, cause: ClassNotFoundException) extends Exception(msg, cause) + + class RunScopeException extends Exception("Cannot call `scala.quoted.run(...)` within a macro or another `run(...)`") + } diff --git a/library/src/scala/quoted/Type.scala b/library/src/scala/quoted/Type.scala index 25a6ed515c7a..d9a578721b5c 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src/scala/quoted/Type.scala @@ -70,7 +70,7 @@ package internal { package quoted { /** An Type backed by a tree */ - final class TreeType[Tree](val typeTree: Tree) extends scala.quoted.Type[Any] { + final class TreeType[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quoted.Type[Any] { override def toString: String = s"Type()" } diff --git a/library/src/scala/quoted/package.scala b/library/src/scala/quoted/package.scala index 0868eee2188e..a601f069016d 100644 --- a/library/src/scala/quoted/package.scala +++ b/library/src/scala/quoted/package.scala @@ -15,8 +15,6 @@ package object quoted { * * This method should not be called in a context where there is already has a `QuoteContext` * such as within a `run` or a `withQuoteContext`. - * - * May throw a FreeVariableError on expressions that came from a macro. */ def run[T](expr: given QuoteContext => Expr[T]) given (toolbox: Toolbox): T = toolbox.run(expr given _) diff --git a/tests/run-macros/i6992/Macro_1.scala b/tests/run-macros/i6992/Macro_1.scala new file mode 100644 index 000000000000..f0e76e6b07a2 --- /dev/null +++ b/tests/run-macros/i6992/Macro_1.scala @@ -0,0 +1,23 @@ + +import scala.quoted._, scala.quoted.matching._ +import delegate scala.quoted._ + +delegate for Toolbox = Toolbox.make(getClass.getClassLoader) + +object macros { + inline def mcr(x: => Any): Any = ${mcrImpl('x)} + + class Foo { val x = 10 } + + def mcrImpl(body: Expr[Any]) given (ctx: QuoteContext): Expr[Any] = { + import ctx.tasty._ + try { + body match { + case '{$x: Foo} => run(x).x.toExpr + } + } catch { + case _: scala.quoted.Toolbox.RunScopeException => + '{"OK"} + } + } +} diff --git a/tests/run-macros/i6992/Test_2.scala b/tests/run-macros/i6992/Test_2.scala new file mode 100644 index 000000000000..13ff747a80c5 --- /dev/null +++ b/tests/run-macros/i6992/Test_2.scala @@ -0,0 +1,9 @@ +import macros._ + +object Test { + val foo = new Foo + + def main(args: Array[String]) = { + println(mcr {foo}) + } +} diff --git a/tests/run-with-compiler/i4730.scala b/tests/run-with-compiler/i4730.scala new file mode 100644 index 000000000000..f6007b18d279 --- /dev/null +++ b/tests/run-with-compiler/i4730.scala @@ -0,0 +1,20 @@ +import scala.quoted._ + +object Test { + implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader) + def ret given QuoteContext: Expr[Int => Int] = '{ (x: Int) => + ${ + val z = run('{x + 1}) // throws a RunScopeException + z.toExpr + } + } + def main(args: Array[String]): Unit = { + try { + run(ret).apply(10) + throw new Exception + } catch { + case ex: scala.quoted.Toolbox.RunScopeException => + // ok + } + } +} diff --git a/tests/run-with-compiler/i6754.check b/tests/run-with-compiler/i6754.check index 3bd1f0e29744..257cc5642cb1 100644 --- a/tests/run-with-compiler/i6754.check +++ b/tests/run-with-compiler/i6754.check @@ -1,2 +1 @@ foo -bar diff --git a/tests/run-with-compiler/i6754.scala b/tests/run-with-compiler/i6754.scala index da60fc80dc0f..596dc0900caa 100644 --- a/tests/run-with-compiler/i6754.scala +++ b/tests/run-with-compiler/i6754.scala @@ -10,6 +10,12 @@ object Test { println("foo") run(x) } - run(y) + try { + run(y) + throw new Exception + } catch { + case ex: java.lang.reflect.InvocationTargetException => + assert(ex.getTargetException.isInstanceOf[scala.quoted.Toolbox.RunScopeException]) + } } }