Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect macro dependencies within the current run #7192

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 51 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1239,17 +1239,60 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
assert(level == 0)
val inlinedFrom = enclosingInlineds.last
val ctx1 = tastyreflect.MacroExpansion.context(inlinedFrom)
val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx1)

val inlinedNormailizer = new TreeMap {
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr)
case _ => super.transform(tree)
val dependencies = macroDependencies(body)

if (dependencies.nonEmpty) {
var location = inlinedFrom.symbol
if (location.isLocalDummy) location = location.owner

val msg =
em"""Failed to expand macro. This macro depends on an implementation that is defined in the same project and not yet compiled.
|In particular ${inlinedFrom.symbol} depends on ${dependencies.map(_.show).mkString(", ")}.
|
|Moving ${dependencies.map(_.show).mkString(", ")} to a different project would fix this.
|""".stripMargin
ctx.error(msg, inlinedFrom.sourcePos)
EmptyTree
} else {
val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx1)

val inlinedNormailizer = new TreeMap {
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = tree match {
case Inlined(EmptyTree, Nil, expr) if enclosingInlineds.isEmpty => transform(expr)
case _ => super.transform(tree)
}
}
val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice)
if (normalizedSplice.isEmpty) normalizedSplice
else normalizedSplice.withSpan(span)
}
val normalizedSplice = inlinedNormailizer.transform(evaluatedSplice)
if (normalizedSplice.isEmpty) normalizedSplice
else normalizedSplice.withSpan(span)
}

/** Return the set of symbols that are refered at level -1 by the tree and defined in the current run.
* This corresponds to the symbols that will need to be interpreted.
*/
private def macroDependencies(tree: Tree)(implicit ctx: Context) = {
new TreeAccumulator[Set[Symbol]] {
private[this] var level = -1
override def apply(syms: Set[Symbol], tree: tpd.Tree)(implicit ctx: Context): Set[Symbol] = {
if (level != -1) foldOver(syms, tree)
else tree match {
case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal =>
foldOver(syms + tree.symbol, tree)
case Quoted(body) =>
level += 1
try apply(syms, body)
finally level -= 1
case Spliced(body) =>
level -= 1
try apply(syms, body)
finally level += 1
case _ =>
foldOver(syms, tree)
}
}
}.apply(Set.empty, tree)
}
}

8 changes: 8 additions & 0 deletions tests/neg-macros/macros-in-same-project-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Error: tests/neg-macros/macros-in-same-project-1/Bar.scala:2:13 -----------------------------------------------------
2 | Foo.myMacro() // error
| ^^^^^^^^^^^^^
|Failed to expand macro. This macro depends on an implementation that is defined in the same project and not yet compiled.
|In particular method myMacro depends on method aMacroImplementation.
|
|Moving method aMacroImplementation to a different project would fix this.
| This location is in code that was inlined at Bar.scala:2
3 changes: 3 additions & 0 deletions tests/neg-macros/macros-in-same-project-1/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Bar {
Foo.myMacro() // error
}
9 changes: 9 additions & 0 deletions tests/neg-macros/macros-in-same-project-1/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import scala.quoted._

object Foo {

inline def myMacro(): Unit = ${ aMacroImplementation }

def aMacroImplementation given QuoteContext: Expr[Unit] = '{ println("Hello") }

}
8 changes: 8 additions & 0 deletions tests/neg-macros/macros-in-same-project-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Error: tests/neg-macros/macros-in-same-project-2/Bar.scala:5:9 ------------------------------------------------------
5 | myMacro() // error
| ^^^^^^^^^
|Failed to expand macro. This macro depends on an implementation that is defined in the same project and not yet compiled.
|In particular method myMacro depends on method aMacroImplementation, object Foo.
|
|Moving method aMacroImplementation, object Foo to a different project would fix this.
| This location is in code that was inlined at Bar.scala:5
7 changes: 7 additions & 0 deletions tests/neg-macros/macros-in-same-project-2/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
object Bar {

inline def myMacro(): Unit = ${ Foo.aMacroImplementation }

myMacro() // error

}
7 changes: 7 additions & 0 deletions tests/neg-macros/macros-in-same-project-2/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import scala.quoted._

object Foo {

def aMacroImplementation given QuoteContext: Expr[Unit] = '{ println("Hello") }

}
8 changes: 8 additions & 0 deletions tests/neg-macros/macros-in-same-project-3.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Error: tests/neg-macros/macros-in-same-project-3/Baz.scala:2:13 -----------------------------------------------------
2 | Bar.myMacro() // error
| ^^^^^^^^^^^^^
|Failed to expand macro. This macro depends on an implementation that is defined in the same project and not yet compiled.
|In particular method myMacro depends on method aMacroImplementation.
|
|Moving method aMacroImplementation to a different project would fix this.
| This location is in code that was inlined at Baz.scala:2
9 changes: 9 additions & 0 deletions tests/neg-macros/macros-in-same-project-3/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import scala.quoted._

object Bar {

inline def myMacro(): Unit = ${ aMacroImplementation }

def aMacroImplementation given QuoteContext: Expr[Unit] = Foo.hello()

}
3 changes: 3 additions & 0 deletions tests/neg-macros/macros-in-same-project-3/Baz.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Baz {
Bar.myMacro() // error
}
7 changes: 7 additions & 0 deletions tests/neg-macros/macros-in-same-project-3/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import scala.quoted._

object Foo {

def hello() given QuoteContext: Expr[Unit] = '{ println("Hello") }

}
8 changes: 8 additions & 0 deletions tests/neg-macros/macros-in-same-project-4.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Error: tests/neg-macros/macros-in-same-project-4/Bar.scala:5:13 -----------------------------------------------------
5 | Foo.myMacro() // error
| ^^^^^^^^^^^^^
|Failed to expand macro. This macro depends on an implementation that is defined in the same project and not yet compiled.
|In particular method myMacro depends on method aMacroImplementation.
|
|Moving method aMacroImplementation to a different project would fix this.
| This location is in code that was inlined at Bar.scala:5
8 changes: 8 additions & 0 deletions tests/neg-macros/macros-in-same-project-4/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.quoted._

object Bar {

Foo.myMacro() // error

def hello() given QuoteContext: Expr[Unit] = '{ println("Hello") }
}
9 changes: 9 additions & 0 deletions tests/neg-macros/macros-in-same-project-4/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import scala.quoted._

object Foo {

inline def myMacro(): Unit = ${ aMacroImplementation }

def aMacroImplementation given QuoteContext: Expr[Unit] = Bar.hello()

}