Skip to content

Commit 55de88a

Browse files
committed
Add Expr.valueOrAbort and reflect.report.errorAndAbort
Provides a homogeneous and unambiguous concept for stopping the expansion of a macro. The advantages of this naming are * Consistent name suffixes `xyzAbort` * `report.e` will show auto-completion for all variants of `error` and `errorAndAbort` * `Abort` cannot be confused with other kinds of error handling in this API
1 parent 83e17f1 commit 55de88a

File tree

59 files changed

+189
-127
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+189
-127
lines changed

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ object Splicer {
6363
catch {
6464
case ex: CompilationUnit.SuspendException =>
6565
throw ex
66-
case ex: scala.quoted.runtime.StopMacroExpansion if ctx.reporter.hasErrors =>
67-
// errors have been emitted
66+
case ex: scala.quoted.runtime.StopMacroExpansion =>
67+
if !ctx.reporter.hasErrors then
68+
report.error("Macro expansion was aborted by the macro without any errors reported. Macros should issue errors to end-users to facilitate debugging when aborting a macro expansion.", splicePos)
69+
// errors have been emitted
6870
EmptyTree
6971
case ex: StopInterpretation =>
7072
report.error(ex.msg, ex.pos)
@@ -546,4 +548,3 @@ object Splicer {
546548
}
547549
}
548550
}
549-

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
4545
def matches(that: scala.quoted.Expr[Any]): Boolean =
4646
treeMatch(reflect.asTerm(self), reflect.asTerm(that)).nonEmpty
4747

48+
override def value(using fromExpr: FromExpr[T]): Option[T] =
49+
fromExpr.unapply(self)(using QuotesImpl.this)
50+
51+
override def valueOrError(using FromExpr[T]): T = self.valueOrAbort
52+
53+
def valueOrAbort(using fromExpr: FromExpr[T]): T =
54+
def reportError =
55+
val tree = reflect.asTerm(self)
56+
val code = reflect.Printer.TreeCode.show(tree)
57+
val msg = s"Expected a known value. \n\nThe value of: $code\ncould not be extracted using $fromExpr"
58+
reflect.report.throwError(msg, self)
59+
fromExpr.unapply(self)(using QuotesImpl.this).getOrElse(reportError)
60+
4861
end extension
4962

5063
extension (self: scala.quoted.Expr[Any])
@@ -2744,14 +2757,23 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
27442757
dotc.report.error(msg, pos)
27452758

27462759
def throwError(msg: String): Nothing =
2760+
errorAndAbort(msg)
2761+
2762+
def throwError(msg: String, expr: Expr[Any]): Nothing =
2763+
errorAndAbort(msg, expr)
2764+
2765+
def throwError(msg: String, pos: Position): Nothing =
2766+
errorAndAbort(msg, pos)
2767+
2768+
def errorAndAbort(msg: String): Nothing =
27472769
error(msg)
27482770
throw new scala.quoted.runtime.StopMacroExpansion
27492771

2750-
def throwError(msg: String, expr: Expr[Any]): Nothing =
2772+
def errorAndAbort(msg: String, expr: Expr[Any]): Nothing =
27512773
error(msg, expr)
27522774
throw new scala.quoted.runtime.StopMacroExpansion
27532775

2754-
def throwError(msg: String, pos: Position): Nothing =
2776+
def errorAndAbort(msg: String, pos: Position): Nothing =
27552777
error(msg, pos)
27562778
throw new scala.quoted.runtime.StopMacroExpansion
27572779

docs/docs/reference/metaprogramming/macros.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ aspect is also important for macro expansion.
460460

461461
To get values out of expressions containing constants `Expr` provides the method
462462
`value` (or `valueOrError`). This will convert the `Expr[T]` into a `Some[T]` (or `T`) when the
463-
expression contains value. Otherwise it will retrun `None` (or emit an error).
463+
expression contains value. Otherwise it will return `None` (or emit an error).
464464
To avoid having incidental val bindings generated by the inlining of the `def`
465465
it is recommended to use an inline parameter. To illustrate this, consider an
466466
implementation of the `power` function that makes use of a statically known exponent:

library/src/scala/quoted/Quotes.scala

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,24 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
4545
* Returns `None` if the expression does not represent a value or possibly contains side effects.
4646
* Otherwise returns the `Some` of the value.
4747
*/
48-
def value(using FromExpr[T]): Option[T] =
49-
given Quotes = Quotes.this
50-
summon[FromExpr[T]].unapply(self)
48+
def value(using FromExpr[T]): Option[T]
5149

