Skip to content

Commit 53dd32e

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 75edcf8 commit 53dd32e

File tree

60 files changed

+198
-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.

60 files changed

+198
-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])
@@ -2750,14 +2763,23 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
27502763
dotc.report.error(msg, pos)
27512764

27522765
def throwError(msg: String): Nothing =
2766+
errorAndAbort(msg)
2767+
2768+
def throwError(msg: String, expr: Expr[Any]): Nothing =
2769+
errorAndAbort(msg, expr)
2770+
2771+
def throwError(msg: String, pos: Position): Nothing =
2772+
errorAndAbort(msg, pos)
2773+
2774+
def errorAndAbort(msg: String): Nothing =
27532775
error(msg)
27542776
throw new scala.quoted.runtime.StopMacroExpansion
27552777

2756-
def throwError(msg: String, expr: Expr[Any]): Nothing =
2778+
def errorAndAbort(msg: String, expr: Expr[Any]): Nothing =
27572779
error(msg, expr)
27582780
throw new scala.quoted.runtime.StopMacroExpansion
27592781

2760-
def throwError(msg: String, pos: Position): Nothing =
2782+
def errorAndAbort(msg: String, pos: Position): Nothing =
27612783
error(msg, pos)
27622784
throw new scala.quoted.runtime.StopMacroExpansion
27632785

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

@@ -4169,12 +4171,30 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
41694171
def error(msg: String, pos: Position): Unit
41704172

41714173
/** Report an error at the position of the macro expansion and throw a StopMacroExpansion */
4174+
@experimental
4175+
def errorAndAbort(msg: String): Nothing
4176+
4177+
/** Report an error at the position of `expr` and throw a StopMacroExpansion */
4178+
@experimental
4179+
def errorAndAbort(msg: String, expr: Expr[Any]): Nothing
4180+
4181+
/** Report an error message at the given position and throw a StopMacroExpansion */
4182+
@experimental
4183+
def errorAndAbort(msg: String, pos: Position): Nothing
4184+
4185+
/** Report an error at the position of the macro expansion and throw a StopMacroExpansion */
4186+
// TODO: deprecate in 3.1.0 and remove @experimental from errorAndAbort
4187+
// @deprecated("Use errorAndAbort", "3.1.0")
41724188
def throwError(msg: String): Nothing
41734189

4174-
/** Report an error at the position of `expr` */
4190+
/** Report an error at the position of `expr` and throw a StopMacroExpansion */
4191+
// TODO: deprecate in 3.1.0 and remove @experimental from errorAndAbort
4192+
// @deprecated("Use errorAndAbort", "3.1.0")
41754193
def throwError(msg: String, expr: Expr[Any]): Nothing
41764194

41774195
/** Report an error message at the given position and throw a StopMacroExpansion */
4196+
// TODO: deprecate in 3.1.0 and remove @experimental from errorAndAbort
4197+
// @deprecated("Use errorAndAbort", "3.1.0")
41784198
def throwError(msg: String, pos: Position): Nothing
41794199

41804200
/** 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

project/MiMaFilters.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,24 @@ import com.typesafe.tools.mima.core.ProblemFilters._
55
object MiMaFilters {
66
val Library: Seq[ProblemFilter] = Seq(
77
// New APIs marked @experimental in 3.0.1
8+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes.value"),
9+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes.valueOrAbort"),
10+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes.valueOrError"),
11+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#reportModule.errorAndAbort"),
812
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"),
913
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMembers"),
1014
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.methodMember"),
1115
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.methodMembers"),
1216
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeMember"),
1317
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.typeMembers"),
1418
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TermParamClauseMethods.isErased"),
19+
exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#TermParamClauseMethods.isErased"),
20+
exclude[MissingClassProblem]("scala.annotation.experimental"),
1521
exclude[MissingClassProblem]("scala.annotation.experimental"),
1622
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
23+
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
24+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes.valueOrAbort"),
25+
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#reportModule.errorAndAbort"),
1726
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"),
1827
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMembers"),
1928
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.methodMember"),

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+
|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.
6+
| This location contains code that was inlined from quoted_1.scala:3

0 commit comments

Comments
 (0)