Skip to content

Commit b5e5154

Browse files
committed
Handle macro annotation suspends and crashes
1 parent c3097af commit b5e5154

File tree

8 files changed

+88
-14
lines changed

8 files changed

+88
-14
lines changed

compiler/src/dotty/tools/dotc/quoted/Interpreter.scala

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -268,19 +268,6 @@ class Interpreter(pos: SrcPos, classLoader: ClassLoader)(using Context):
268268
}
269269
}
270270

271-
private object MissingClassDefinedInCurrentRun {
272-
def unapply(targetException: Throwable)(using Context): Option[Symbol] = {
273-
targetException match
274-
case _: NoClassDefFoundError | _: ClassNotFoundException =>
275-
val className = targetException.getMessage
276-
if className eq null then None
277-
else
278-
val sym = staticRef(className.toTypeName).symbol
279-
if (sym.isDefinedInCurrentRun) Some(sym) else None
280-
case _ => None
281-
}
282-
}
283-
284271
/** List of classes of the parameters of the signature of `sym` */
285272
private def paramsSig(sym: Symbol): List[Class[?]] = {
286273
def paramClass(param: Type): Class[?] = {
@@ -364,3 +351,16 @@ object Interpreter:
364351
}
365352
}
366353
end Call
354+
355+
object MissingClassDefinedInCurrentRun {
356+
def unapply(targetException: Throwable)(using Context): Option[Symbol] = {
357+
targetException match
358+
case _: NoClassDefFoundError | _: ClassNotFoundException =>
359+
val className = targetException.getMessage
360+
if className eq null then None
361+
else
362+
val sym = staticRef(className.toTypeName).symbol
363+
if (sym.isDefinedInCurrentRun) Some(sym) else None
364+
case _ => None
365+
}
366+
}

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ import dotty.tools.dotc.core.DenotTransformers.DenotTransformer
1313
import dotty.tools.dotc.core.Flags.*
1414
import dotty.tools.dotc.core.MacroClassLoader
1515
import dotty.tools.dotc.core.Symbols.*
16+
import dotty.tools.dotc.core.Types._
1617
import dotty.tools.dotc.quoted.*
1718
import dotty.tools.dotc.util.SrcPos
1819
import scala.quoted.runtime.impl.{QuotesImpl, SpliceScope}
1920

2021
import scala.quoted.Quotes
22+
import scala.util.control.NonFatal
23+
24+
import java.lang.reflect.InvocationTargetException
2125

2226
class MacroAnnotations(thisPhase: DenotTransformer):
2327
import tpd.*
@@ -53,7 +57,33 @@ class MacroAnnotations(thisPhase: DenotTransformer):
5357
debug.println(i"Expanding macro annotation: ${annot}")
5458

5559
// Interpret call to `new myAnnot(..).transform(using <Quotes>)(<tree>)`
56-
val transformedTrees = callMacro(macroInterpreter, tree, annot)
60+
val transformedTrees =
61+
try callMacro(macroInterpreter, tree, annot)
62+
catch
63+
// TODO: Replace this case when scala.annaotaion.MacroAnnotation is no longer experimental and reflectiveSelectable is not used
64+
// Replace this case with the nested cases.
65+
case ex0: InvocationTargetException =>
66+
ex0.getCause match
67+
case ex: scala.quoted.runtime.StopMacroExpansion =>
68+
if !ctx.reporter.hasErrors then
69+
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.", annot.tree)
70+
List(tree)
71+
case Interpreter.MissingClassDefinedInCurrentRun(sym) if ctx.compilationUnit.isSuspendable =>
72+
if (ctx.settings.XprintSuspension.value)
73+
report.echo(i"suspension triggered by a dependency on $sym", annot.tree)
74+
ctx.compilationUnit.suspend() // this throws a SuspendException
75+
case NonFatal(ex) =>
76+
val stack0 = ex.getStackTrace.takeWhile(_.getClassName != "dotty.tools.dotc.transform.MacroAnnotations")
77+
val stack = stack0.take(1 + stack0.lastIndexWhere(_.getMethodName == "transform"))
78+
val msg =
79+
em"""Failed to evaluate macro.
80+
| Caused by ${ex.getClass}: ${if (ex.getMessage == null) "" else ex.getMessage}
81+
| ${stack.mkString("\n ")}
82+
|"""
83+
report.error(msg, annot.tree)
84+
List(tree)
85+
case _ =>
86+
throw ex0
5787
transformedTrees.span(_.symbol != tree.symbol) match
5888
case (prefixed, newTree :: suffixed) =>
5989
allTrees ++= prefixed

tests/neg-macros/annot-crash.check

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
-- Error: tests/neg-macros/annot-crash/Test_2.scala:1:0 ----------------------------------------------------------------
3+
1 |@crash // error
4+
|^^^^^^
5+
|Failed to evaluate macro.
6+
| Caused by class scala.NotImplementedError: an implementation is missing
7+
| scala.Predef$.$qmark$qmark$qmark(Predef.scala:344)
8+
| crash.transform(Macro_1.scala:7)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.annotation.{experimental, MacroAnnotation}
2+
import scala.quoted._
3+
4+
@experimental
5+
class crash extends MacroAnnotation {
6+
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
7+
???
8+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@crash // error
2+
def test = ()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- [E129] Potential Issue Warning: tests/neg-macros/annot-suspend-cycle/Macro.scala:7:4 --------------------------------
2+
7 | new Foo
3+
| ^^^^^^^
4+
| A pure expression does nothing in statement position; you may be omitting necessary parentheses
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
Cyclic macro dependencies in tests/neg-macros/annot-suspend-cycle/Test.scala.
8+
Compilation stopped since no further progress can be made.
9+
10+
To fix this, place macros in one set of files and their callers in another.
11+
12+
Compiling with -Xprint-suspension gives more information.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.annotation.{experimental, MacroAnnotation}
2+
import scala.quoted._
3+
4+
@experimental
5+
class cycle extends MacroAnnotation {
6+
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
7+
new Foo
8+
List(tree)
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// nopos-error
2+
class Foo
3+
4+
@cycle
5+
def test = ()

0 commit comments

Comments
 (0)