scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.85k stars 1.06k forks source link

Enabling `-experimental` introduces compiler crash in transparent inline macro #21802

Open WojciechMazur opened 3 days ago

WojciechMazur commented 3 days ago

Based on OpenCB failure in 2 projects:

Compiler version

Last good release: 3.5.0-RC1-bin-20240515-177b489-NIGHTLY First bad release: 3.5.0-RC1-bin-20240516-c608177-NIGHTLY Bisect points to c8764bac0f555b2d3c9f42403d1ae216960d28ad

Minimized code

//> using options -experimental

class ProbeFailedException(cause: Exception) extends Exception(cause)
trait Probing: 
  self: Metrics =>
    val probeFailureCounter: MetricsGroup[Counter] = 
      counters("ustats_probe_failures_count").labelled

trait Counter
class Metrics:
  class counters(name: String):
    transparent inline final def labelled: MetricsGroup[Counter] = MetricsGroup.refine[Counter]
class MetricsGroup[A]
object MetricsGroup:
  import scala.quoted.*

  transparent inline final def refine[A]: MetricsGroup[A] =
    ${ refineImpl[A] }

  private def refineImpl[A](using qctx: Quotes, tpe: Type[A]): Expr[MetricsGroup[A]] =
    import qctx.reflect.*

    val mt = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[A])
    val tpe = Refinement(TypeRepr.of[MetricsGroup[A]], "apply", mt).asType
    tpe match
      case '[tpe] =>
        '{ MetricsGroup[A]().asInstanceOf[MetricsGroup[A] & tpe] }

Output

unhandled exception while running posttyper on /Users/wmazur/projects/sandbox/test.scala

  An unhandled exception was thrown in the compiler.
  Please file a crash report here:
  https://github.com/scala/scala3/issues/new/choose
  For non-enriched exceptions, compile with -Xno-enrich-error-messages.

     while compiling: /Users/wmazur/projects/sandbox/test.scala
        during phase: posttyper
                mode: Mode(ImplicitsEnabled)
     library version: version 2.13.12
    compiler version: version 3.5.0-RC1-bin-SNAPSHOT-git-c8764ba
            settings: -classpath /Users/wmazur/.ivy2/local/org.scala-lang/scala3-library_3/3.5.0-RC1-bin-SNAPSHOT/jars/scala3-library_3.jar:/Users/wmazur/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.12/scala-library-2.13.12.jar -d /Users/wmazur/projects/sandbox/.scala-build/sandbox_265807102e-9acd4df6a7/classes/main -experimental true -java-output-version 17 -sourceroot /Users/wmazur/projects/sandbox

  Exception while compiling /Users/wmazur/projects/sandbox/test.scala, /Users/wmazur/projects/sandbox/test.macros.scala

  An unhandled exception was thrown in the compiler.
  Please file a crash report here:
  https://github.com/scala/scala3/issues/new/choose
  For non-enriched exceptions, compile with -Xno-enrich-error-messages.

     while compiling: <no file>
        during phase: parser
                mode: Mode()
     library version: version 2.13.12
    compiler version: version 3.5.0-RC1-bin-SNAPSHOT-git-c8764ba
            settings: -classpath /Users/wmazur/.ivy2/local/org.scala-lang/scala3-library_3/3.5.0-RC1-bin-SNAPSHOT/jars/scala3-library_3.jar:/Users/wmazur/Library/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.12/scala-library-2.13.12.jar -d /Users/wmazur/projects/sandbox/.scala-build/sandbox_265807102e-9acd4df6a7/classes/main -experimental true -java-output-version 17 -sourceroot /Users/wmazur/projects/sandbox

