Skip to content

Move macro error reporting to QuoteContext #6820

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 7 additions & 23 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
}

Expand Down
54 changes: 30 additions & 24 deletions library/src-bootstrapped/dotty/internal/StringContextMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand All @@ -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
}
}

Expand All @@ -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
Expand Down Expand Up @@ -148,7 +154,7 @@ object StringContextMacro {
}
}

interpolate(partsExpr, args, argsExpr, reporter)
interpolate(parts, args, argsExpr, reporter)
}

/** Helper function for the interpolate function above
Expand All @@ -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
Expand Down Expand Up @@ -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){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions library/src/scala/quoted/QuoteContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-macros/quote-error-2/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"); '{ ??? } }

}
4 changes: 2 additions & 2 deletions tests/neg-macros/quote-error/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"); '{ ??? } }
}
3 changes: 2 additions & 1 deletion tests/neg-with-compiler/i5941/macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)`")
'{???}
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion tests/run-macros/f-interpolation-1/FQuote_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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*
Expand Down
3 changes: 2 additions & 1 deletion tests/run-macros/f-interpolator-neg/Macros_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
32 changes: 20 additions & 12 deletions tests/run-macros/i5941/macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)`")
'{???}
}
}
}
Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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")
'{???}
}
}

Expand Down
11 changes: 7 additions & 4 deletions tests/run-macros/tasty-interpolation-1/Macro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
'{???}
}
}

Expand Down
12 changes: 8 additions & 4 deletions tests/run-macros/tasty-macro-const/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
}

Expand Down
6 changes: 2 additions & 4 deletions tests/run-macros/xml-interpolation-1/XmlQuote_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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*
Expand Down
Loading