diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 44006ad1d934..541e71561a1e 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 44006ad1d934160054b6ad1a15aa188b54860b94 +Subproject commit 541e71561a1ec377be7936a00ad954365c290114 diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index fb523e9dbfa6..6c117812cdfc 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -45,16 +45,6 @@ object Splicer { interpretedExpr.fold(tree)(x => PickledQuotes.quotedExprToTree(x)) } catch { - case ex: scala.quoted.QuoteError => - val pos1 = ex.from match { - case None => pos - case Some(expr) => - val reflect: scala.tasty.Reflection = ReflectionImpl(ctx) - import reflect._ - expr.unseal.underlyingArgument.pos.asInstanceOf[SourcePosition] - } - ctx.error(ex.getMessage, pos1) - EmptyTree case NonFatal(ex) => val msg = s"""Failed to evaluate macro. @@ -205,19 +195,13 @@ object Splicer { sw.write("\n") throw new StopInterpretation(sw.toString, pos) case ex: InvocationTargetException => - ex.getCause match { - case cause: scala.quoted.QuoteError => - throw cause - case _ => - val sw = new StringWriter() - sw.write("An exception occurred while executing macro expansion\n") - sw.write(ex.getTargetException.getMessage) - sw.write("\n") - ex.getTargetException.printStackTrace(new PrintWriter(sw)) - sw.write("\n") - throw new StopInterpretation(sw.toString, pos) - } - + val sw = new StringWriter() + sw.write("An exception occurred while executing macro expansion\n") + sw.write(ex.getTargetException.getMessage) + sw.write("\n") + ex.getTargetException.printStackTrace(new PrintWriter(sw)) + sw.write("\n") + throw new StopInterpretation(sw.toString, pos) } } diff --git a/library/src-bootstrapped/dotty/internal/StringContextMacro.scala b/library/src-bootstrapped/dotty/internal/StringContextMacro.scala index d97f022f3b8c..333988e703d7 100644 --- a/library/src-bootstrapped/dotty/internal/StringContextMacro.scala +++ b/library/src-bootstrapped/dotty/internal/StringContextMacro.scala @@ -53,28 +53,25 @@ object StringContextMacro { def restoreReported() : Unit } - /** Retrieves a String from an Expr containing it - * - * @param expression the Expr containing the String - * @return the String contained in the given Expr - * quotes an error if the given Expr does not contain a String - */ - private def literalToString(expression : Expr[String]) given (ctx: QuoteContext) : String = expression match { - case Const(string : String) => string - case _ => QuoteError("Expected statically known literal", expression) - } - /** Retrieves the parts from a StringContext, given inside an Expr, and returns them as a list of Expr of String * * @param strCtxExpr the Expr containing the StringContext * @return a list of Expr containing Strings, each corresponding to one parts of the given StringContext * quotes an error if the given Expr does not correspond to a StringContext */ - def getPartsExprs(strCtxExpr : Expr[scala.StringContext]) given QuoteContext: List[Expr[String]] = { + def getPartsExprs(strCtxExpr : Expr[scala.StringContext]) given (qctx: QuoteContext): Option[(List[Expr[String]], List[String])] = { + def notStatic = { + qctx.error("Expected statically known String Context", strCtxExpr) + None + } + def splitParts(seq: Expr[Seq[String]]) = (seq, seq) match { + case (ExprSeq(p1), ConstSeq(p2)) => Some((p1.toList, p2.toList)) + case _ => notStatic + } strCtxExpr match { - case '{ StringContext(${ExprSeq(parts)}: _*) } => parts.toList - case '{ new StringContext(${ExprSeq(parts)}: _*) } => parts.toList - case _ => QuoteError("Expected statically known String Context", strCtxExpr) + case '{ StringContext($parts: _*) } => splitParts(parts) + case '{ new StringContext($parts: _*) } => splitParts(parts) + case _ => notStatic } } @@ -84,11 +81,14 @@ object StringContextMacro { * @return a list of Expr containing arguments * quotes an error if the given Expr does not contain a list of arguments */ - def getArgsExprs(argsExpr: Expr[Seq[Any]]) given (qctx: QuoteContext): List[Expr[Any]] = { + def getArgsExprs(argsExpr: Expr[Seq[Any]]) given (qctx: QuoteContext): Option[List[Expr[Any]]] = { import qctx.tasty._ argsExpr.unseal.underlyingArgument match { - case Typed(Repeated(args, _), _) => args.map(_.seal) - case tree => QuoteError("Expected statically known argument list", argsExpr) + case Typed(Repeated(args, _), _) => + Some(args.map(_.seal)) + case tree => + qctx.error("Expected statically known argument list", argsExpr) + None } } @@ -102,8 +102,14 @@ object StringContextMacro { import qctx.tasty._ val sourceFile = strCtxExpr.unseal.pos.sourceFile - val partsExpr = getPartsExprs(strCtxExpr) - val args = getArgsExprs(argsExpr) + val (partsExpr, parts) = getPartsExprs(strCtxExpr) match { + case Some(x) => x + case None => return '{""} + } + val args = getArgsExprs(argsExpr) match { + case Some(args) => args + case None => return '{""} + } val reporter = new Reporter{ private[this] var reported = false @@ -148,7 +154,7 @@ object StringContextMacro { } } - interpolate(partsExpr, args, argsExpr, reporter) + interpolate(parts, args, argsExpr, reporter) } /** Helper function for the interpolate function above @@ -158,7 +164,7 @@ object StringContextMacro { * @param reporter the reporter to return any error/warning when a problem is encountered * @return the Expr containing the formatted and interpolated String or an error/warning report if the parameters are not correct */ - def interpolate(partsExpr : List[Expr[String]], args : List[Expr[Any]], argsExpr: Expr[Seq[Any]], reporter : Reporter) given (qctx: QuoteContext) : Expr[String] = { + def interpolate(parts0 : List[String], args : List[Expr[Any]], argsExpr: Expr[Seq[Any]], reporter : Reporter) given (qctx: QuoteContext) : Expr[String] = { import qctx.tasty._ /** Checks if the number of arguments are the same as the number of formatting strings @@ -721,10 +727,10 @@ object StringContextMacro { val argument = args.size // check validity of formatting - checkSizes(partsExpr.size - 1, argument) + checkSizes(parts0.size - 1, argument) // add default format - val parts = addDefaultFormat(partsExpr.map(literalToString)) + val parts = addDefaultFormat(parts0) if (!parts.isEmpty && !reporter.hasReported()) { if (parts.size == 1 && args.size == 0 && parts.head.size != 0){ diff --git a/library/src/scala/quoted/QuoteError.scala b/library/src-non-bootstrapped/scala/quoted/QuoteError.scala similarity index 92% rename from library/src/scala/quoted/QuoteError.scala rename to library/src-non-bootstrapped/scala/quoted/QuoteError.scala index bd11a02caf1c..1091d3f44429 100644 --- a/library/src/scala/quoted/QuoteError.scala +++ b/library/src-non-bootstrapped/scala/quoted/QuoteError.scala @@ -3,8 +3,10 @@ package scala.quoted /** Throwing this error in the implementation of a macro * will result in a compilation error with the given message. */ +@deprecated("", "0.17") class QuoteError(message: String, val from: Option[Expr[_]]) extends Throwable(message) +@deprecated("", "0.17") object QuoteError { /** Throws a QuoteError with the given message */ def apply(message: => String): Nothing = throw new QuoteError(message, None) diff --git a/library/src/scala/quoted/QuoteContext.scala b/library/src/scala/quoted/QuoteContext.scala index 8e17ce9e1ab2..2b22cdd44de9 100644 --- a/library/src/scala/quoted/QuoteContext.scala +++ b/library/src/scala/quoted/QuoteContext.scala @@ -22,6 +22,30 @@ class QuoteContext(val tasty: scala.tasty.Reflection) { tpe.unseal.show(syntaxHighlight) } + /** Report an error */ + def error(msg: => String): Unit = { + import tasty._ + tasty.error(msg, rootPosition) given rootContext + } + + /** Report an error at the on the position of `expr` */ + def error(msg: => String, expr: Expr[_]): Unit = { + import tasty._ + tasty.error(msg, expr.unseal.pos) given rootContext + } + + /** Report a warning */ + def warning(msg: => String): Unit = { + import tasty._ + tasty.warning(msg, rootPosition) given rootContext + } + + /** Report a warning at the on the position of `expr` */ + def warning(msg: => String, expr: Expr[_]): Unit = { + import tasty._ + tasty.warning(msg, expr.unseal.pos) given rootContext + } + } object QuoteContext { diff --git a/tests/neg-macros/quote-error-2/Macro_1.scala b/tests/neg-macros/quote-error-2/Macro_1.scala index 08c98401fd6a..3348ce0dc262 100644 --- a/tests/neg-macros/quote-error-2/Macro_1.scala +++ b/tests/neg-macros/quote-error-2/Macro_1.scala @@ -5,8 +5,8 @@ object Macro_1 { def fooImpl(b: Boolean) given QuoteContext: Expr[Unit] = '{println(${msg(b)})} - def msg(b: Boolean) given QuoteContext: Expr[String] = + def msg(b: Boolean) given (qctx: QuoteContext): Expr[String] = if (b) '{"foo(true)"} - else QuoteError("foo cannot be called with false") + else { qctx.error("foo cannot be called with false"); '{ ??? } } } diff --git a/tests/neg-macros/quote-error/Macro_1.scala b/tests/neg-macros/quote-error/Macro_1.scala index f081c3656da3..f42b42789685 100644 --- a/tests/neg-macros/quote-error/Macro_1.scala +++ b/tests/neg-macros/quote-error/Macro_1.scala @@ -2,7 +2,7 @@ import quoted._ object Macro_1 { inline def foo(inline b: Boolean): Unit = ${fooImpl(b)} - def fooImpl(b: Boolean) given QuoteContext: Expr[Unit] = + def fooImpl(b: Boolean) given (qctx: QuoteContext): Expr[Unit] = if (b) '{println("foo(true)")} - else QuoteError("foo cannot be called with false") + else { qctx.error("foo cannot be called with false"); '{ ??? } } } diff --git a/tests/neg-with-compiler/i5941/macro_1.scala b/tests/neg-with-compiler/i5941/macro_1.scala index 1562fd0e08e6..d356955097e2 100644 --- a/tests/neg-with-compiler/i5941/macro_1.scala +++ b/tests/neg-with-compiler/i5941/macro_1.scala @@ -33,7 +33,8 @@ object Lens { apply($getter)(setter) } case _ => - QuoteError("Unsupported syntax. Example: `GenLens[Address](_.streetNumber)`") + qctx.error("Unsupported syntax. Example: `GenLens[Address](_.streetNumber)`") + '{???} } } } diff --git a/tests/run-macros/f-interpolation-1/FQuote_1.scala b/tests/run-macros/f-interpolation-1/FQuote_1.scala index f7c389934451..8bf6892eeaca 100644 --- a/tests/run-macros/f-interpolation-1/FQuote_1.scala +++ b/tests/run-macros/f-interpolation-1/FQuote_1.scala @@ -39,7 +39,8 @@ object FQuote { values.forall(isStringConstant) => values.collect { case Literal(Constant(value: String)) => value } case tree => - QuoteError(s"String literal expected, but ${tree.showExtractors} found") + qctx.error(s"String literal expected, but ${tree.showExtractors} found") + return '{???} } // [a0, ...]: Any* diff --git a/tests/run-macros/f-interpolator-neg/Macros_1.scala b/tests/run-macros/f-interpolator-neg/Macros_1.scala index 284c960920ec..87de7c505817 100644 --- a/tests/run-macros/f-interpolator-neg/Macros_1.scala +++ b/tests/run-macros/f-interpolator-neg/Macros_1.scala @@ -21,7 +21,7 @@ object Macro { } } - def fooErrorsImpl(parts : Seq[Expr[String]], args: Seq[Expr[Any]], argsExpr: Expr[Seq[Any]]) given QuoteContext= { + def fooErrorsImpl(parts0: Seq[Expr[String]], args: Seq[Expr[Any]], argsExpr: Expr[Seq[Any]]) given QuoteContext= { val errors = List.newBuilder[Expr[(Boolean, Int, Int, Int, String)]] // true if error, false if warning // 0 if part, 1 if arg, 2 if strCtx, 3 if args @@ -67,6 +67,7 @@ object Macro { reported = oldReported } } + val parts = parts0.map { case Const(s) => s } dotty.internal.StringContextMacro.interpolate(parts.toList, args.toList, argsExpr, reporter) // Discard result errors.result().toExprOfList } diff --git a/tests/run-macros/i5941/macro_1.scala b/tests/run-macros/i5941/macro_1.scala index 0df03c9534d1..ff220ab9278f 100644 --- a/tests/run-macros/i5941/macro_1.scala +++ b/tests/run-macros/i5941/macro_1.scala @@ -53,7 +53,8 @@ object Lens { apply($getter)(setter) } case _ => - QuoteError("Unsupported syntax. Example: `GenLens[Address](_.streetNumber)`") + qctx.error("Unsupported syntax. Example: `GenLens[Address](_.streetNumber)`") + '{???} } } } @@ -93,8 +94,10 @@ object Iso { // 1. S must be a case class // 2. A must be a tuple // 3. The parameters of S must match A - if (tpS.classSymbol.flatMap(cls => if (cls.flags.is(Flags.Case)) Some(true) else None).isEmpty) - QuoteError("Only support generation for case classes") + if (tpS.classSymbol.flatMap(cls => if (cls.flags.is(Flags.Case)) Some(true) else None).isEmpty) { + qctx.error("Only support generation for case classes") + return '{???} + } val cls = tpS.classSymbol.get @@ -103,14 +106,16 @@ object Iso { case Type.TypeRef(name, prefix) => Type.TermRef(prefix, name) } - if (cls.caseFields.size != 1) - QuoteError("Use GenIso.fields for case classes more than one parameter") + if (cls.caseFields.size != 1) { + qctx.error("Use GenIso.fields for case classes more than one parameter") + return '{???} + } val fieldTp = tpS.memberType(cls.caseFields.head) - if (!(fieldTp =:= tpA)) - QuoteError(s"The type of case class field $fieldTp does not match $tpA") - - '{ + if (!(fieldTp =:= tpA)) { + qctx.error(s"The type of case class field $fieldTp does not match $tpA") + '{???} + } else '{ // (p: S) => p._1 val to = (p: S) => ${ Select.unique(('p).unseal, "_1").seal.cast[A] } // (p: A) => S(p) @@ -134,8 +139,10 @@ object Iso { else if (tpS.classSymbol.flatMap(cls => if (cls.flags.is(Flags.Case)) Some(true) else None).nonEmpty) { val cls = tpS.classSymbol.get - if (cls.caseFields.size != 0) - QuoteError("Use GenIso.fields for case classes more than one parameter") + if (cls.caseFields.size != 0) { + qctx.error("Use GenIso.fields for case classes more than one parameter") + return '{???} + } val companion = tpS match { case Type.SymRef(sym, prefix) => Type.TermRef(prefix, sym.name) @@ -149,7 +156,8 @@ object Iso { } } else { - QuoteError("Only support generation for case classes or singleton types") + qctx.error("Only support generation for case classes or singleton types") + '{???} } } diff --git a/tests/run-macros/tasty-interpolation-1/Macro.scala b/tests/run-macros/tasty-interpolation-1/Macro.scala index 4d01652fc17d..884c63754a3e 100644 --- a/tests/run-macros/tasty-interpolation-1/Macro.scala +++ b/tests/run-macros/tasty-interpolation-1/Macro.scala @@ -31,18 +31,21 @@ object FooIntepolator extends MacroStringInterpolator[String] { // TODO put this class in the stdlib or separate project? abstract class MacroStringInterpolator[T] { - final def apply(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]]) given QuoteContext: Expr[T] = { + final def apply(strCtxExpr: Expr[StringContext], argsExpr: Expr[Seq[Any]]) given (qctx: QuoteContext): Expr[T] = { try interpolate(strCtxExpr, argsExpr) catch { case ex: NotStaticlyKnownError => // TODO use ex.expr to recover the position - QuoteError(ex.getMessage) + qctx.error(ex.getMessage) + '{???} case ex: StringContextError => // TODO use ex.idx to recover the position - QuoteError(ex.getMessage) + qctx.error(ex.getMessage) + '{???} case ex: ArgumentError => // TODO use ex.idx to recover the position - QuoteError(ex.getMessage) + qctx.error(ex.getMessage) + '{???} } } diff --git a/tests/run-macros/tasty-macro-const/quoted_1.scala b/tests/run-macros/tasty-macro-const/quoted_1.scala index 15b41ca0b5f5..4567c435a551 100644 --- a/tests/run-macros/tasty-macro-const/quoted_1.scala +++ b/tests/run-macros/tasty-macro-const/quoted_1.scala @@ -9,11 +9,15 @@ object Macros { val xTree: Term = x.unseal xTree match { case Inlined(_, _, Literal(Constant(n: Int))) => - if (n <= 0) - QuoteError("Parameter must be natural number") - xTree.seal.cast[Int] + if (n <= 0) { + qctx.error("Parameter must be natural number") + '{0} + } else { + xTree.seal.cast[Int] + } case _ => - QuoteError("Parameter must be a known constant") + qctx.error("Parameter must be a known constant") + '{0} } } diff --git a/tests/run-macros/xml-interpolation-1/XmlQuote_1.scala b/tests/run-macros/xml-interpolation-1/XmlQuote_1.scala index f1799175eaee..0b2bd62b5af1 100644 --- a/tests/run-macros/xml-interpolation-1/XmlQuote_1.scala +++ b/tests/run-macros/xml-interpolation-1/XmlQuote_1.scala @@ -16,9 +16,6 @@ object XmlQuote { given (qctx: QuoteContext): Expr[Xml] = { import qctx.tasty._ - def abort(msg: String): Nothing = - QuoteError(msg) - // for debugging purpose def pp(tree: Tree): Unit = { println(tree.showExtractors) @@ -52,7 +49,8 @@ object XmlQuote { values.forall(isStringConstant) => values.collect { case Literal(Constant(value: String)) => value } case tree => - abort(s"String literal expected, but ${tree.showExtractors} found") + qctx.error(s"String literal expected, but ${tree.showExtractors} found") + return '{ ??? } } // [a0, ...]: Any* diff --git a/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala b/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala index f04b4c090fdd..88604ba2e18b 100644 --- a/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala +++ b/tests/run-macros/xml-interpolation-2/XmlQuote_1.scala @@ -43,12 +43,17 @@ object XmlQuote { case Apply(fun, List(Typed(Repeated(values, _), _))) if isStringContextApply(fun) => values.iterator.map { case Literal(Constant(value: String)) => value - case _ => QuoteError("Expected statically known String") + case _ => + qctx.error("Expected statically known String") + return '{???} }.toList - case _ => QuoteError("Expected statically known StringContext") + case _ => + qctx.error("Expected statically known StringContext") + return '{???} } case _ => - QuoteError("Expected statically known SCOps") + qctx.error("Expected statically known SCOps") + return '{???} } // [a0, ...]: Any* diff --git a/tests/run-with-compiler/staged-tuples/StagedTuple.scala b/tests/run-with-compiler/staged-tuples/StagedTuple.scala index 4085e745cfa6..1e0431d8b3b5 100644 --- a/tests/run-with-compiler/staged-tuples/StagedTuple.scala +++ b/tests/run-with-compiler/staged-tuples/StagedTuple.scala @@ -131,7 +131,9 @@ object StagedTuple { if (!specialize) '{dynamicApply($tup, $n)} else { def fallbackApply(): Expr[Elem[Tup, N]] = nValue match { - case Some(n) => QuoteError("index out of bounds: " + n, tup) + case Some(n) => + qctx.error("index out of bounds: " + n, tup) + '{ throw new IndexOutOfBoundsException(${n.toString.toExpr}) } case None => '{dynamicApply($tup, $n)} } val res = size match {