Skip to content

Commit 321a614

Browse files
authored
Error instead of StaleSymbol crash for certain cyclic macro dependencies (#19549)
If a suspended macro refers back to a symbol in a previously compiled package object, report this as an error instead of crashing with a StaleSymbol exception. There's unfortunately not a lot of info available at the point where the error is raised, so the error message is a bit vague. But it's better than crashing. Fixes #19351
2 parents aac902e + 9cb9d4b commit 321a614

File tree

9 files changed

+68
-3
lines changed

9 files changed

+68
-3
lines changed

Diff for: compiler/src/dotty/tools/dotc/Driver.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,7 @@ class Driver {
5454
if (ctx.settings.XprintSuspension.value)
5555
report.echo(i"compiling suspended $suspendedUnits%, %")
5656
val run1 = compiler.newRun
57-
for unit <- suspendedUnits do unit.suspended = false
58-
run1.compileUnits(suspendedUnits)
57+
run1.compileSuspendedUnits(suspendedUnits)
5958
finish(compiler, run1)(using MacroClassLoader.init(ctx.fresh))
6059

6160
protected def initCtx: Context = (new ContextBase).initialCtx

Diff for: compiler/src/dotty/tools/dotc/Run.scala

+11
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,17 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
358358
compiling = false
359359
}
360360

361+
private var myCompilingSuspended: Boolean = false
362+
363+
/** Is this run started via a compilingSuspended? */
364+
def isCompilingSuspended: Boolean = myCompilingSuspended
365+
366+
/** Compile units `us` which were suspended in a previous run */
367+
def compileSuspendedUnits(us: List[CompilationUnit]): Unit =
368+
myCompilingSuspended = true
369+
for unit <- us do unit.suspended = false
370+
compileUnits(us)
371+
361372
/** Enter top-level definitions of classes and objects contained in source file `file`.
362373
* The newly added symbols replace any previously entered symbols.
363374
* If `typeCheck = true`, also run typer on the compilation unit, and set

Diff for: compiler/src/dotty/tools/dotc/core/Denotations.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,10 @@ object Denotations {
954954
}
955955

956956
def staleSymbolError(using Context): Nothing =
957-
throw new StaleSymbol(staleSymbolMsg)
957+
if symbol.isPackageObject && ctx.run != null && ctx.run.nn.isCompilingSuspended then
958+
throw TypeError(em"Cyclic macro dependency; macro refers to a toplevel symbol in ${symbol.source} from which the macro is called")
959+
else
960+
throw new StaleSymbol(staleSymbolMsg)
958961

959962
def staleSymbolMsg(using Context): String = {
960963
def ownerMsg = this match {

Diff for: tests/neg/i19351.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg/i19351/A.scala:3:35 --------------------------------------------------------------------------------
2+
3 | inline def myMacro(): x.type = ${myMacroExpr} // error
3+
| ^
4+
|Cyclic macro dependency; macro refers to a toplevel symbol in tests/neg/i19351/A.scala from which the macro is called

Diff for: tests/neg/i19351/A.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//object A:
2+
val x: Int = 1
3+
inline def myMacro(): x.type = ${myMacroExpr} // error
4+
def test = myMacro()
5+

Diff for: tests/neg/i19351/B.scala

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import scala.quoted.*
2+
//import A.*
3+
def myMacroExpr(using Quotes): Expr[x.type] = '{???}

Diff for: tests/neg/i19351a.check

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-- Error: tests/neg/i19351a/Test.scala:8:34 ----------------------------------------------------------------------------
2+
8 |inline def not(b: Bool): Bool = ${notMacro('b)} // error // error
3+
| ^
4+
|Cyclic macro dependency; macro refers to a toplevel symbol in tests/neg/i19351a/Test.scala from which the macro is called
5+
-- [E046] Cyclic Error: tests/neg/i19351a/Test.scala:8:46 --------------------------------------------------------------
6+
8 |inline def not(b: Bool): Bool = ${notMacro('b)} // error // error
7+
| ^
8+
| Cyclic reference involving method $anonfun
9+
|
10+
| Run with -explain-cyclic for more details.
11+
|
12+
| longer explanation available when compiling with `-explain`

Diff for: tests/neg/i19351a/Macro.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Macro class
2+
package test
3+
4+
import scala.quoted.*
5+
6+
def notMacro(bool: Expr[Bool])(using Quotes): Expr[Bool] =
7+
'{$bool(False, True)}
8+
9+
def showMacro(bool: Expr[Bool])(using Quotes): Expr[String] =
10+
'{$bool("TRUE", "FALSE")}
11+
12+
def foldMacro[T: Type](bool: Expr[Bool], t: Expr[T], f: Expr[T])(using Quotes): Expr[T] =
13+
'{$bool($t, $f)}

Diff for: tests/neg/i19351a/Test.scala

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//Test class
2+
package test
3+
4+
type Bool = [R] => (R, R) => R
5+
val True: Bool = [R] => (t: R, _: R) => t
6+
val False: Bool = [R] => (_: R, f: R) => f
7+
8+
inline def not(b: Bool): Bool = ${notMacro('b)} // error // error
9+
inline def show(b: Bool): String = ${showMacro('b)}
10+
//inline def not(b: Bool): Bool = ${foldMacro('b, 'False, 'True)}
11+
//inline def show(b: Bool): String = ${foldMacro('b, '{"TRUE"}, '{"FALSE"})}
12+
13+
14+
@main def testing =
15+
println(show(not(True)))

0 commit comments

Comments
 (0)