scala / scala3

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

Macro annotation that processes inlined tree crashes under `-Ycheck:all` #17007

Open pweisenburger opened 1 year ago

pweisenburger commented 1 year ago

Compiler version

3.3.1-RC1-bin-20230216-2507577-NIGHTLY

Minimized code

Consider the following macro definition (the macro annotation extracts the number v that is applied to locally in inlineMethod):

import scala.annotation.MacroAnnotation
import scala.quoted.*

class annotation extends MacroAnnotation:
  def transform(using Quotes)(tree: quotes.reflect.Definition) =
    import quotes.reflect.*

    tree match
      case tree: ClassDef =>
        val List(DefDef(name, paramss, tpt, Some(body))) = tree.body: @unchecked

        val rhs = body match
          case Inlined(_, _, Block(List(Apply(_ /* `locally` */, List(rhs))), _)) => rhs

        val method = DefDef.copy(tree.body.head)(name, paramss, tpt, Some(rhs.changeOwner(tree.body.head.symbol)))

        List(ClassDef.copy(tree)(tree.name, tree.constructor, tree.parents, tree.self, List(method)))

Macro call site:

transparent inline def inlineMethod(v: Int) =
  locally(v)
  0

@annotation
class Test:
  def method = inlineMethod(42)

Output

When compiling with -Ycheck:all, the compiler crashes; otherwise, the code compiles. It seems that the transparent inline method is crucial to produce the crash.

Error and Stack Trace ```scala *** error while checking Test.scala after phase inlining *** java.util.NoSuchElementException: head of empty list while running Ycheck on Test.scala java.util.NoSuchElementException: head of empty list while compiling Test.scala, TestMacro.scala [error] ## Exception when compiling 2 sources [error] java.util.NoSuchElementException: head of empty list [error] scala.collection.immutable.Nil$.head(List.scala:662) [error] scala.collection.immutable.Nil$.head(List.scala:661) [error] dotty.tools.dotc.transform.YCheckPositions$$anon$1.traverse(YCheckPositions.scala:45) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.foldOver(Trees.scala:1620) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.traverseChildren(Trees.scala:1661) [error] dotty.tools.dotc.transform.YCheckPositions$$anon$1.traverse(YCheckPositions.scala:53) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.fold$1(Trees.scala:1532) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.apply(Trees.scala:1534) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.foldOver(Trees.scala:1627) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.traverseChildren(Trees.scala:1661) [error] dotty.tools.dotc.transform.YCheckPositions$$anon$1.traverse(YCheckPositions.scala:53) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.foldOver(Trees.scala:1624) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.traverseChildren(Trees.scala:1661) [error] dotty.tools.dotc.transform.YCheckPositions$$anon$1.traverse(YCheckPositions.scala:53) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.apply(Trees.scala:1660) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.fold$1(Trees.scala:1532) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.apply(Trees.scala:1534) [error] dotty.tools.dotc.ast.Trees$Instance$TreeAccumulator.foldOver(Trees.scala:1633) [error] dotty.tools.dotc.ast.Trees$Instance$TreeTraverser.traverseChildren(Trees.scala:1661) [error] dotty.tools.dotc.transform.YCheckPositions$$anon$1.traverse(YCheckPositions.scala:53) [error] dotty.tools.dotc.transform.YCheckPositions.checkPostCondition(YCheckPositions.scala:58) [error] dotty.tools.dotc.transform.TreeChecker$Checker.typedUnadapted$$anonfun$1(TreeChecker.scala:412) [error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) [error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) [error] scala.collection.immutable.List.foreach(List.scala:333) [error] dotty.tools.dotc.transform.TreeChecker$Checker.typedUnadapted(TreeChecker.scala:412) [error] dotty.tools.dotc.typer.Typer.typed(Typer.scala:3077) [error] dotty.tools.dotc.typer.Typer.typed(Typer.scala:3081) [error] dotty.tools.dotc.transform.TreeChecker$Checker.typed(TreeChecker.scala:378) [error] dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3193) [error] dotty.tools.dotc.transform.TreeChecker.check(TreeChecker.scala:126) [error] dotty.tools.dotc.transform.TreeChecker.run(TreeChecker.scala:106) [error] dotty.tools.dotc.core.Phases$Phase.runOn$$anonfun$1(Phases.scala:324) [error] scala.collection.immutable.List.map(List.scala:246) [error] dotty.tools.dotc.core.Phases$Phase.runOn(Phases.scala:328) [error] dotty.tools.dotc.Run.runPhases$1$$anonfun$1(Run.scala:247) [error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:15) [error] scala.runtime.function.JProcedure1.apply(JProcedure1.java:10) [error] scala.collection.ArrayOps$.foreach$extension(ArrayOps.scala:1321) [error] dotty.tools.dotc.Run.runPhases$1(Run.scala:263) [error] dotty.tools.dotc.Run.compileUnits$$anonfun$1(Run.scala:271) [error] dotty.tools.dotc.Run.compileUnits$$anonfun$adapted$1(Run.scala:280) [error] dotty.tools.dotc.util.Stats$.maybeMonitored(Stats.scala:67) [error] dotty.tools.dotc.Run.compileUnits(Run.scala:280) [error] dotty.tools.dotc.Run.compileUnits(Run.scala:201) [error] dotty.tools.dotc.Driver.finish(Driver.scala:56) [error] dotty.tools.dotc.Driver.doCompile(Driver.scala:36) [error] dotty.tools.xsbt.CompilerBridgeDriver.run(CompilerBridgeDriver.java:88) [error] dotty.tools.xsbt.CompilerBridge.run(CompilerBridge.java:22) ```

Note that I'm not entirely certain whether extracting a subtree of an Inlined node using Inlined(_, _, Block(List(Apply(_, List(rhs))), _)) is legal, but I think it should be (how else would you get the tree to process it further).

nicolasstucki commented 1 year ago

Note that I'm not entirely certain whether extracting a subtree of an Inlined node using Inlined(, , Block(List(Apply(, List(rhs))), )) is legal, but I think it should be (how else would you get the tree to process it further).

You should not drop the inlined nodes. These are used to keep track of the source file of a position. Maybe you should write:

val rhs = body match
  case Inlined(call, bindings, Block(List(Apply(_ /* `locally` */, List(rhs))), _)) => Inlined.copy(body)(call, bindings, rhs)