Exception in thread "main" dotty.tools.dotc.core.Denotations$StaleSymbolException: stale symbol; module class Metrics$#4138 in module class <empty>, defined in Period(2.1-12), is referred to in run Period(3.9)
        at dotty.tools.dotc.core.Denotations$SingleDenotation.staleSymbolError(Denotations.scala:961)
        at dotty.tools.dotc.core.Denotations$SingleDenotation.bringForward(Denotations.scala:759)
        at dotty.tools.dotc.core.Denotations$SingleDenotation.toNewRun$1(Denotations.scala:806)
        at dotty.tools.dotc.core.Denotations$SingleDenotation.current(Denotations.scala:877)
        at dotty.tools.dotc.core.Symbols$Symbol.recomputeDenot(Symbols.scala:124)
        at dotty.tools.dotc.core.Symbols$Symbol.computeDenot(Symbols.scala:118)
        at dotty.tools.dotc.core.Symbols$Symbol.denot(Symbols.scala:109)
        at dotty.tools.dotc.core.Symbols$Symbol.source(Symbols.scala:321)
        at dotty.tools.dotc.core.Symbols$Symbol.source(Symbols.scala:329)
        at dotty.tools.dotc.typer.Checking$.isNonExperimentalTopLevelDefinition$1(Checking.scala:810)
        at dotty.tools.dotc.typer.Checking$.nonExperimentalTopLevelDefs$1$$anonfun$1(Checking.scala:818)
        at scala.collection.Iterator$$anon$10.nextCur(Iterator.scala:594)
        at scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:608)
        at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:576)
        at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:574)
        at scala.collection.AbstractIterator.foreach(Iterator.scala:1300)
        at dotty.tools.dotc.typer.Checking$.markTopLevelDefsAsExperimental$1(Checking.scala:838)
        at dotty.tools.dotc.typer.Checking$.checkAndAdaptExperimentalImports(Checking.scala:846)
        at dotty.tools.dotc.transform.PostTyper$PostTyperTransformer.transformStats(PostTyper.scala:569)
        at dotty.tools.dotc.ast.tpd$TreeMapWithPreciseStatContexts.transformStats(tpd.scala:1242)
        at dotty.tools.dotc.ast.Trees$Instance$TreeMap.transform(Trees.scala:1613)
        at dotty.tools.dotc.transform.MacroTransform$Transformer.transform(MacroTransform.scala:42)
        at dotty.tools.dotc.transform.PostTyper$PostTyperTransformer.transform(PostTyper.scala:560)
        at dotty.tools.dotc.transform.MacroTransform.run(MacroTransform.scala:20)
        at dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:380)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
        at scala.collection.immutable.List.foreach(List.scala:333)
        at dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:373)
        at dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:343)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:15)
        at scala.runtime.function.JProcedure1.apply(JProcedure1.java:10)
        at scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1323)
        at dotty.tools.dotc.Run.runPhases$1(Run.scala:336)
        at dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:384)
        at dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:396)
        at dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:69)
        at dotty.tools.dotc.Run.compileUnits(Run.scala:396)
        at dotty.tools.dotc.Run.compileUnits(Run.scala:288)
        at dotty.tools.dotc.Run.compileSuspendedUnits(Run.scala:410)
        at dotty.tools.dotc.Driver.finish(Driver.scala:63)
        at dotty.tools.dotc.Driver.doCompile(Driver.scala:38)
        at dotty.tools.dotc.Driver.process(Driver.scala:201)
        at dotty.tools.dotc.Driver.process(Driver.scala:169)
        at dotty.tools.dotc.Driver.process(Driver.scala:181)
        at dotty.tools.dotc.Driver.main(Driver.scala:211)
        at dotty.tools.dotc.Main.main(Main.scala)

Expectation

Should compile

Gedochao commented 3 days ago

cc @smarter @jchyb

smarter commented 3 days ago

https://github.com/scala/scala3/pull/20409 was a stopgap, as noted by Martin in the PR we should really only traverse the symbols of trees we're currently compiling which are therefore guaranteed to not be stale (it seemsisDefinedInCurrentRun is not enough when macros are involved due to the SuspendException mechanism), if we want another stopgap, we can move the isExperimental check earlier, since unlike the sym.source it doesn't force the companion:

diff --git compiler/src/dotty/tools/dotc/typer/Checking.scala compiler/src/dotty/tools/dotc/typer/Checking.scala
index 700bd483ff..eae34c2304 100644
--- compiler/src/dotty/tools/dotc/typer/Checking.scala
+++ compiler/src/dotty/tools/dotc/typer/Checking.scala
@@ -807,10 +807,10 @@ object Checking {
     def nonExperimentalTopLevelDefs(pack: Symbol): Iterator[Symbol] =
       def isNonExperimentalTopLevelDefinition(sym: Symbol) =
         sym.isDefinedInCurrentRun
+        && !sym.isExperimental
         && sym.source == ctx.compilationUnit.source
         && !sym.isConstructor // not constructor of package object
         && !sym.is(Package) && !sym.name.isPackageObjectName
-        && !sym.isExperimental

       pack.info.decls.toList.iterator.flatMap: sym =>
         if sym.isClass && (sym.is(Package) || sym.isPackageObject) then