From 6f21c721e40d8895bf41f41653879b3fbcd7e6b1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Sep 2019 19:08:44 +0200 Subject: [PATCH 01/27] Suspend callers of macros compiled in the same run Allows macros to be defined and called in the same project. The idea is to suspend the callers of a macro (and their upstream dependencies), compile the sourcefile(s) containing the macro(s), and then compile the suspended files in a second run. The scheme detects and flags as errors calls to macros in the same file and mutual dependcies between different files which would not work with suspensions. It needs to be complements by another mechanism that detects that class files loaded by a macro class loader do not have assiciated source files that are currently compiled. --- .../dotty/tools/dotc/CompilationUnit.scala | 10 ++++++ compiler/src/dotty/tools/dotc/Driver.scala | 21 ++++++++---- compiler/src/dotty/tools/dotc/Run.scala | 16 +++++---- .../src/dotty/tools/dotc/typer/FrontEnd.scala | 34 ++++++++++++++----- .../src/dotty/tools/dotc/typer/Inliner.scala | 4 +++ .../src/dotty/tools/dotc/typer/Namer.scala | 20 ++++++++--- tests/neg/macro-cycle1.scala | 8 +++++ tests/run/macros-in-same-project1.check | 3 ++ tests/run/macros-in-same-project1/Foo.scala | 9 +++++ tests/run/macros-in-same-project1/Test.scala | 3 ++ 10 files changed, 102 insertions(+), 26 deletions(-) create mode 100644 tests/neg/macro-cycle1.scala create mode 100644 tests/run/macros-in-same-project1.check create mode 100644 tests/run/macros-in-same-project1/Foo.scala create mode 100644 tests/run/macros-in-same-project1/Test.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index bce33813fa70..c4efcc7f0e68 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -31,10 +31,20 @@ class CompilationUnit protected (val source: SourceFile) { /** A structure containing a temporary map for generating inline accessors */ val inlineAccessors: InlineAccessors = new InlineAccessors + + var suspended: Boolean = false + + def suspend()(given ctx: Context): Nothing = + if !suspended then + suspended = true + ctx.run.suspendedUnits += this + throw CompilationUnit.SuspendException() } object CompilationUnit { + class SuspendException extends Exception + /** Make a compilation unit for top class `clsd` with the contents of the `unpickled` tree */ def apply(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = apply(new SourceFile(clsd.symbol.associatedFile, Array.empty[Char]), unpickled, forceTrees) diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index 81f79ecdb094..790383060267 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -30,23 +30,30 @@ class Driver { protected def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = if (fileNames.nonEmpty) - try { + try val run = compiler.newRun run.compile(fileNames) - run.printSummary() - } - catch { + + def finish(run: Run): Unit = + run.printSummary() + if !ctx.reporter.errorsReported && run.suspendedUnits.nonEmpty then + val run1 = compiler.newRun + for unit <- run.suspendedUnits do unit.suspended = false + run1.compileUnits(run.suspendedUnits.toList) + finish(run1) + + finish(run) + catch case ex: FatalError => ctx.error(ex.getMessage) // signals that we should fail compilation. - ctx.reporter case ex: TypeError => println(s"${ex.toMessage} while compiling ${fileNames.mkString(", ")}") throw ex case ex: Throwable => println(s"$ex while compiling ${fileNames.mkString(", ")}") throw ex - } - else ctx.reporter + ctx.reporter + end doCompile protected def initCtx: Context = (new ContextBase).initialCtx diff --git a/compiler/src/dotty/tools/dotc/Run.scala b/compiler/src/dotty/tools/dotc/Run.scala index a4ccd404d86c..4397c7ba89ec 100644 --- a/compiler/src/dotty/tools/dotc/Run.scala +++ b/compiler/src/dotty/tools/dotc/Run.scala @@ -78,17 +78,20 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint */ def units: List[CompilationUnit] = myUnits + var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer() + private def units_=(us: List[CompilationUnit]): Unit = myUnits = us - /** The files currently being compiled, this may return different results over time. - * These files do not have to be source files since it's possible to compile - * from TASTY. - */ + /** The files currently being compiled (active or suspended). + * This may return different results over time. + * These files do not have to be source files since it's possible to compile + * from TASTY. + */ def files: Set[AbstractFile] = { if (myUnits ne myUnitsCached) { myUnitsCached = myUnits - myFiles = myUnits.map(_.source.file).toSet + myFiles = (myUnits ++ suspendedUnits).map(_.source.file).toSet } myFiles } @@ -247,11 +250,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint } /** Print summary; return # of errors encountered */ - def printSummary(): Reporter = { + def printSummary(): Unit = { printMaxConstraint() val r = ctx.reporter r.printSummary - r } override def reset(): Unit = { diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 520c455b0596..934ca81de3fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -6,6 +6,7 @@ import core._ import Phases._ import Contexts._ import Symbols._ +import Decorators._ import dotty.tools.dotc.parsing.JavaParsers.JavaParser import parsing.Parsers.Parser import config.Config @@ -71,11 +72,15 @@ class FrontEnd extends Phase { } def typeCheck(implicit ctx: Context): Unit = monitor("typechecking") { - val unit = ctx.compilationUnit - unit.tpdTree = ctx.typer.typedExpr(unit.untpdTree) - typr.println("typed: " + unit.source) - record("retained untyped trees", unit.untpdTree.treeSize) - record("retained typed trees after typer", unit.tpdTree.treeSize) + try + val unit = ctx.compilationUnit + if !unit.suspended then + unit.tpdTree = ctx.typer.typedExpr(unit.untpdTree) + typr.println("typed: " + unit.source) + record("retained untyped trees", unit.untpdTree.treeSize) + record("retained typed trees after typer", unit.tpdTree.treeSize) + catch + case ex: CompilationUnit.SuspendException => } private def firstTopLevelDef(trees: List[tpd.Tree])(implicit ctx: Context): Symbol = trees match { @@ -86,14 +91,14 @@ class FrontEnd extends Phase { } protected def discardAfterTyper(unit: CompilationUnit)(implicit ctx: Context): Boolean = - unit.isJava || firstTopLevelDef(unit.tpdTree :: Nil).isPrimitiveValueClass + unit.isJava || unit.suspended || firstTopLevelDef(unit.tpdTree :: Nil).isPrimitiveValueClass override def runOn(units: List[CompilationUnit])(implicit ctx: Context): List[CompilationUnit] = { val unitContexts = for (unit <- units) yield { ctx.inform(s"compiling ${unit.source}") ctx.fresh.setCompilationUnit(unit) } - unitContexts foreach (parse(_)) + unitContexts.foreach(parse(_)) record("parsedTrees", ast.Trees.ntrees) remaining = unitContexts while (remaining.nonEmpty) { @@ -108,7 +113,20 @@ class FrontEnd extends Phase { unitContexts.foreach(typeCheck(_)) record("total trees after typer", ast.Trees.ntrees) - unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper) + val newUnits = unitContexts.map(_.compilationUnit).filterNot(discardAfterTyper) + val suspendedUnits = ctx.run.suspendedUnits + if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then + val where = + if suspendedUnits.size == 1 then i"in ${suspendedUnits.head}." + else i"""among + | + | ${suspendedUnits.toList}%, % + |""" + ctx.error(em"""Cyclic macro dependencies $where + |Compilation stopped since no further progress can be made. + | + |To fix this, place macros in one set of files and their callers in another.""") + newUnits } def run(implicit ctx: Context): Unit = unsupported("run") diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 64af560f8847..ddf0c670a983 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -70,6 +70,10 @@ object Inliner { */ def inlineCall(tree: Tree)(implicit ctx: Context): Tree = { if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) + if tree.symbol.is(Macro) && tree.symbol.isDefinedInCurrentRun then + if ctx.compilationUnit.source.file == tree.symbol.associatedFile then + ctx.error("Cannot call macro defined in the same source file", tree.sourcePos) + ctx.compilationUnit.suspend() /** Set the position of all trees logically contained in the expansion of * inlined call `call` to the position of `call`. This transform is necessary diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e27dc791b594..3a3fd86d80de 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -792,10 +792,15 @@ class Namer { typer: Typer => assert(ctx.mode.is(Mode.Interactive), s"completing $denot in wrong run ${ctx.runId}, was created in ${creationContext.runId}") denot.info = UnspecifiedErrorType } - else { - completeInCreationContext(denot) - if (denot.isCompleted) registerIfChild(denot) - } + else + try + completeInCreationContext(denot) + if (denot.isCompleted) registerIfChild(denot) + catch + case ex: CompilationUnit.SuspendException => + val completer = SuspendCompleter() + denot.info = completer + completer.complete(denot) } protected def addAnnotations(sym: Symbol): Unit = original match { @@ -1192,6 +1197,13 @@ class Namer { typer: Typer => } } + class SuspendCompleter extends LazyType, SymbolLoaders.SecondCompleter { + + final override def complete(denot: SymDenotation)(implicit ctx: Context): Unit = + denot.resetFlag(Touched) // allow one more completion + ctx.compilationUnit.suspend() + } + /** Typecheck `tree` during completion using `typed`, and remember result in TypedAhead map */ def typedAhead(tree: Tree, typed: untpd.Tree => tpd.Tree)(implicit ctx: Context): tpd.Tree = { val xtree = expanded(tree) diff --git a/tests/neg/macro-cycle1.scala b/tests/neg/macro-cycle1.scala new file mode 100644 index 000000000000..b772005e35f3 --- /dev/null +++ b/tests/neg/macro-cycle1.scala @@ -0,0 +1,8 @@ +import scala.quoted.{Expr, QuoteContext} +object Test { + def fooImpl(given QuoteContext): Expr[Unit] = '{println("hi")} + + inline def foo: Unit = ${fooImpl} + + foo +} diff --git a/tests/run/macros-in-same-project1.check b/tests/run/macros-in-same-project1.check new file mode 100644 index 000000000000..81c22b90899a --- /dev/null +++ b/tests/run/macros-in-same-project1.check @@ -0,0 +1,3 @@ +object Test extends App { + Foo.myMacro() +} \ No newline at end of file diff --git a/tests/run/macros-in-same-project1/Foo.scala b/tests/run/macros-in-same-project1/Foo.scala new file mode 100644 index 000000000000..524ccde401f8 --- /dev/null +++ b/tests/run/macros-in-same-project1/Foo.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object Foo { + + inline def myMacro(): Unit = ${ aMacroImplementation } + + def aMacroImplementation(given QuoteContext): Expr[Unit] = '{ println("Hello") } + +} \ No newline at end of file diff --git a/tests/run/macros-in-same-project1/Test.scala b/tests/run/macros-in-same-project1/Test.scala new file mode 100644 index 000000000000..81c22b90899a --- /dev/null +++ b/tests/run/macros-in-same-project1/Test.scala @@ -0,0 +1,3 @@ +object Test extends App { + Foo.myMacro() +} \ No newline at end of file From 90c2a37483a852f799787f175f740a4be0365d2f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 12:27:30 +0200 Subject: [PATCH 02/27] Make macroDependencies compilation-order independent Using a Set risks giving different results in different runs. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index ddf0c670a983..00f80947d1f7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1276,13 +1276,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * This corresponds to the symbols that will need to be interpreted. */ private def macroDependencies(tree: Tree)(implicit ctx: Context) = - new TreeAccumulator[Set[Symbol]] { + new TreeAccumulator[List[Symbol]] { private[this] var level = -1 - override def apply(syms: Set[Symbol], tree: tpd.Tree)(implicit ctx: Context): Set[Symbol] = + override def apply(syms: List[Symbol], tree: tpd.Tree)(implicit ctx: Context): List[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) + foldOver(tree.symbol :: syms, tree) case Quoted(body) => level += 1 try apply(syms, body) @@ -1294,6 +1294,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => foldOver(syms, tree) } - }.apply(Set.empty, tree) + }.apply(Nil, tree) } From 922b57e2cde82b3330e47028a60f3b6db7e09a8c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 15:07:57 +0200 Subject: [PATCH 03/27] Track splice calls instead of inline calls The real thing that needs to be tracked for cyclic dependencies are calls to spliced methods. Move the suspend machinery from inline calls to these. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 00f80947d1f7..34f5d2ac94ab 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -70,10 +70,6 @@ object Inliner { */ def inlineCall(tree: Tree)(implicit ctx: Context): Tree = { if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree) - if tree.symbol.is(Macro) && tree.symbol.isDefinedInCurrentRun then - if ctx.compilationUnit.source.file == tree.symbol.associatedFile then - ctx.error("Cannot call macro defined in the same source file", tree.sourcePos) - ctx.compilationUnit.suspend() /** Set the position of all trees logically contained in the expansion of * inlined call `call` to the position of `call`. This transform is necessary @@ -1241,35 +1237,25 @@ 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 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) + if dependencies.nonEmpty then + for sym <- dependencies do + if ctx.compilationUnit.source.file == sym.associatedFile then + ctx.error(em"Cannot call macro $sym defined in the same source file", body.sourcePos) + ctx.compilationUnit.suspend() // this throws a SuspendException - 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 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. From 77f65f81f9e59c4ca37ff400fb1146d04a9d9060 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 15:13:36 +0200 Subject: [PATCH 04/27] Fix tests Drop reflect-inline, since it did not do what one thought (called stdlib stripMargin instead of locally defined one). Move macros-in-same-project1 to run-macros Fix error marker for macro-cycle1.scala. --- compiler/test/dotc/run-test-pickling.blacklist | 1 + tests/neg/macro-cycle1.scala | 2 +- .../macros-in-same-project1/Foo.scala | 0 .../macros-in-same-project1/Test.scala | 0 tests/run-macros/reflect-inline/assert_1.scala | 16 ---------------- tests/run-macros/reflect-inline/test_2.scala | 10 ---------- 6 files changed, 2 insertions(+), 27 deletions(-) rename tests/{run => run-macros}/macros-in-same-project1/Foo.scala (100%) rename tests/{run => run-macros}/macros-in-same-project1/Test.scala (100%) delete mode 100644 tests/run-macros/reflect-inline/assert_1.scala delete mode 100644 tests/run-macros/reflect-inline/test_2.scala diff --git a/compiler/test/dotc/run-test-pickling.blacklist b/compiler/test/dotc/run-test-pickling.blacklist index f6dab8b29bcd..f3e4c7ec847c 100644 --- a/compiler/test/dotc/run-test-pickling.blacklist +++ b/compiler/test/dotc/run-test-pickling.blacklist @@ -16,6 +16,7 @@ derive-generic.scala mixin-forwarder-overload t8905 t10889 +macros-in-same-project1 i5257.scala enum-java tuple-ops.scala diff --git a/tests/neg/macro-cycle1.scala b/tests/neg/macro-cycle1.scala index b772005e35f3..9fca9ef3de12 100644 --- a/tests/neg/macro-cycle1.scala +++ b/tests/neg/macro-cycle1.scala @@ -2,7 +2,7 @@ import scala.quoted.{Expr, QuoteContext} object Test { def fooImpl(given QuoteContext): Expr[Unit] = '{println("hi")} - inline def foo: Unit = ${fooImpl} + inline def foo: Unit = ${fooImpl} // error foo } diff --git a/tests/run/macros-in-same-project1/Foo.scala b/tests/run-macros/macros-in-same-project1/Foo.scala similarity index 100% rename from tests/run/macros-in-same-project1/Foo.scala rename to tests/run-macros/macros-in-same-project1/Foo.scala diff --git a/tests/run/macros-in-same-project1/Test.scala b/tests/run-macros/macros-in-same-project1/Test.scala similarity index 100% rename from tests/run/macros-in-same-project1/Test.scala rename to tests/run-macros/macros-in-same-project1/Test.scala diff --git a/tests/run-macros/reflect-inline/assert_1.scala b/tests/run-macros/reflect-inline/assert_1.scala deleted file mode 100644 index 693a185eee32..000000000000 --- a/tests/run-macros/reflect-inline/assert_1.scala +++ /dev/null @@ -1,16 +0,0 @@ -import scala.quoted._ - -object api { - inline def (inline x: String) stripMargin: String = - ${ stripImpl(x) } - - private def stripImpl(x: String)(given qctx: QuoteContext): Expr[String] = - Expr(x.stripMargin) - - inline def typeChecks(inline x: String): Boolean = - ${ typeChecksImpl(scala.compiletime.testing.typeChecks(x)) } - - private def typeChecksImpl(b: Boolean)(given qctx: QuoteContext): Expr[Boolean] = { - if (b) Expr(true) else Expr(false) - } -} diff --git a/tests/run-macros/reflect-inline/test_2.scala b/tests/run-macros/reflect-inline/test_2.scala deleted file mode 100644 index f99fbacbf0d7..000000000000 --- a/tests/run-macros/reflect-inline/test_2.scala +++ /dev/null @@ -1,10 +0,0 @@ -import api._ - -object Test { - def main(args: Array[String]): Unit = { - val a: String = "5" - assert(typeChecks("|1 + 1".stripMargin)) - assert(scala.compiletime.testing.typeChecks("|1 + 1".stripMargin)) - assert(("|3 + " + a).stripMargin == "3 + 5") - } -} \ No newline at end of file From 33178b026d2be9b79ae4c2db978502b3567d5233 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 15:14:18 +0200 Subject: [PATCH 05/27] Add `outermost` utility method to SourcePosition. Not needed right now, but might be useful in the future. --- compiler/src/dotty/tools/dotc/util/SourcePosition.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala index 51dde590c475..62744659dde3 100644 --- a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala @@ -61,6 +61,9 @@ extends interfaces.SourcePosition with Showable { def focus : SourcePosition = withSpan(span.focus) def toSynthetic: SourcePosition = withSpan(span.toSynthetic) + def outermost: SourcePosition = + if outer == null || outer == NoSourcePosition then this else outer.outermost + override def toString: String = s"${if (source.exists) source.file.toString else "(no source)"}:$span" From 7fa6f9a3433fe95b5fb310f723053f86ed46e270 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 16:22:53 +0200 Subject: [PATCH 06/27] Add debug output to track down problem compiling ScalaTest --- compiler/src/dotty/tools/dotc/CompilationUnit.scala | 2 ++ compiler/src/dotty/tools/dotc/typer/Inliner.scala | 1 + 2 files changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index c4efcc7f0e68..e1ccc8a4c1c0 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -10,6 +10,7 @@ import dotty.tools.dotc.core.SymDenotations.ClassDenotation import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.transform.SymUtils._ import util.{NoSource, SourceFile} +import core.Decorators._ class CompilationUnit protected (val source: SourceFile) { @@ -36,6 +37,7 @@ class CompilationUnit protected (val source: SourceFile) { def suspend()(given ctx: Context): Nothing = if !suspended then + println(i"suspended: $this") suspended = true ctx.run.suspendedUnits += this throw CompilationUnit.SuspendException() diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 34f5d2ac94ab..52780f62d00b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1243,6 +1243,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { for sym <- dependencies do if ctx.compilationUnit.source.file == sym.associatedFile then ctx.error(em"Cannot call macro $sym defined in the same source file", body.sourcePos) + println(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}") ctx.compilationUnit.suspend() // this throws a SuspendException val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx1) From 18b296c953eaa27613e4d0f61320979be8f2dacf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 16:48:30 +0200 Subject: [PATCH 07/27] Add -Xprint-suspension option to print info about suspensions --- compiler/src/dotty/tools/dotc/CompilationUnit.scala | 3 ++- compiler/src/dotty/tools/dotc/Driver.scala | 8 ++++++-- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/FrontEnd.scala | 4 +++- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 3 ++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index e1ccc8a4c1c0..ea0faa19ce6d 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -37,7 +37,8 @@ class CompilationUnit protected (val source: SourceFile) { def suspend()(given ctx: Context): Nothing = if !suspended then - println(i"suspended: $this") + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"suspended: $this") suspended = true ctx.run.suspendedUnits += this throw CompilationUnit.SuspendException() diff --git a/compiler/src/dotty/tools/dotc/Driver.scala b/compiler/src/dotty/tools/dotc/Driver.scala index 790383060267..67f16b7208f5 100644 --- a/compiler/src/dotty/tools/dotc/Driver.scala +++ b/compiler/src/dotty/tools/dotc/Driver.scala @@ -10,6 +10,7 @@ import core.{MacroClassLoader, Mode, TypeError} import dotty.tools.dotc.ast.Positioned import dotty.tools.io.File import reporting._ +import core.Decorators._ import scala.util.control.NonFatal import fromtasty.{TASTYCompiler, TastyFileUtil} @@ -37,9 +38,12 @@ class Driver { def finish(run: Run): Unit = run.printSummary() if !ctx.reporter.errorsReported && run.suspendedUnits.nonEmpty then + val suspendedUnits = run.suspendedUnits.toList + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"compiling suspended $suspendedUnits%, %") val run1 = compiler.newRun - for unit <- run.suspendedUnits do unit.suspended = false - run1.compileUnits(run.suspendedUnits.toList) + for unit <- suspendedUnits do unit.suspended = false + run1.compileUnits(suspendedUnits) finish(run1) finish(run) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 3cd5a42c41ed..c52f7a231896 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -77,6 +77,7 @@ class ScalaSettings extends Settings.SettingGroup { val XprintDiff: Setting[Boolean] = BooleanSetting("-Xprint-diff", "Print changed parts of the tree since last print.") val XprintDiffDel: Setting[Boolean] = BooleanSetting("-Xprint-diff-del", "Print changed parts of the tree since last print including deleted parts.") val XprintInline: Setting[Boolean] = BooleanSetting("-Xprint-inline", "Show where inlined code comes from") + val XprintSuspension: Setting[Boolean] = BooleanSetting("-Xprint-suspension", "Show when code is suspended until macros are compiled") val Xprompt: Setting[Boolean] = BooleanSetting("-Xprompt", "Display a prompt after each error (debugging option).") val XnoValueClasses: Setting[Boolean] = BooleanSetting("-Xno-value-classes", "Do not use value classes. Helps debugging.") val XreplLineWidth: Setting[Int] = IntSetting("-Xrepl-line-width", "Maximal number of columns per line for REPL output", 390) @@ -200,7 +201,7 @@ class ScalaSettings extends Settings.SettingGroup { "The source repository of your project", "" ) - + val projectLogo: Setting[String] = StringSetting( "-project-logo", "project logo filename", diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 934ca81de3fb..98a45b4b442d 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -125,7 +125,9 @@ class FrontEnd extends Phase { ctx.error(em"""Cyclic macro dependencies $where |Compilation stopped since no further progress can be made. | - |To fix this, place macros in one set of files and their callers in another.""") + |To fix this, place macros in one set of files and their callers in another. + | + |Compiling with -XprintSuspension gives more information.""") newUnits } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 52780f62d00b..fa37a015dc05 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1243,7 +1243,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { for sym <- dependencies do if ctx.compilationUnit.source.file == sym.associatedFile then ctx.error(em"Cannot call macro $sym defined in the same source file", body.sourcePos) - println(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}") + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", body.sourcePos) ctx.compilationUnit.suspend() // this throws a SuspendException val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx1) From 1715f317871dec6fb180df5f9c23b8064abd3bb6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 17:11:27 +0200 Subject: [PATCH 08/27] Compile scalatest with -Xprint-suspension --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 6a53948f8ad7..8cb899d8025f 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 6a53948f8ad733896387fceec860cc6a04e068d8 +Subproject commit 8cb899d8025f25f9d758ac5918f44a6ca1cfa756 From 8cea72067fb5736285a6dbc8a209bddae6ee1e18 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Sep 2019 17:11:55 +0200 Subject: [PATCH 09/27] Don't count vals as macro dependencies --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index fa37a015dc05..db514835d003 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1269,7 +1269,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def apply(syms: List[Symbol], tree: tpd.Tree)(implicit ctx: Context): List[Symbol] = if (level != -1) foldOver(syms, tree) else tree match { - case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => + case tree: RefTree + if level == -1 + && tree.symbol.isDefinedInCurrentRun + && tree.symbol.isRealMethod + && !tree.symbol.isLocal => foldOver(tree.symbol :: syms, tree) case Quoted(body) => level += 1 From d241090225cbb79898ed9d2037abace32a8e6abd Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sat, 28 Sep 2019 09:14:03 +0200 Subject: [PATCH 10/27] Move tests to pos-macros --- tests/neg-macros/macros-in-same-project-1.check | 8 -------- tests/neg-macros/macros-in-same-project-1/Bar.scala | 3 --- tests/neg-macros/macros-in-same-project-2.check | 8 -------- tests/neg-macros/macros-in-same-project-3.check | 8 -------- tests/pos-macros/macros-in-same-project-1/Bar.scala | 3 +++ .../macros-in-same-project-1/Foo.scala | 0 .../macros-in-same-project-2/Bar.scala | 2 +- .../macros-in-same-project-2/Foo.scala | 0 .../macros-in-same-project-3/Bar.scala | 0 .../macros-in-same-project-3/Baz.scala | 0 .../macros-in-same-project-3/Foo.scala | 0 11 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 tests/neg-macros/macros-in-same-project-1.check delete mode 100644 tests/neg-macros/macros-in-same-project-1/Bar.scala delete mode 100644 tests/neg-macros/macros-in-same-project-2.check delete mode 100644 tests/neg-macros/macros-in-same-project-3.check create mode 100644 tests/pos-macros/macros-in-same-project-1/Bar.scala rename tests/{neg-macros => pos-macros}/macros-in-same-project-1/Foo.scala (100%) rename tests/{neg-macros => pos-macros}/macros-in-same-project-2/Bar.scala (79%) rename tests/{neg-macros => pos-macros}/macros-in-same-project-2/Foo.scala (100%) rename tests/{neg-macros => pos-macros}/macros-in-same-project-3/Bar.scala (100%) rename tests/{neg-macros => pos-macros}/macros-in-same-project-3/Baz.scala (100%) rename tests/{neg-macros => pos-macros}/macros-in-same-project-3/Foo.scala (100%) diff --git a/tests/neg-macros/macros-in-same-project-1.check b/tests/neg-macros/macros-in-same-project-1.check deleted file mode 100644 index f257aa16f0d2..000000000000 --- a/tests/neg-macros/macros-in-same-project-1.check +++ /dev/null @@ -1,8 +0,0 @@ --- 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 diff --git a/tests/neg-macros/macros-in-same-project-1/Bar.scala b/tests/neg-macros/macros-in-same-project-1/Bar.scala deleted file mode 100644 index 2924e2b2411a..000000000000 --- a/tests/neg-macros/macros-in-same-project-1/Bar.scala +++ /dev/null @@ -1,3 +0,0 @@ -object Bar { - Foo.myMacro() // error -} diff --git a/tests/neg-macros/macros-in-same-project-2.check b/tests/neg-macros/macros-in-same-project-2.check deleted file mode 100644 index 0ee5ec3f3a98..000000000000 --- a/tests/neg-macros/macros-in-same-project-2.check +++ /dev/null @@ -1,8 +0,0 @@ --- 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 diff --git a/tests/neg-macros/macros-in-same-project-3.check b/tests/neg-macros/macros-in-same-project-3.check deleted file mode 100644 index e2865e55578d..000000000000 --- a/tests/neg-macros/macros-in-same-project-3.check +++ /dev/null @@ -1,8 +0,0 @@ --- 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 diff --git a/tests/pos-macros/macros-in-same-project-1/Bar.scala b/tests/pos-macros/macros-in-same-project-1/Bar.scala new file mode 100644 index 000000000000..c53df5221218 --- /dev/null +++ b/tests/pos-macros/macros-in-same-project-1/Bar.scala @@ -0,0 +1,3 @@ +object Bar { + Foo.myMacro() +} diff --git a/tests/neg-macros/macros-in-same-project-1/Foo.scala b/tests/pos-macros/macros-in-same-project-1/Foo.scala similarity index 100% rename from tests/neg-macros/macros-in-same-project-1/Foo.scala rename to tests/pos-macros/macros-in-same-project-1/Foo.scala diff --git a/tests/neg-macros/macros-in-same-project-2/Bar.scala b/tests/pos-macros/macros-in-same-project-2/Bar.scala similarity index 79% rename from tests/neg-macros/macros-in-same-project-2/Bar.scala rename to tests/pos-macros/macros-in-same-project-2/Bar.scala index ebd6fb25895f..3f477541a1d3 100644 --- a/tests/neg-macros/macros-in-same-project-2/Bar.scala +++ b/tests/pos-macros/macros-in-same-project-2/Bar.scala @@ -2,6 +2,6 @@ object Bar { inline def myMacro(): Unit = ${ Foo.aMacroImplementation } - myMacro() // error + myMacro() } diff --git a/tests/neg-macros/macros-in-same-project-2/Foo.scala b/tests/pos-macros/macros-in-same-project-2/Foo.scala similarity index 100% rename from tests/neg-macros/macros-in-same-project-2/Foo.scala rename to tests/pos-macros/macros-in-same-project-2/Foo.scala diff --git a/tests/neg-macros/macros-in-same-project-3/Bar.scala b/tests/pos-macros/macros-in-same-project-3/Bar.scala similarity index 100% rename from tests/neg-macros/macros-in-same-project-3/Bar.scala rename to tests/pos-macros/macros-in-same-project-3/Bar.scala diff --git a/tests/neg-macros/macros-in-same-project-3/Baz.scala b/tests/pos-macros/macros-in-same-project-3/Baz.scala similarity index 100% rename from tests/neg-macros/macros-in-same-project-3/Baz.scala rename to tests/pos-macros/macros-in-same-project-3/Baz.scala diff --git a/tests/neg-macros/macros-in-same-project-3/Foo.scala b/tests/pos-macros/macros-in-same-project-3/Foo.scala similarity index 100% rename from tests/neg-macros/macros-in-same-project-3/Foo.scala rename to tests/pos-macros/macros-in-same-project-3/Foo.scala From 3612aae75c986bdcdc68a20dffd861d21dd939a4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sat, 28 Sep 2019 10:15:56 +0200 Subject: [PATCH 11/27] Fix error message --- compiler/src/dotty/tools/dotc/typer/FrontEnd.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 98a45b4b442d..695a1d85f80a 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -127,7 +127,7 @@ class FrontEnd extends Phase { | |To fix this, place macros in one set of files and their callers in another. | - |Compiling with -XprintSuspension gives more information.""") + |Compiling with -Xprint-suspension gives more information.""") newUnits } From c09ba9b6cf62361ab2e46a341f3e5f7ac5c50533 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sat, 28 Sep 2019 21:24:14 +0200 Subject: [PATCH 12/27] Fix reflect-inline test --- tests/run-macros/reflect-inline/assert_1.scala | 16 ++++++++++++++++ tests/run-macros/reflect-inline/test_2.scala | 10 ++++++++++ 2 files changed, 26 insertions(+) create mode 100644 tests/run-macros/reflect-inline/assert_1.scala create mode 100644 tests/run-macros/reflect-inline/test_2.scala diff --git a/tests/run-macros/reflect-inline/assert_1.scala b/tests/run-macros/reflect-inline/assert_1.scala new file mode 100644 index 000000000000..d408236f5fe2 --- /dev/null +++ b/tests/run-macros/reflect-inline/assert_1.scala @@ -0,0 +1,16 @@ +import scala.quoted._ + +object api { + inline def (inline x: String) stripMargin: String = + ${ stripImpl(x) } + + private def stripImpl(x: String)(given qctx: QuoteContext): Expr[String] = + Expr(augmentString(x).stripMargin) + + inline def typeChecks(inline x: String): Boolean = + ${ typeChecksImpl(scala.compiletime.testing.typeChecks(x)) } + + private def typeChecksImpl(b: Boolean)(given qctx: QuoteContext): Expr[Boolean] = { + if (b) Expr(true) else Expr(false) + } +} diff --git a/tests/run-macros/reflect-inline/test_2.scala b/tests/run-macros/reflect-inline/test_2.scala new file mode 100644 index 000000000000..2f36d7b52214 --- /dev/null +++ b/tests/run-macros/reflect-inline/test_2.scala @@ -0,0 +1,10 @@ +import api._ + +object Test { + def main(args: Array[String]): Unit = { + val a: String = "5" + assert(typeChecks("|1 + 1".stripMargin)) + assert(scala.compiletime.testing.typeChecks("|1 + 1".stripMargin)) + assert(("|3 + " + a).stripMargin == "3 + 5") + } +} From 3919421666102e9001d502bfc63e49c13b5dac2d Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sat, 28 Sep 2019 21:31:55 +0200 Subject: [PATCH 13/27] Revert "Don't count vals as macro dependencies" This reverts commit 8cea72067fb5736285a6dbc8a209bddae6ee1e18. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index db514835d003..fa37a015dc05 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1269,11 +1269,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def apply(syms: List[Symbol], tree: tpd.Tree)(implicit ctx: Context): List[Symbol] = if (level != -1) foldOver(syms, tree) else tree match { - case tree: RefTree - if level == -1 - && tree.symbol.isDefinedInCurrentRun - && tree.symbol.isRealMethod - && !tree.symbol.isLocal => + case tree: RefTree if level == -1 && tree.symbol.isDefinedInCurrentRun && !tree.symbol.isLocal => foldOver(tree.symbol :: syms, tree) case Quoted(body) => level += 1 From ef0a001e0f0bb069ba0660d93d0c414a62c21134 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sat, 28 Sep 2019 22:41:03 +0200 Subject: [PATCH 14/27] Reimplemet scalatest stripMargin --- community-build/community-projects/scalatest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 8cb899d8025f..1e4715ad7323 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 8cb899d8025f25f9d758ac5918f44a6ca1cfa756 +Subproject commit 1e4715ad7323f1eb13c7f6e799462fb47b6d212c From 4de288b89d35b0bf830ad523ffa858788947e217 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Sat, 28 Sep 2019 22:55:27 +0200 Subject: [PATCH 15/27] Add minimization of previous scalatest stripMargin issue --- tests/neg-macros/reflect-inline/assert_1.scala | 10 ++++++++++ tests/neg-macros/reflect-inline/test_2.scala | 6 ++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/neg-macros/reflect-inline/assert_1.scala create mode 100644 tests/neg-macros/reflect-inline/test_2.scala diff --git a/tests/neg-macros/reflect-inline/assert_1.scala b/tests/neg-macros/reflect-inline/assert_1.scala new file mode 100644 index 000000000000..eec9852b53d9 --- /dev/null +++ b/tests/neg-macros/reflect-inline/assert_1.scala @@ -0,0 +1,10 @@ +import scala.quoted._ + +object api { + inline def (inline x: String) stripMargin2: String = + ${ stripImpl(x) } + + private def stripImpl(x: String)(given qctx: QuoteContext): Expr[String] = + Expr(x.stripMargin) + +} diff --git a/tests/neg-macros/reflect-inline/test_2.scala b/tests/neg-macros/reflect-inline/test_2.scala new file mode 100644 index 000000000000..0be48280e8ec --- /dev/null +++ b/tests/neg-macros/reflect-inline/test_2.scala @@ -0,0 +1,6 @@ +import api._ + +class Test { + val a: String = "5" + a.stripMargin2 // error: `a should be a known constant +} From f1be00f9a2611d4c3c854dc02f9ec78b44426360 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 Sep 2019 10:23:30 +0200 Subject: [PATCH 16/27] Suspend macros when class not found during macro expansion --- .../dotty/tools/dotc/transform/Splicer.scala | 33 ++++++++++++------- .../src/dotty/tools/dotc/typer/Inliner.scala | 4 +-- .../neg-macros/macros-in-same-project-4.check | 14 ++++---- .../macros-in-same-project-4/Bar.scala | 3 +- .../macros-in-same-project-5/Bar.scala | 10 ++++++ .../macros-in-same-project-5/Foo.scala | 8 +++++ tests/neg/macro-cycle1.scala | 4 +-- 7 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 tests/neg-macros/macros-in-same-project-5/Bar.scala create mode 100644 tests/neg-macros/macros-in-same-project-5/Foo.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index bd988422541f..9d8d283caa9c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -47,6 +47,8 @@ object Splicer { interpretedExpr.fold(tree)(macroClosure => PickledQuotes.quotedExprToTree(macroClosure(QuoteContext()))) } catch { + case ex: CompilationUnit.SuspendException => + throw ex case ex: StopInterpretation => ctx.error(ex.msg, ex.pos) EmptyTree @@ -348,19 +350,26 @@ object Splicer { sw.write("\n") throw new StopInterpretation(sw.toString, pos) case ex: InvocationTargetException => - val sw = new StringWriter() - sw.write("Exception occurred while executing macro expansion.\n") - val targetException = ex.getTargetException - if (!ctx.settings.Ydebug.value) { - val end = targetException.getStackTrace.lastIndexWhere { x => - x.getClassName == method.getDeclaringClass.getCanonicalName && x.getMethodName == method.getName - } - val shortStackTrace = targetException.getStackTrace.take(end + 1) - targetException.setStackTrace(shortStackTrace) + ex.getTargetException match { + case targetException: NoClassDefFoundError => // FIXME check that the class is beeining compiled now + val className = targetException.getMessage + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"suspension triggered by NoClassDefFoundError($className)", pos) // TODO improve message + ctx.compilationUnit.suspend() // this throws a SuspendException + case targetException => + val sw = new StringWriter() + sw.write("Exception occurred while executing macro expansion.\n") + if (!ctx.settings.Ydebug.value) { + val end = targetException.getStackTrace.lastIndexWhere { x => + x.getClassName == method.getDeclaringClass.getCanonicalName && x.getMethodName == method.getName + } + val shortStackTrace = targetException.getStackTrace.take(end + 1) + targetException.setStackTrace(shortStackTrace) + } + targetException.printStackTrace(new PrintWriter(sw)) + sw.write("\n") + throw new StopInterpretation(sw.toString, pos) } - targetException.printStackTrace(new PrintWriter(sw)) - sw.write("\n") - throw new StopInterpretation(sw.toString, pos) } /** List of classes of the parameters of the signature of `sym` */ diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index fa37a015dc05..6f05a502db22 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1242,9 +1242,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if dependencies.nonEmpty then for sym <- dependencies do if ctx.compilationUnit.source.file == sym.associatedFile then - ctx.error(em"Cannot call macro $sym defined in the same source file", body.sourcePos) + ctx.error(em"Cannot call macro $sym defined in the same source file", call.sourcePos) if (ctx.settings.XprintSuspension.value) - ctx.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", body.sourcePos) + ctx.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.sourcePos) ctx.compilationUnit.suspend() // this throws a SuspendException val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, MacroClassLoader.fromContext)(ctx1) diff --git a/tests/neg-macros/macros-in-same-project-4.check b/tests/neg-macros/macros-in-same-project-4.check index 5cdc12b31cb9..79c78fd0fb7f 100644 --- a/tests/neg-macros/macros-in-same-project-4.check +++ b/tests/neg-macros/macros-in-same-project-4.check @@ -1,8 +1,6 @@ --- 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 +Cyclic macro dependencies in tests/neg-macros/macros-in-same-project-4/Bar.scala. +Compilation stopped since no further progress can be made. + +To fix this, place macros in one set of files and their callers in another. + +Compiling with -Xprint-suspension gives more information. diff --git a/tests/neg-macros/macros-in-same-project-4/Bar.scala b/tests/neg-macros/macros-in-same-project-4/Bar.scala index de9ec400b49b..da86189bb201 100644 --- a/tests/neg-macros/macros-in-same-project-4/Bar.scala +++ b/tests/neg-macros/macros-in-same-project-4/Bar.scala @@ -1,8 +1,9 @@ +// nopos-error import scala.quoted._ object Bar { - Foo.myMacro() // error + Foo.myMacro() def hello()(given QuoteContext): Expr[Unit] = '{ println("Hello") } } diff --git a/tests/neg-macros/macros-in-same-project-5/Bar.scala b/tests/neg-macros/macros-in-same-project-5/Bar.scala new file mode 100644 index 000000000000..b3b6e792a984 --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-5/Bar.scala @@ -0,0 +1,10 @@ +import scala.quoted._ + +object Bar { + + Foo.myMacro() // error + + def aMacroImplementation(given QuoteContext): Expr[Unit] = Bar.hello() + + def hello()(given QuoteContext): Expr[Unit] = '{ println("Hello") } +} diff --git a/tests/neg-macros/macros-in-same-project-5/Foo.scala b/tests/neg-macros/macros-in-same-project-5/Foo.scala new file mode 100644 index 000000000000..b50e017cc1c5 --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-5/Foo.scala @@ -0,0 +1,8 @@ +import scala.quoted._ +import Bar.aMacroImplementation + +object Foo { + + inline def myMacro(): Unit = ${ aMacroImplementation } + +} diff --git a/tests/neg/macro-cycle1.scala b/tests/neg/macro-cycle1.scala index 9fca9ef3de12..7004812bec46 100644 --- a/tests/neg/macro-cycle1.scala +++ b/tests/neg/macro-cycle1.scala @@ -2,7 +2,7 @@ import scala.quoted.{Expr, QuoteContext} object Test { def fooImpl(given QuoteContext): Expr[Unit] = '{println("hi")} - inline def foo: Unit = ${fooImpl} // error + inline def foo: Unit = ${fooImpl} - foo + foo // error } From 06bdc8a43443c99744960b056f85275fd103ee49 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 Sep 2019 11:53:12 +0200 Subject: [PATCH 17/27] Improve error message --- compiler/src/dotty/tools/dotc/typer/FrontEnd.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 695a1d85f80a..24125965e4c0 100644 --- a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala +++ b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala @@ -122,12 +122,13 @@ class FrontEnd extends Phase { | | ${suspendedUnits.toList}%, % |""" + val enableXprintSuspensionHint = + if (ctx.settings.XprintSuspension.value) "" + else "\n\nCompiling with -Xprint-suspension gives more information." ctx.error(em"""Cyclic macro dependencies $where |Compilation stopped since no further progress can be made. | - |To fix this, place macros in one set of files and their callers in another. - | - |Compiling with -Xprint-suspension gives more information.""") + |To fix this, place macros in one set of files and their callers in another.$enableXprintSuspensionHint""") newUnits } From 10957fa3597d444b6eec5b9fe2088e0689776dc6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Sep 2019 16:08:11 +0200 Subject: [PATCH 18/27] Don't suspend if errors were reported. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 6f05a502db22..7bd9163ccc6e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1239,7 +1239,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val ctx1 = tastyreflect.MacroExpansion.context(inlinedFrom) val dependencies = macroDependencies(body) - if dependencies.nonEmpty then + if dependencies.nonEmpty && !ctx.reporter.errorsReported then for sym <- dependencies do if ctx.compilationUnit.source.file == sym.associatedFile then ctx.error(em"Cannot call macro $sym defined in the same source file", call.sourcePos) From 77632ce3121ddace92e0a32be00f6ad7d2d9e1a2 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 Sep 2019 17:30:42 +0200 Subject: [PATCH 19/27] Identify classes defined in current run --- .../src/dotty/tools/dotc/transform/Splicer.scala | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 9d8d283caa9c..e0006dd723f4 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -351,10 +351,9 @@ object Splicer { throw new StopInterpretation(sw.toString, pos) case ex: InvocationTargetException => ex.getTargetException match { - case targetException: NoClassDefFoundError => // FIXME check that the class is beeining compiled now - val className = targetException.getMessage + case ClassDefinedInCurrentRun(sym) => if (ctx.settings.XprintSuspension.value) - ctx.echo(i"suspension triggered by NoClassDefFoundError($className)", pos) // TODO improve message + ctx.echo(i"suspension triggered by a dependency on $sym", pos) ctx.compilationUnit.suspend() // this throws a SuspendException case targetException => val sw = new StringWriter() @@ -372,6 +371,14 @@ object Splicer { } } + private object ClassDefinedInCurrentRun { + def unapply(targetException: NoClassDefFoundError)(given ctx: Context): Option[Symbol] = { + val className = targetException.getMessage + val sym = ctx.base.staticRef(className.toTypeName).symbol + if (sym.isDefinedInCurrentRun) Some(sym) else None + } + } + /** List of classes of the parameters of the signature of `sym` */ private def paramsSig(sym: Symbol): List[Class[?]] = { def paramClass(param: Type): Class[?] = { From a53939c1974be0b85001fad4a9f70525892407ed Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 Sep 2019 17:41:53 +0200 Subject: [PATCH 20/27] Remove outdated comment --- compiler/src/dotty/tools/dotc/transform/Splicer.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index e0006dd723f4..0022c92b174e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -324,7 +324,7 @@ object Splicer { try classLoader.loadClass(name) catch { case _: ClassNotFoundException => - val msg = s"Could not find class $name in classpath$extraMsg" + val msg = s"Could not find class $name in classpath" throw new StopInterpretation(msg, pos) } @@ -332,12 +332,10 @@ object Splicer { try clazz.getMethod(name.toString, paramClasses: _*) catch { case _: NoSuchMethodException => - val msg = em"Could not find method ${clazz.getCanonicalName}.$name with parameters ($paramClasses%, %)$extraMsg" + val msg = em"Could not find method ${clazz.getCanonicalName}.$name with parameters ($paramClasses%, %)" throw new StopInterpretation(msg, pos) } - private def extraMsg = ". The most common reason for that is that you apply macros in the compilation run that defines them" - private def stopIfRuntimeException[T](thunk: => T, method: Method): T = try thunk catch { @@ -351,7 +349,7 @@ object Splicer { throw new StopInterpretation(sw.toString, pos) case ex: InvocationTargetException => ex.getTargetException match { - case ClassDefinedInCurrentRun(sym) => + case MissingClassDefinedInCurrentRun(sym) => if (ctx.settings.XprintSuspension.value) ctx.echo(i"suspension triggered by a dependency on $sym", pos) ctx.compilationUnit.suspend() // this throws a SuspendException @@ -371,7 +369,7 @@ object Splicer { } } - private object ClassDefinedInCurrentRun { + private object MissingClassDefinedInCurrentRun { def unapply(targetException: NoClassDefFoundError)(given ctx: Context): Option[Symbol] = { val className = targetException.getMessage val sym = ctx.base.staticRef(className.toTypeName).symbol From 69e8f19d4d1bff7f59c8d01e11dbda020f668947 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 Sep 2019 17:52:59 +0200 Subject: [PATCH 21/27] Handle suspended units in InteractiveDriver --- .../src/dotty/tools/dotc/interactive/InteractiveDriver.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala index 317fcf22728a..30f2fa00c859 100644 --- a/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala +++ b/compiler/src/dotty/tools/dotc/interactive/InteractiveDriver.scala @@ -155,7 +155,7 @@ class InteractiveDriver(val settings: List[String]) extends Driver { run.compileSources(List(source)) run.printSummary() - val unit = ctx.run.units.head + val unit = if ctx.run.units.nonEmpty then ctx.run.units.head else ctx.run.suspendedUnits.head val t = unit.tpdTree cleanup(t) myOpenedTrees(uri) = topLevelTrees(t, source) From bcc074d92a34f9574a55365e2cac1f511b00f477 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 2 Oct 2019 10:48:32 +0200 Subject: [PATCH 22/27] Test for legitimate NoClassDefFoundError in Splicer --- compiler/src/dotty/tools/dotc/transform/Splicer.scala | 7 +++++-- tests/neg-macros/macro-class-not-found-1.check | 8 ++++++++ tests/neg-macros/macro-class-not-found-1/Bar.scala | 5 +++++ tests/neg-macros/macro-class-not-found-1/Foo.scala | 10 ++++++++++ tests/neg-macros/macro-class-not-found-2.check | 8 ++++++++ tests/neg-macros/macro-class-not-found-2/Bar.scala | 5 +++++ tests/neg-macros/macro-class-not-found-2/Foo.scala | 10 ++++++++++ 7 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tests/neg-macros/macro-class-not-found-1.check create mode 100644 tests/neg-macros/macro-class-not-found-1/Bar.scala create mode 100644 tests/neg-macros/macro-class-not-found-1/Foo.scala create mode 100644 tests/neg-macros/macro-class-not-found-2.check create mode 100644 tests/neg-macros/macro-class-not-found-2/Bar.scala create mode 100644 tests/neg-macros/macro-class-not-found-2/Foo.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 0022c92b174e..6086ad9e7756 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -372,8 +372,11 @@ object Splicer { private object MissingClassDefinedInCurrentRun { def unapply(targetException: NoClassDefFoundError)(given ctx: Context): Option[Symbol] = { val className = targetException.getMessage - val sym = ctx.base.staticRef(className.toTypeName).symbol - if (sym.isDefinedInCurrentRun) Some(sym) else None + if (className eq null) None + else { + val sym = ctx.base.staticRef(className.toTypeName).symbol + if (sym.isDefinedInCurrentRun) Some(sym) else None + } } } diff --git a/tests/neg-macros/macro-class-not-found-1.check b/tests/neg-macros/macro-class-not-found-1.check new file mode 100644 index 000000000000..e619dff0c600 --- /dev/null +++ b/tests/neg-macros/macro-class-not-found-1.check @@ -0,0 +1,8 @@ +-- Error: tests/neg-macros/macro-class-not-found-1/Bar.scala:4:13 ------------------------------------------------------ +4 | Foo.myMacro() // error + | ^^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.NoClassDefFoundError + | at Foo$.aMacroImplementation(Foo.scala:8) + | + | This location is in code that was inlined at Bar.scala:4 diff --git a/tests/neg-macros/macro-class-not-found-1/Bar.scala b/tests/neg-macros/macro-class-not-found-1/Bar.scala new file mode 100644 index 000000000000..df9d90619500 --- /dev/null +++ b/tests/neg-macros/macro-class-not-found-1/Bar.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +object Bar { + Foo.myMacro() // error +} diff --git a/tests/neg-macros/macro-class-not-found-1/Foo.scala b/tests/neg-macros/macro-class-not-found-1/Foo.scala new file mode 100644 index 000000000000..f722502c5ecf --- /dev/null +++ b/tests/neg-macros/macro-class-not-found-1/Foo.scala @@ -0,0 +1,10 @@ +import scala.quoted._ + +object Foo { + + inline def myMacro(): Unit = ${ aMacroImplementation } + + def aMacroImplementation(given QuoteContext): Expr[Unit] = + throw new NoClassDefFoundError() + +} diff --git a/tests/neg-macros/macro-class-not-found-2.check b/tests/neg-macros/macro-class-not-found-2.check new file mode 100644 index 000000000000..90a5be49540f --- /dev/null +++ b/tests/neg-macros/macro-class-not-found-2.check @@ -0,0 +1,8 @@ +-- Error: tests/neg-macros/macro-class-not-found-2/Bar.scala:4:13 ------------------------------------------------------ +4 | Foo.myMacro() // error + | ^^^^^^^^^^^^^ + | Exception occurred while executing macro expansion. + | java.lang.NoClassDefFoundError: this.is.not.a.Class + | at Foo$.aMacroImplementation(Foo.scala:8) + | + | This location is in code that was inlined at Bar.scala:4 diff --git a/tests/neg-macros/macro-class-not-found-2/Bar.scala b/tests/neg-macros/macro-class-not-found-2/Bar.scala new file mode 100644 index 000000000000..df9d90619500 --- /dev/null +++ b/tests/neg-macros/macro-class-not-found-2/Bar.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +object Bar { + Foo.myMacro() // error +} diff --git a/tests/neg-macros/macro-class-not-found-2/Foo.scala b/tests/neg-macros/macro-class-not-found-2/Foo.scala new file mode 100644 index 000000000000..f6b68a2770cd --- /dev/null +++ b/tests/neg-macros/macro-class-not-found-2/Foo.scala @@ -0,0 +1,10 @@ +import scala.quoted._ + +object Foo { + + inline def myMacro(): Unit = ${ aMacroImplementation } + + def aMacroImplementation(given QuoteContext): Expr[Unit] = + throw new NoClassDefFoundError("this.is.not.a.Class") + +} From e44097426d97c45db8900e32a078f3c4ec9d52a3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 2 Oct 2019 11:03:23 +0200 Subject: [PATCH 23/27] Add regression tests --- tests/neg-macros/macros-in-same-project-1.scala | 12 ++++++++++++ tests/neg-macros/macros-in-same-project-2.scala | 13 +++++++++++++ tests/neg-macros/macros-in-same-project-6.check | 5 +++++ tests/neg-macros/macros-in-same-project-6/Bar.scala | 5 +++++ tests/neg-macros/macros-in-same-project-6/Foo.scala | 12 ++++++++++++ tests/pos-macros/macros-in-same-project-4/Bar.scala | 11 +++++++++++ tests/pos-macros/macros-in-same-project-4/Baz.scala | 8 ++++++++ 7 files changed, 66 insertions(+) create mode 100644 tests/neg-macros/macros-in-same-project-1.scala create mode 100644 tests/neg-macros/macros-in-same-project-2.scala create mode 100644 tests/neg-macros/macros-in-same-project-6.check create mode 100644 tests/neg-macros/macros-in-same-project-6/Bar.scala create mode 100644 tests/neg-macros/macros-in-same-project-6/Foo.scala create mode 100644 tests/pos-macros/macros-in-same-project-4/Bar.scala create mode 100644 tests/pos-macros/macros-in-same-project-4/Baz.scala diff --git a/tests/neg-macros/macros-in-same-project-1.scala b/tests/neg-macros/macros-in-same-project-1.scala new file mode 100644 index 000000000000..2ccbd6d1ce2c --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-1.scala @@ -0,0 +1,12 @@ + +import scala.quoted._ + +object Bar { + + myMacro() // error + + inline def myMacro(): Unit = ${ aMacroImplementation } + + def aMacroImplementation(given QuoteContext): Expr[Unit] = '{} + +} diff --git a/tests/neg-macros/macros-in-same-project-2.scala b/tests/neg-macros/macros-in-same-project-2.scala new file mode 100644 index 000000000000..0e4053dc1aa9 --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-2.scala @@ -0,0 +1,13 @@ + +import scala.quoted._ + +object Bar { + + myMacro() + + inline def myMacro(): Unit = myMacro2() // error + inline def myMacro2(): Unit = ${ aMacroImplementation } + + def aMacroImplementation(given QuoteContext): Expr[Unit] = '{} + +} diff --git a/tests/neg-macros/macros-in-same-project-6.check b/tests/neg-macros/macros-in-same-project-6.check new file mode 100644 index 000000000000..989cae136283 --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-6.check @@ -0,0 +1,5 @@ +-- Error: tests/neg-macros/macros-in-same-project-6/Bar.scala:4:13 ----------------------------------------------------- +4 | Foo.myMacro() // error + | ^^^^^^^^^^^^^ + | some error + | This location is in code that was inlined at Bar.scala:4 diff --git a/tests/neg-macros/macros-in-same-project-6/Bar.scala b/tests/neg-macros/macros-in-same-project-6/Bar.scala new file mode 100644 index 000000000000..df9d90619500 --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-6/Bar.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +object Bar { + Foo.myMacro() // error +} diff --git a/tests/neg-macros/macros-in-same-project-6/Foo.scala b/tests/neg-macros/macros-in-same-project-6/Foo.scala new file mode 100644 index 000000000000..7822c7fd234e --- /dev/null +++ b/tests/neg-macros/macros-in-same-project-6/Foo.scala @@ -0,0 +1,12 @@ +import scala.quoted._ + +object Foo { + + inline def myMacro(): Unit = ${ aMacroImplementation } + + def aMacroImplementation(given qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + error("some error", rootPosition) + throw new NoClassDefFoundError("Bar$") + } +} diff --git a/tests/pos-macros/macros-in-same-project-4/Bar.scala b/tests/pos-macros/macros-in-same-project-4/Bar.scala new file mode 100644 index 000000000000..31bd6c04b392 --- /dev/null +++ b/tests/pos-macros/macros-in-same-project-4/Bar.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Bar { + + inline def eqMacro(x: Foo, y: Foo): Boolean = ${ eqMacroExpr('x, 'y) } + def eqMacroExpr(x: Expr[Foo], y: Expr[Foo])(given QuoteContext): Expr[Boolean] = '{ $x == $y } + + inline def plusMacro(x: Foo, y: Foo): Foo = ${ eqPlusExpr('x, 'y) } + def eqPlusExpr(x: Expr[Foo], y: Expr[Foo])(given QuoteContext): Expr[Foo] = '{ new Foo($x.value + $y.value) } + +} diff --git a/tests/pos-macros/macros-in-same-project-4/Baz.scala b/tests/pos-macros/macros-in-same-project-4/Baz.scala new file mode 100644 index 000000000000..b82446e22d34 --- /dev/null +++ b/tests/pos-macros/macros-in-same-project-4/Baz.scala @@ -0,0 +1,8 @@ + + +object Foo { + def eq(x: Foo, y: Foo): Boolean = Bar.eqMacro(x, y) + def plus(x: Foo, y: Foo): Foo = Bar.plusMacro(x, y) +} + +class Foo(val value: Int) From 4317e7c5f10385b7055306db4360b516c2501a68 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 2 Oct 2019 13:29:46 +0200 Subject: [PATCH 24/27] Add output as macro classpath for suspended compilation --- compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala | 3 ++- project/scripts/cmdTests | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala index cbddcbaa6f05..86f4f7a7c7a6 100644 --- a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala +++ b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala @@ -21,6 +21,7 @@ object MacroClassLoader { private def makeMacroClassLoader(implicit ctx: Context): ClassLoader = trace("new macro class loader") { val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) - new java.net.URLClassLoader(urls, getClass.getClassLoader) + val out = ctx.settings.outputDir.value.jpath.toUri.toURL // to find classes in case of suspended compilation + new java.net.URLClassLoader(urls :+ out, getClass.getClassLoader) } } diff --git a/project/scripts/cmdTests b/project/scripts/cmdTests index 79aa4a14ceeb..542dcf3feecb 100755 --- a/project/scripts/cmdTests +++ b/project/scripts/cmdTests @@ -37,6 +37,10 @@ clear_out "$OUT" "$SBT" ";dotc -d $OUT/out.jar $SOURCE; dotc -decompile -classpath $OUT/out.jar -color:never $MAIN" > "$tmp" grep -qe "def main(args: scala.Array\[scala.Predef.String\]): scala.Unit =" "$tmp" +echo "testing sbt dotc with suspension" +clear_out "$OUT" +"$SBT" "dotc -d $OUT tests/pos-macros/macros-in-same-project-1/Bar.scala tests/pos-macros/macros-in-same-project-1/Foo.scala" > "$tmp" + # check that missing source file does not crash message rendering echo "testing that missing source file does not crash message rendering" clear_out "$OUT" From 6d2ac04fcfa51d4c11638fcbadfd9d5d7b3a93f3 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 2 Oct 2019 17:33:22 +0200 Subject: [PATCH 25/27] Add error when suspension has jar output To avoid a compiler crash. --- compiler/src/dotty/tools/backend/jvm/GenBCode.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 9c97e7747721..84ba12cfee7b 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -56,6 +56,10 @@ class GenBCode extends Phase { try super.runOn(units) finally myOutput match { case jar: JarArchive => + if (ctx.run.suspendedUnits.nonEmpty) + // If we close the jar the next run will not be able to write on the jar. + // But if we do not close it we cannot use it as part of the macro classpath of the suspended files. + ctx.error("Can not suspend and output to a jar at the same time. See suspension with -Xprint-suspension.") jar.close() case _ => } From c61d9dbe495cb194944ba21e95a702bb51097a81 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Thu, 3 Oct 2019 11:10:45 +0200 Subject: [PATCH 26/27] Add supspension to macro docs --- docs/docs/reference/metaprogramming/macros.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 0ad19aa737fa..147067fbcbe9 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -598,6 +598,19 @@ def defaultOfImpl(str: String): Expr[Any] = str match { // in a separate file val a: Int = defaultOf("int") val b: String = defaultOf("string") + ``` +### Defining a macro and using it in a single project +It is possible to define macros and use them in the same project as the implementation of +the macros does not depend on code in the file where it used. It might still depend +on types and quoted code that refers to the use-site file. + +To provide this functionality Dotty provides a transparent compilation mode were files that +expand try to expand a macro but fail because the macro has not been compiled yet are suspended. +If there are any suspended files when the compilation ends, the compiler will automatically restart +compilation of the suspended files using the output of the previous (partial) compilation as macro classpath. +In case all files are suspended due to cyclic dependencies the compilation will fail with an error. + + [More details](./macros-spec.md) From 69cdc2cee7c1f51e1677370ecb784c90679d4e22 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 30 Sep 2019 21:02:48 +0200 Subject: [PATCH 27/27] Collect macro expansion dependencies --- .../tools/dotc/core/MacroClassLoader.scala | 21 ++++++++++++++++--- .../tools/dotc/sbt/ExtractDependencies.scala | 4 ++++ .../macro-expansion-dependencies-1/Main.scala | 3 +++ .../changes/Macro.scala | 11 ++++++++++ .../changes/MacroCompileError.scala | 13 ++++++++++++ .../changes/MacroRuntimeError.scala | 11 ++++++++++ .../project/DottyInjectedPlugin.scala | 11 ++++++++++ .../project/plugins.sbt | 1 + .../macro-expansion-dependencies-1/test | 13 ++++++++++++ .../Macro.scala | 11 ++++++++++ .../macro-expansion-dependencies-2/Main.scala | 3 +++ .../changes/MacroRuntime.scala | 9 ++++++++ .../changes/MacroRuntimeCompileError.scala | 11 ++++++++++ .../changes/MacroRuntimeRuntimeError.scala | 9 ++++++++ .../project/DottyInjectedPlugin.scala | 11 ++++++++++ .../project/plugins.sbt | 1 + .../macro-expansion-dependencies-2/test | 13 ++++++++++++ .../Macro.scala | 14 +++++++++++++ .../macro-expansion-dependencies-3/Main.scala | 3 +++ .../changes/MacroRuntime.scala | 9 ++++++++ .../changes/MacroRuntimeCompileError.scala | 11 ++++++++++ .../changes/MacroRuntimeRuntimeError.scala | 9 ++++++++ .../project/DottyInjectedPlugin.scala | 11 ++++++++++ .../project/plugins.sbt | 1 + .../macro-expansion-dependencies-3/test | 13 ++++++++++++ .../testA_1.scala | 3 +++ .../testB_1.scala | 8 +++++++ .../testC_2.scala | 5 +++++ 28 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/Main.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/Macro.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroCompileError.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroRuntimeError.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/DottyInjectedPlugin.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Macro.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Main.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntime.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeCompileError.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeRuntimeError.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/DottyInjectedPlugin.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/test create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Macro.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Main.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntime.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeCompileError.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeRuntimeError.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/DottyInjectedPlugin.scala create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/test create mode 100644 tests/pos-macros/macro-with-runtime-dependencies/testA_1.scala create mode 100644 tests/pos-macros/macro-with-runtime-dependencies/testB_1.scala create mode 100644 tests/pos-macros/macro-with-runtime-dependencies/testC_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala index 86f4f7a7c7a6..867422467119 100644 --- a/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala +++ b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala @@ -9,7 +9,7 @@ import scala.collection.mutable object MacroClassLoader { /** A key to be used in a context property that caches the class loader used for macro expansion */ - private val MacroClassLoaderKey = new Property.Key[ClassLoader] + private val MacroClassLoaderKey = new Property.Key[MacroClassLoader] /** Get the macro class loader */ def fromContext(implicit ctx: Context): ClassLoader = @@ -19,9 +19,24 @@ object MacroClassLoader { def init(ctx: FreshContext): ctx.type = ctx.setProperty(MacroClassLoaderKey, makeMacroClassLoader(ctx)) - private def makeMacroClassLoader(implicit ctx: Context): ClassLoader = trace("new macro class loader") { + def loadedClasses(implicit ctx: Context): List[String] = + ctx.property(MacroClassLoaderKey).get.getLoadedClasses + + private def makeMacroClassLoader(implicit ctx: Context): MacroClassLoader = trace("new macro class loader") { val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL) val out = ctx.settings.outputDir.value.jpath.toUri.toURL // to find classes in case of suspended compilation - new java.net.URLClassLoader(urls :+ out, getClass.getClassLoader) + new MacroClassLoader(urls :+ out, getClass.getClassLoader) + } +} + +private class MacroClassLoader(urls: Array[java.net.URL], parent: ClassLoader) extends java.net.URLClassLoader(urls, parent) { + + private[this] val loadedClasses: mutable.SortedSet[String] = mutable.SortedSet.empty[String] + + def getLoadedClasses: List[String] = loadedClasses.toList + + override def loadClass(name: String): Class[?] = { + loadedClasses.add(name) + super.loadClass(name) } } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 73e8143ed5fa..65d0d28acb58 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -361,6 +361,10 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT case _ => } + if (tree.isDef && tree.symbol.isTopLevelClass) // FIXME do not add these dependencies if no macro was used in this file + for (name <- core.MacroClassLoader.loadedClasses) + ctx.sbtCallback.classDependency(name, tree.symbol.showFullName, DependencyContext.LocalDependencyByInheritance) + tree match { case Inlined(call, _, _) if !call.isEmpty => // The inlined call is normally ignored by TreeTraverser but we need to diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/Main.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/Main.scala new file mode 100644 index 000000000000..4d904bf6177f --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/Main.scala @@ -0,0 +1,3 @@ +object Main extends App { + Macro.f() +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/Macro.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/Macro.scala new file mode 100644 index 000000000000..1540b3faabee --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/Macro.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macro { + + inline def f(): Unit = ${ macroImplementation } + + def macroImplementation(given QuoteContext): Expr[Unit] = { + '{ println("Implementation in Macro") } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroCompileError.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroCompileError.scala new file mode 100644 index 000000000000..cd91962130da --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroCompileError.scala @@ -0,0 +1,13 @@ +import scala.quoted._ + +object Macro { + + inline def f(): Unit = ${ macroImplementation } + + def macroImplementation(given qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + error("some error", rootPosition) + '{ println("Implementation in MacroCompileError") } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroRuntimeError.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroRuntimeError.scala new file mode 100644 index 000000000000..23a4e6665154 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/changes/MacroRuntimeError.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macro { + + inline def f(): Unit = ${ macroImplementation } + + def macroImplementation(given qctx: QuoteContext): Expr[Unit] = { + '{ ??? } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/plugins.sbt b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test new file mode 100644 index 000000000000..45a1c3f4fb8f --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-1/test @@ -0,0 +1,13 @@ +$ copy-file changes/Macro.scala Macro.scala +> run + +# use an implemntation of the macro that emits a compile time error +$ copy-file changes/MacroCompileError.scala Macro.scala +-> compile + +$ copy-file changes/Macro.scala Macro.scala +> clean +> compile + +$ copy-file changes/MacroRuntimeError.scala Macro.scala +-> run diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Macro.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Macro.scala new file mode 100644 index 000000000000..138a629f8761 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Macro.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object Macro { + + inline def f(): Unit = ${ macroImplementation } + + def macroImplementation(given QuoteContext): Expr[Unit] = + MacroRuntime.impl() + + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Main.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Main.scala new file mode 100644 index 000000000000..4d904bf6177f --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/Main.scala @@ -0,0 +1,3 @@ +object Main extends App { + Macro.f() +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntime.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntime.scala new file mode 100644 index 000000000000..011ac9885f86 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntime.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object MacroRuntime { + + def impl()(given QuoteContext): Expr[Unit] = { + '{ println("Implementation in Macro") } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeCompileError.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeCompileError.scala new file mode 100644 index 000000000000..d06584e0023e --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeCompileError.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object MacroRuntime { + + def impl()(given qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + error("some error", rootPosition) + '{ println("Implementation in MacroCompileError") } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeRuntimeError.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeRuntimeError.scala new file mode 100644 index 000000000000..dcda4e276c3f --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/changes/MacroRuntimeRuntimeError.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object MacroRuntime { + + def impl()(given qctx: QuoteContext): Expr[Unit] = { + '{ ??? } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/plugins.sbt b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/test b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/test new file mode 100644 index 000000000000..bf2c8effa3bd --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-2/test @@ -0,0 +1,13 @@ +$ copy-file changes/MacroRuntime.scala MacroRuntime.scala +> run + +# use an implemntation of the macro that emits a compile time error +$ copy-file changes/MacroRuntimeCompileError.scala MacroRuntime.scala +-> compile + +$ copy-file changes/MacroRuntime.scala MacroRuntime.scala +> clean +> compile + +$ copy-file changes/MacroRuntimeRuntimeError.scala MacroRuntime.scala +-> run diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Macro.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Macro.scala new file mode 100644 index 000000000000..208f52af7cdd --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Macro.scala @@ -0,0 +1,14 @@ +import scala.quoted._ + +object Macro { + + inline def f(): Unit = ${ macroImplementation } + + def macroImplementation(given qctx: QuoteContext): Expr[Unit] = { + val clazz = Class.forName("MacroRuntime") + val method = clazz.getMethod("impl", classOf[QuoteContext]) + method.invoke(null, qctx).asInstanceOf[Expr[Unit]] + } + + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Main.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Main.scala new file mode 100644 index 000000000000..4d904bf6177f --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/Main.scala @@ -0,0 +1,3 @@ +object Main extends App { + Macro.f() +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntime.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntime.scala new file mode 100644 index 000000000000..011ac9885f86 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntime.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object MacroRuntime { + + def impl()(given QuoteContext): Expr[Unit] = { + '{ println("Implementation in Macro") } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeCompileError.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeCompileError.scala new file mode 100644 index 000000000000..d06584e0023e --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeCompileError.scala @@ -0,0 +1,11 @@ +import scala.quoted._ + +object MacroRuntime { + + def impl()(given qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty._ + error("some error", rootPosition) + '{ println("Implementation in MacroCompileError") } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeRuntimeError.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeRuntimeError.scala new file mode 100644 index 000000000000..dcda4e276c3f --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/changes/MacroRuntimeRuntimeError.scala @@ -0,0 +1,9 @@ +import scala.quoted._ + +object MacroRuntime { + + def impl()(given qctx: QuoteContext): Expr[Unit] = { + '{ ??? } + } + +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/DottyInjectedPlugin.scala b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/DottyInjectedPlugin.scala new file mode 100644 index 000000000000..fb946c4b8c61 --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/DottyInjectedPlugin.scala @@ -0,0 +1,11 @@ +import sbt._ +import Keys._ + +object DottyInjectedPlugin extends AutoPlugin { + override def requires = plugins.JvmPlugin + override def trigger = allRequirements + + override val projectSettings = Seq( + scalaVersion := sys.props("plugin.scalaVersion") + ) +} diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/plugins.sbt b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/test b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/test new file mode 100644 index 000000000000..bf2c8effa3bd --- /dev/null +++ b/sbt-dotty/sbt-test/source-dependencies/macro-expansion-dependencies-3/test @@ -0,0 +1,13 @@ +$ copy-file changes/MacroRuntime.scala MacroRuntime.scala +> run + +# use an implemntation of the macro that emits a compile time error +$ copy-file changes/MacroRuntimeCompileError.scala MacroRuntime.scala +-> compile + +$ copy-file changes/MacroRuntime.scala MacroRuntime.scala +> clean +> compile + +$ copy-file changes/MacroRuntimeRuntimeError.scala MacroRuntime.scala +-> run diff --git a/tests/pos-macros/macro-with-runtime-dependencies/testA_1.scala b/tests/pos-macros/macro-with-runtime-dependencies/testA_1.scala new file mode 100644 index 000000000000..cd87142931ef --- /dev/null +++ b/tests/pos-macros/macro-with-runtime-dependencies/testA_1.scala @@ -0,0 +1,3 @@ +object TestA { + def testA(): Unit = println(9) +} diff --git a/tests/pos-macros/macro-with-runtime-dependencies/testB_1.scala b/tests/pos-macros/macro-with-runtime-dependencies/testB_1.scala new file mode 100644 index 000000000000..c1c310b4d3b1 --- /dev/null +++ b/tests/pos-macros/macro-with-runtime-dependencies/testB_1.scala @@ -0,0 +1,8 @@ +import scala.quoted._ + +object TestB { + def testB()(given QuoteContext): Expr[Int] = { + TestA.testA() + '{5} + } +} diff --git a/tests/pos-macros/macro-with-runtime-dependencies/testC_2.scala b/tests/pos-macros/macro-with-runtime-dependencies/testC_2.scala new file mode 100644 index 000000000000..73015bb5eee4 --- /dev/null +++ b/tests/pos-macros/macro-with-runtime-dependencies/testC_2.scala @@ -0,0 +1,5 @@ +object TestC { + inline def f() = ${ TestB.testB() } + + f() +}