From aadd17fd320e24ce02fb14cc8ead00b55cef9c05 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 8 May 2018 10:47:49 +0200 Subject: [PATCH 1/2] Handle QuotedError when evaluating holes in quotes These holes are exapnded while unpickling in PickledQuotes.quotedExprToTree --- .../dotty/tools/dotc/transform/Splicer.scala | 34 +++++++++---------- tests/neg/quote-error-2/Macro_1.scala | 12 +++++++ tests/neg/quote-error-2/Test_2.scala | 6 ++++ 3 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 tests/neg/quote-error-2/Macro_1.scala create mode 100644 tests/neg/quote-error-2/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 3877c36cdb59..3b7e5630453f 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -36,7 +36,22 @@ object Splicer { val liftedArgs = getLiftedArgs(call, bindings) val interpreter = new Interpreter(pos, classLoader) val interpreted = interpreter.interpretCallToSymbol[Seq[Any] => Object](call.symbol) - interpreted.flatMap(lambda => evaluateLambda(lambda, liftedArgs, pos)).fold(tree)(PickledQuotes.quotedExprToTree) + try { + val evaluated = interpreted.map(lambda => lambda(liftedArgs).asInstanceOf[scala.quoted.Expr[Nothing]]) + evaluated.fold(tree)(PickledQuotes.quotedExprToTree) + } catch { + case ex: scala.quoted.QuoteError => + ctx.error(ex.getMessage, pos) + EmptyTree + case NonFatal(ex) => + val msg = + s"""Failed to evaluate inlined quote. + | Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage} + | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")} + """.stripMargin + ctx.error(msg, pos) + EmptyTree + } } /** Given the inline code and bindings, compute the lifted arguments that will be used to execute the macro @@ -72,23 +87,6 @@ object Splicer { liftArgs(call.symbol.info, allArgs(call, Nil)) } - private def evaluateLambda(lambda: Seq[Any] => Object, args: Seq[Any], pos: Position)(implicit ctx: Context): Option[scala.quoted.Expr[Nothing]] = { - try Some(lambda(args).asInstanceOf[scala.quoted.Expr[Nothing]]) - catch { - case ex: scala.quoted.QuoteError => - ctx.error(ex.getMessage, pos) - None - case NonFatal(ex) => - val msg = - s"""Failed to evaluate inlined quote. - | Caused by: ${ex.getMessage} - | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")} - """.stripMargin - ctx.error(msg, pos) - None - } - } - /** Tree interpreter that can interpret calls to static methods with it's default arguments * * The interpreter assumes that all calls in the trees are to code that was diff --git a/tests/neg/quote-error-2/Macro_1.scala b/tests/neg/quote-error-2/Macro_1.scala new file mode 100644 index 000000000000..0cce8284f871 --- /dev/null +++ b/tests/neg/quote-error-2/Macro_1.scala @@ -0,0 +1,12 @@ +import quoted._ + +object Macro_1 { + inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + def fooImpl(b: Boolean): Expr[Unit] = + '(println(~msg(b))) + + def msg(b: Boolean): Expr[String] = + if (b) '("foo(true)") + else QuoteError("foo cannot be called with false") + +} diff --git a/tests/neg/quote-error-2/Test_2.scala b/tests/neg/quote-error-2/Test_2.scala new file mode 100644 index 000000000000..fccefeaad1f3 --- /dev/null +++ b/tests/neg/quote-error-2/Test_2.scala @@ -0,0 +1,6 @@ +import Macro_1._ + +object Test_2 { + foo(true) + foo(false) // error: foo cannot be called with false +} From 3c187156e6667b90c852f9a482daba22254041fb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 17 May 2018 22:25:09 +0200 Subject: [PATCH 2/2] Outline macro evaluation exception handling --- .../dotty/tools/dotc/transform/Splicer.scala | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 3b7e5630453f..3e667908844e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -36,21 +36,10 @@ object Splicer { val liftedArgs = getLiftedArgs(call, bindings) val interpreter = new Interpreter(pos, classLoader) val interpreted = interpreter.interpretCallToSymbol[Seq[Any] => Object](call.symbol) - try { + evaluateMacro(pos) { + // Some parts of the macro are evaluated during the unpickling performed in quotedExprToTree val evaluated = interpreted.map(lambda => lambda(liftedArgs).asInstanceOf[scala.quoted.Expr[Nothing]]) evaluated.fold(tree)(PickledQuotes.quotedExprToTree) - } catch { - case ex: scala.quoted.QuoteError => - ctx.error(ex.getMessage, pos) - EmptyTree - case NonFatal(ex) => - val msg = - s"""Failed to evaluate inlined quote. - | Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage} - | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")} - """.stripMargin - ctx.error(msg, pos) - EmptyTree } } @@ -87,6 +76,24 @@ object Splicer { liftArgs(call.symbol.info, allArgs(call, Nil)) } + /* Evaluate the code in the macro and handle exceptions durring evaluation */ + private def evaluateMacro(pos: Position)(code: => Tree)(implicit ctx: Context): Tree = { + try code + catch { + case ex: scala.quoted.QuoteError => + ctx.error(ex.getMessage, pos) + EmptyTree + case NonFatal(ex) => + val msg = + s"""Failed to evaluate inlined quote. + | Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage} + | ${ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.Splicer$").init.mkString("\n ")} + """.stripMargin + ctx.error(msg, pos) + EmptyTree + } + } + /** Tree interpreter that can interpret calls to static methods with it's default arguments * * The interpreter assumes that all calls in the trees are to code that was