diff --git a/community-build/community-projects/scalatest b/community-build/community-projects/scalatest index 6a53948f8ad7..1e4715ad7323 160000 --- a/community-build/community-projects/scalatest +++ b/community-build/community-projects/scalatest @@ -1 +1 @@ -Subproject commit 6a53948f8ad733896387fceec860cc6a04e068d8 +Subproject commit 1e4715ad7323f1eb13c7f6e799462fb47b6d212c 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 _ => } diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index bce33813fa70..ea0faa19ce6d 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) { @@ -31,10 +32,22 @@ 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 + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"suspended: $this") + 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..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} @@ -30,23 +31,33 @@ 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 suspendedUnits = run.suspendedUnits.toList + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"compiling suspended $suspendedUnits%, %") + val run1 = compiler.newRun + for unit <- suspendedUnits do unit.suspended = false + run1.compileUnits(suspendedUnits) + 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/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/core/MacroClassLoader.scala b/compiler/src/dotty/tools/dotc/core/MacroClassLoader.scala index cbddcbaa6f05..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,8 +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) - 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 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/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) 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/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index bd988422541f..6086ad9e7756 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 @@ -322,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) } @@ -330,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 { @@ -348,21 +348,38 @@ 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 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 + 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) } + private object MissingClassDefinedInCurrentRun { + def unapply(targetException: NoClassDefFoundError)(given ctx: Context): Option[Symbol] = { + val className = targetException.getMessage + if (className eq null) None + else { + 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[?] = { diff --git a/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala b/compiler/src/dotty/tools/dotc/typer/FrontEnd.scala index 520c455b0596..24125965e4c0 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,23 @@ 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}%, % + |""" + 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.$enableXprintSuspensionHint""") + 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..7bd9163ccc6e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -1237,48 +1237,40 @@ 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 && !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) + if (ctx.settings.XprintSuspension.value) + ctx.echo(i"suspension triggered by macro call to ${sym.showLocated} in ${sym.associatedFile}", call.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. * 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) @@ -1290,6 +1282,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => foldOver(syms, tree) } - }.apply(Set.empty, tree) + }.apply(Nil, tree) } 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/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" 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/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) 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" 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/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/macros-in-same-project-1/Bar.scala b/tests/neg-macros/macro-class-not-found-1/Bar.scala similarity index 63% rename from tests/neg-macros/macros-in-same-project-1/Bar.scala rename to tests/neg-macros/macro-class-not-found-1/Bar.scala index 2924e2b2411a..df9d90619500 100644 --- a/tests/neg-macros/macros-in-same-project-1/Bar.scala +++ b/tests/neg-macros/macro-class-not-found-1/Bar.scala @@ -1,3 +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") + +} 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.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.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-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-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/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-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/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 +} diff --git a/tests/neg/macro-cycle1.scala b/tests/neg/macro-cycle1.scala new file mode 100644 index 000000000000..7004812bec46 --- /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 // error +} 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() +} 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 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) diff --git a/tests/run-macros/macros-in-same-project1/Foo.scala b/tests/run-macros/macros-in-same-project1/Foo.scala new file mode 100644 index 000000000000..524ccde401f8 --- /dev/null +++ b/tests/run-macros/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/macros-in-same-project1/Test.scala b/tests/run-macros/macros-in-same-project1/Test.scala new file mode 100644 index 000000000000..81c22b90899a --- /dev/null +++ b/tests/run-macros/macros-in-same-project1/Test.scala @@ -0,0 +1,3 @@ +object Test extends App { + Foo.myMacro() +} \ No newline at end of file diff --git a/tests/run-macros/reflect-inline/assert_1.scala b/tests/run-macros/reflect-inline/assert_1.scala index 693a185eee32..d408236f5fe2 100644 --- a/tests/run-macros/reflect-inline/assert_1.scala +++ b/tests/run-macros/reflect-inline/assert_1.scala @@ -5,7 +5,7 @@ object api { ${ stripImpl(x) } private def stripImpl(x: String)(given qctx: QuoteContext): Expr[String] = - Expr(x.stripMargin) + Expr(augmentString(x).stripMargin) inline def typeChecks(inline x: String): Boolean = ${ typeChecksImpl(scala.compiletime.testing.typeChecks(x)) } diff --git a/tests/run-macros/reflect-inline/test_2.scala b/tests/run-macros/reflect-inline/test_2.scala index f99fbacbf0d7..2f36d7b52214 100644 --- a/tests/run-macros/reflect-inline/test_2.scala +++ b/tests/run-macros/reflect-inline/test_2.scala @@ -7,4 +7,4 @@ object Test { assert(scala.compiletime.testing.typeChecks("|1 + 1".stripMargin)) assert(("|3 + " + a).stripMargin == "3 + 5") } -} \ No newline at end of file +} 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