5250
/** Return the value of this expression.
5351
*
5452
* Emits an error and throws if the expression does not represent a value or possibly contains side effects.
5553
* Otherwise returns the value.
5654
*/
57-
def valueOrError(using FromExpr[T]): T =
58-
val fromExpr = summon[FromExpr[T]]
59-
def reportError =
60-
val msg = s"Expected a known value. \n\nThe value of: ${self.show}\ncould not be extracted using $fromExpr"
61-
reflect.report.throwError(msg, self)
62-
given Quotes = Quotes.this
63-
fromExpr.unapply(self).getOrElse(reportError)
55+
// TODO: deprecate in 3.1.0 and remove @experimental from valueOrAbort
56+
// @deprecated("Use valueOrThrow", "3.1.0")
57+
def valueOrError(using FromExpr[T]): T
58+
59+
/** Return the value of this expression.
60+
*
61+
* Emits an error and aborts if the expression does not represent a value or possibly contains side effects.
62+
* Otherwise returns the value.
63+
*/
64+
@experimental
65+
def valueOrAbort(using FromExpr[T]): T
6466

6567
end extension
6668

@@ -4133,12 +4135,30 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
41334135
def error(msg: String, pos: Position): Unit
41344136

41354137
/** Report an error at the position of the macro expansion and throw a StopMacroExpansion */
4138+
@experimental
4139+
def errorAndAbort(msg: String): Nothing
4140+
4141+
/** Report an error at the position of `expr` and throw a StopMacroExpansion */
4142+
@experimental
4143+
def errorAndAbort(msg: String, expr: Expr[Any]): Nothing
4144+
4145+
/** Report an error message at the given position and throw a StopMacroExpansion */
4146+
@experimental
4147+
def errorAndAbort(msg: String, pos: Position): Nothing
4148+
4149+
/** Report an error at the position of the macro expansion and throw a StopMacroExpansion */
4150+
// TODO: deprecate in 3.1.0 and remove @experimental from errorAndAbort
4151+
// @deprecated("Use errorAndAbort", "3.1.0")
41364152
def throwError(msg: String): Nothing
41374153

4138-
/** Report an error at the position of `expr` */
4154+
/** Report an error at the position of `expr` and throw a StopMacroExpansion */
4155+
// TODO: deprecate in 3.1.0 and remove @experimental from errorAndAbort
4156+
// @deprecated("Use errorAndAbort", "3.1.0")
41394157
def throwError(msg: String, expr: Expr[Any]): Nothing
41404158

41414159
/** Report an error message at the given position and throw a StopMacroExpansion */
4160+
// TODO: deprecate in 3.1.0 and remove @experimental from errorAndAbort
4161+
// @deprecated("Use errorAndAbort", "3.1.0")
41424162
def throwError(msg: String, pos: Position): Nothing
41434163

41444164
/** Report a warning at the position of the macro expansion */
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
package scala.quoted.runtime
22

3-
/** Throwable used to stop the expansion of a macro after an error was reported */
4-
class StopMacroExpansion extends Throwable
3+
/** Throwable used to abort the expansion of a macro after an error was reported */
4+
class StopMacroExpansion extends Throwable:
5+
6+
// Do not fill the stacktrace for performance.
7+
// We know that the stacktrace will be ignored
8+
// and only the reported error message will be used.
9+
override def fillInStackTrace(): Throwable = this

tests/bench/power-macro/PowerMacro.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ object PowerMacro {
55
inline def power(inline n: Long, x: Double) = ${ powerCode('n, 'x) }
66

77
def powerCode(n: Expr[Long], x: Expr[Double])(using Quotes): Expr[Double] =
8-
powerCode(n.valueOrError, x)
8+
powerCode(n.valueOrAbort, x)
99

1010
def powerCode(n: Long, x: Expr[Double])(using Quotes): Expr[Double] =
1111
if (n == 0) '{1.0}

tests/neg-macros/i9014/Macros_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import scala.quoted.*
22
trait Bar
33
inline given Bar = ${ impl }
4-
def impl(using Quotes): Expr[Bar] = quotes.reflect.report.throwError("Failed to expand!")
4+
def impl(using Quotes): Expr[Bar] = quotes.reflect.report.errorAndAbort("Failed to expand!")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import scala.quoted._
22
trait Bar
33
transparent inline given Bar = ${ impl }
4-
def impl(using Quotes): Expr[Bar] = quotes.reflect.report.throwError("Failed to expand!")
4+
def impl(using Quotes): Expr[Bar] = quotes.reflect.report.errorAndAbort("Failed to expand!")

tests/neg-macros/ill-abort.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
-- Error: tests/neg-macros/ill-abort/quoted_2.scala:1:15 ---------------------------------------------------------------
3+
1 |def test = fail() // error
4+
| ^^^^^^
5+
| A StopMacroExpansion was thrown without any errors reported while expanding a macro. This macro has a bug.
6+
| This location contains code that was inlined from quoted_1.scala:3
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import scala.quoted.*
2+
3+
inline def fail(): Unit = ${ impl }
4+
5+
private def impl(using Quotes) : Expr[Unit] =
6+
// should never be done without reporting error before (see docs)
7+
throw new scala.quoted.runtime.StopMacroExpansion

0 commit comments

Comments
 (0)