From 2a98355c64ab37509e4c5b333cbdc5ae51dce5bd Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Fri, 7 Feb 2014 22:52:44 +0100 Subject: [PATCH 1/5] Test for missing dependencies of macro arguments Add a test which shows the problem of not properly capturing dependencies of macro arguments. --- .../macro-arg-dep/macro-client/Client.scala | 5 ++++ .../macro-arg-dep/macro-client/Foo.scala | 5 ++++ .../macro-client/changes/Foo.scala | 3 ++ .../macro-provider/Provider.scala | 12 ++++++++ .../macro-arg-dep/project/build.scala | 29 +++++++++++++++++++ .../source-dependencies/macro-arg-dep/test | 15 ++++++++++ 6 files changed, 69 insertions(+) create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Client.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/changes/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-provider/Provider.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep/project/build.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep/test diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Client.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Client.scala new file mode 100644 index 000000000..94ad4bcc8 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Client.scala @@ -0,0 +1,5 @@ +package macro + +object Client { + Provider.printTree(Foo.str) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Foo.scala new file mode 100644 index 000000000..1908f0673 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/Foo.scala @@ -0,0 +1,5 @@ +package macro + +object Foo { + def str: String = "abc" +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/changes/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/changes/Foo.scala new file mode 100644 index 000000000..e3deb0f43 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-client/changes/Foo.scala @@ -0,0 +1,3 @@ +package macro +object Foo { +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-provider/Provider.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-provider/Provider.scala new file mode 100644 index 000000000..facc4a468 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/macro-provider/Provider.scala @@ -0,0 +1,12 @@ +package macro +import scala.language.experimental.macros +import scala.reflect.macros._ + +object Provider { + def printTree(arg: Any) = macro printTreeImpl + def printTreeImpl(c: Context)(arg: c.Expr[Any]): c.Expr[String] = { + val argStr = arg.tree.toString + val literalStr = c.universe.Literal(c.universe.Constant(argStr)) + c.Expr[String](literalStr) + } +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/project/build.scala new file mode 100644 index 000000000..a5382240f --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/project/build.scala @@ -0,0 +1,29 @@ +import sbt._ +import Keys._ + +object build extends Build { + val defaultSettings = Seq( + libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _ ), + incOptions := incOptions.value.withNameHashing(true) + ) + + lazy val root = Project( + base = file("."), + id = "macro", + aggregate = Seq(macroProvider, macroClient), + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroProvider = Project( + base = file("macro-provider"), + id = "macro-provider", + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroClient = Project( + base = file("macro-client"), + id = "macro-client", + dependencies = Seq(macroProvider), + settings = Defaults.defaultSettings ++ defaultSettings + ) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test new file mode 100644 index 000000000..e6486db5e --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test @@ -0,0 +1,15 @@ +> compile + +# remove `Foo.str` which is an argument to a macro that incremental compiler doesn't see in +# Client.scala because macro has been already expanded + +$ copy-file macro-client/changes/Foo.scala macro-client/Foo.scala + +# we should recompile Foo.scala first and then fail to compile Client.scala due to missing +# `Foo.str`; however recompilation of Client.scala is never triggered due to missing +# dependency +-> macro-client/compile + +> clean + +-> compile From 70fecfe767f5d9a771a546e86238b3d2c06250a4 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Tue, 4 Mar 2014 18:21:44 +0100 Subject: [PATCH 2/5] Record dependencies on macro arguments Macros take arguments as trees and return some other trees; both of them have dependencies but we see trees only after expansion and recorded only those dependencies. This commit solves this problem by looking into the attachments of the trees that are supposed to contain originals of macro expansions and recording dependencies of the macro before its expansion. --- .../src/main/scala/xsbt/Compat.scala | 38 ++++++++++++ .../src/main/scala/xsbt/Dependency.scala | 2 + .../main/scala/xsbt/ExtractUsedNames.scala | 62 ++++++++++++------- 3 files changed, 78 insertions(+), 24 deletions(-) diff --git a/compile/interface/src/main/scala/xsbt/Compat.scala b/compile/interface/src/main/scala/xsbt/Compat.scala index 17a1a8f6b..d92ba6e73 100644 --- a/compile/interface/src/main/scala/xsbt/Compat.scala +++ b/compile/interface/src/main/scala/xsbt/Compat.scala @@ -91,4 +91,42 @@ abstract class Compat private[this] def sourceCompatibilityOnly: Nothing = throw new RuntimeException("For source compatibility only: should not get here.") private[this] final implicit def miscCompat(n: AnyRef): MiscCompat = new MiscCompat + + object MacroExpansionOf { + def unapply(tree: Tree): Option[Tree] = { + + // MacroExpansionAttachment (MEA) compatibility for 2.8.x and 2.9.x + object Compat { + class MacroExpansionAttachment(val original: Tree) + + // Trees have no attachments in 2.8.x and 2.9.x + implicit def withAttachments(tree: Tree): WithAttachments = new WithAttachments(tree) + class WithAttachments(val tree: Tree) { + object EmptyAttachments { + def all = Set.empty[Any] + } + val attachments = EmptyAttachments + } + } + import Compat._ + + locally { + // Wildcard imports are necessary since 2.8.x and 2.9.x don't have `MacroExpansionAttachment` at all + import global._ // this is where MEA lives in 2.10.x + + // `original` has been renamed to `expandee` in 2.11.x + implicit def withExpandee(att: MacroExpansionAttachment): WithExpandee = new WithExpandee(att) + class WithExpandee(att: MacroExpansionAttachment) { + def expandee: Tree = att.original + } + + locally { + import analyzer._ // this is where MEA lives in 2.11.x + tree.attachments.all.collect { + case att: MacroExpansionAttachment => att.expandee + } headOption + } + } + } + } } diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index e9b482ef9..b8a55c8a9 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -146,6 +146,8 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile deps.foreach(addDependency) case Template(parents, self, body) => traverseTrees(body) + case MacroExpansionOf(original) => + this.traverse(original) case other => () } super.traverse(tree) diff --git a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala index 9f89a3459..6ab01c9eb 100644 --- a/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala +++ b/compile/interface/src/main/scala/xsbt/ExtractUsedNames.scala @@ -38,7 +38,7 @@ import scala.tools.nsc._ * The tree walking algorithm walks into TypeTree.original explicitly. * */ -class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) { +class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) extends Compat { import global._ def extract(unit: CompilationUnit): Set[String] = { @@ -53,30 +53,44 @@ class ExtractUsedNames[GlobalType <: CallbackGlobal](val global: GlobalType) { val symbolNameAsString = symbol.name.decode.trim namesBuffer += symbolNameAsString } - def handleTreeNode(node: Tree): Unit = node match { - case _: DefTree | _: Template => () - // turns out that Import node has a TermSymbol associated with it - // I (Grzegorz) tried to understand why it's there and what does it represent but - // that logic was introduced in 2005 without any justification I'll just ignore the - // import node altogether and just process the selectors in the import node - case Import(_, selectors: List[ImportSelector]) => - def usedNameInImportSelector(name: Name): Unit = - if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString - selectors foreach { selector => - usedNameInImportSelector(selector.name) - usedNameInImportSelector(selector.rename) - } - // TODO: figure out whether we should process the original tree or walk the type - // the argument for processing the original tree: we process what user wrote - // the argument for processing the type: we catch all transformations that typer applies - // to types but that might be a bad thing because it might expand aliases eagerly which - // not what we need - case t: TypeTree if t.original != null => - t.original.foreach(handleTreeNode) - case t if t.hasSymbol && eligibleAsUsedName(t.symbol) => - addSymbol(t.symbol) - case _ => () + + def handleTreeNode(node: Tree): Unit = { + def handleMacroExpansion(original: Tree): Unit = original.foreach(handleTreeNode) + + def handleClassicTreeNode(node: Tree): Unit = node match { + case _: DefTree | _: Template => () + // turns out that Import node has a TermSymbol associated with it + // I (Grzegorz) tried to understand why it's there and what does it represent but + // that logic was introduced in 2005 without any justification I'll just ignore the + // import node altogether and just process the selectors in the import node + case Import(_, selectors: List[ImportSelector]) => + def usedNameInImportSelector(name: Name): Unit = + if ((name != null) && (name != nme.WILDCARD)) namesBuffer += name.toString + selectors foreach { selector => + usedNameInImportSelector(selector.name) + usedNameInImportSelector(selector.rename) + } + // TODO: figure out whether we should process the original tree or walk the type + // the argument for processing the original tree: we process what user wrote + // the argument for processing the type: we catch all transformations that typer applies + // to types but that might be a bad thing because it might expand aliases eagerly which + // not what we need + case t: TypeTree if t.original != null => + t.original.foreach(handleTreeNode) + case t if t.hasSymbol && eligibleAsUsedName(t.symbol) => + addSymbol(t.symbol) + case _ => () + } + + node match { + case MacroExpansionOf(original) => + handleClassicTreeNode(node) + handleMacroExpansion(original) + case _ => + handleClassicTreeNode(node) + } } + tree.foreach(handleTreeNode) namesBuffer.toSet } From b21e47536453f606682e1b24ba2182e5c01995f8 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 19 Mar 2014 22:21:29 +0100 Subject: [PATCH 3/5] Improve unit testing compiler It was not possible to make `ScalaCompilerForUnitTesting` compile several files in different runs, which means that it was not possible to compile and use a macro in a test case, since macros cannot be used in the same compilation run that defines them. This commit allows a test case to provide multiple grouped snippets of code that will be compiled in separate runs. For instance : List(Map(, ), Map()) Here, and will be compiled together, and then will be compiled, and will be able to use symbols defined in or . --- .../xsbt/ScalaCompilerForUnitTesting.scala | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala index 5362b1ca6..cb10d1d53 100644 --- a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala +++ b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala @@ -53,15 +53,19 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { * dependencies between snippets. Source code snippets are identified by symbols. Each symbol should * be associated with one snippet only. * + * Snippets can be grouped to be compiled together in the same compiler run. This is + * useful to compile macros, which cannot be used in the same compilation run that + * defines them. + * * Symbols are used to express extracted dependencies between source code snippets. This way we have * file system-independent way of testing dependencies between source code "files". */ - def extractDependenciesFromSrcs(srcs: (Symbol, String)*): ExtractedSourceDependencies = { - val (symbolsForSrcs, rawSrcs) = srcs.unzip - assert(symbolsForSrcs.distinct.size == symbolsForSrcs.size, - s"Duplicate symbols for srcs detected: $symbolsForSrcs") - val (tempSrcFiles, testCallback) = compileSrcs(rawSrcs: _*) - val fileToSymbol = (tempSrcFiles zip symbolsForSrcs).toMap + def extractDependenciesFromSrcs(srcs: List[Map[Symbol, String]]): ExtractedSourceDependencies = { + val rawGroupedSrcs = srcs.map(_.values.toList).toList + val symbols = srcs.map(_.keys).flatten + val (tempSrcFiles, testCallback) = compileSrcs(rawGroupedSrcs) + val fileToSymbol = (tempSrcFiles zip symbols).toMap + val memberRefFileDeps = testCallback.sourceDependencies collect { // false indicates that those dependencies are not introduced by inheritance case (target, src, false) => (src, target) @@ -82,40 +86,64 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { // convert all collections to immutable variants multiMap.toMap.mapValues(_.toSet).withDefaultValue(Set.empty) } + ExtractedSourceDependencies(pairsToMultiMap(memberRefDeps), pairsToMultiMap(inheritanceDeps)) } + def extractDependenciesFromSrcs(srcs: (Symbol, String)*): ExtractedSourceDependencies = { + val symbols = srcs.map(_._1) + assert(symbols.distinct.size == symbols.size, + s"Duplicate symbols for srcs detected: $symbols") + extractDependenciesFromSrcs(List(srcs.toMap)) + } + /** - * Compiles given source code snippets written to a temporary files. Each snippet is + * Compiles given source code snippets written to temporary files. Each snippet is * written to a separate temporary file. * + * Snippets can be grouped to be compiled together in the same compiler run. This is + * useful to compile macros, which cannot be used in the same compilation run that + * defines them. + * * The sequence of temporary files corresponding to passed snippets and analysis * callback is returned as a result. */ - private def compileSrcs(srcs: String*): (Seq[File], TestCallback) = { + private def compileSrcs(groupedSrcs: List[List[String]]): (Seq[File], TestCallback) = { withTemporaryDirectory { temp => val analysisCallback = new TestCallback(nameHashing) val classesDir = new File(temp, "classes") classesDir.mkdir() - val compiler = prepareCompiler(classesDir, analysisCallback) - val run = new compiler.Run - val srcFiles = srcs.toSeq.zipWithIndex map { case (src, i) => - val fileName = s"Test_$i.scala" - prepareSrcFile(temp, fileName, src) + + val compiler = prepareCompiler(classesDir, analysisCallback, classesDir.toString) + + val files = for((compilationUnit, unitId) <- groupedSrcs.zipWithIndex) yield { + val run = new compiler.Run + val srcFiles = compilationUnit.toSeq.zipWithIndex map { case (src, i) => + val fileName = s"Test-$unitId-$i.scala" + prepareSrcFile(temp, fileName, src) + } + val srcFilePaths = srcFiles.map(srcFile => srcFile.getAbsolutePath).toList + + run.compile(srcFilePaths) + + srcFilePaths.foreach(f => new File(f).delete) + srcFiles } - val srcFilePaths = srcFiles.map(srcFile => srcFile.getAbsolutePath).toList - run.compile(srcFilePaths) - (srcFiles, analysisCallback) + (files.flatten.toSeq, analysisCallback) } } + private def compileSrcs(srcs: String*): (Seq[File], TestCallback) = { + compileSrcs(List(srcs.toList)) + } + private def prepareSrcFile(baseDir: File, fileName: String, src: String): File = { val srcFile = new File(baseDir, fileName) sbt.IO.write(srcFile, src) srcFile } - private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback): CachedCompiler0#Compiler = { + private def prepareCompiler(outputDir: File, analysisCallback: AnalysisCallback, classpath: String = "."): CachedCompiler0#Compiler = { val args = Array.empty[String] object output extends SingleOutput { def outputDirectory: File = outputDir @@ -123,6 +151,7 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { val weakLog = new WeakLog(ConsoleLogger(), ConsoleReporter) val cachedCompiler = new CachedCompiler0(args, output, weakLog, false) val settings = cachedCompiler.settings + settings.classpath.value = classpath settings.usejavacp.value = true val scalaReporter = new ConsoleReporter(settings) val delegatingReporter = DelegatingReporter(settings, ConsoleReporter) From 133ba07eb81f07f5b2acb85446bfbcbb348da4a8 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 19 Mar 2014 22:23:25 +0100 Subject: [PATCH 4/5] Unit test for dependency extraction from macro applications Add a unit test which checks whether we capture dependencies introduced by arguments to macros. Those dependencies are special because macros get expanded during type checking and arguments to macros are not visible during regular tree walk. --- .../scala/xsbt/DependencySpecification.scala | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/compile/interface/src/test/scala/xsbt/DependencySpecification.scala b/compile/interface/src/test/scala/xsbt/DependencySpecification.scala index 040ad1d6e..ec2f76ed9 100644 --- a/compile/interface/src/test/scala/xsbt/DependencySpecification.scala +++ b/compile/interface/src/test/scala/xsbt/DependencySpecification.scala @@ -65,6 +65,19 @@ class DependencySpecification extends Specification { inheritance('D) === Set('A, 'C) } + "Extracted source dependencies from macro arguments" in { + val sourceDependencies = extractSourceDependenciesFromMacroArgument + val memberRef = sourceDependencies.memberRef + val inheritance = sourceDependencies.inheritance + + memberRef('A) === Set('B, 'C) + inheritance('A) === Set.empty + memberRef('B) === Set.empty + inheritance('B) === Set.empty + memberRef('C) === Set.empty + inheritance('C) === Set.empty + } + private def extractSourceDependenciesPublic: ExtractedSourceDependencies = { val srcA = "class A" val srcB = "class B extends D[A]" @@ -109,4 +122,25 @@ class DependencySpecification extends Specification { compilerForTesting.extractDependenciesFromSrcs('A -> srcA, 'B -> srcB, 'C -> srcC, 'D -> srcD) sourceDependencies } + + private def extractSourceDependenciesFromMacroArgument: ExtractedSourceDependencies = { + val srcA = "class A { println(B.printTree(C.foo)) }" + val srcB = """ + |import scala.language.experimental.macros + |import scala.reflect.macros._ + |object B { + | def printTree(arg: Any) = macro printTreeImpl + | def printTreeImpl(c: Context)(arg: c.Expr[Any]): c.Expr[String] = { + | val argStr = arg.tree.toString + | val literalStr = c.universe.Literal(c.universe.Constant(argStr)) + | c.Expr[String](literalStr) + | } + |}""".stripMargin + val srcC = "object C { val foo = 1 }" + + val compilerForTesting = new ScalaCompilerForUnitTesting(nameHashing = true) + val sourceDependencies = + compilerForTesting.extractDependenciesFromSrcs(List(Map('B -> srcB, 'C -> srcC), Map('A -> srcA))) + sourceDependencies + } } From 04e226bd598122f96fe39132fa4c1d9d8b35f523 Mon Sep 17 00:00:00 2001 From: Martin Duhem Date: Wed, 19 Mar 2014 20:57:20 +0100 Subject: [PATCH 5/5] Add scripted test for nested macros Add test analogous to source-dependencies/macro-arg-dep but check if dependencies of nested macro applications are handled properly. Nested macro applications are tricky because we have to look into original (before macro expansion) trees recursively. This test verifies that. --- .../macro-client/Client.scala | 5 ++++ .../macro-client/Foo.scala | 5 ++++ .../macro-client/changes/Foo.scala | 3 ++ .../macro-provider/Provider.scala | 12 ++++++++ .../macro-arg-dep-nested/project/build.scala | 29 +++++++++++++++++++ .../macro-arg-dep-nested/test | 13 +++++++++ .../source-dependencies/macro-arg-dep/test | 7 ++--- 7 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Client.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/changes/Foo.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-provider/Provider.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/project/build.scala create mode 100644 sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/test diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Client.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Client.scala new file mode 100644 index 000000000..d80fd559e --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Client.scala @@ -0,0 +1,5 @@ +package macro + +object Client { + Provider.printTree(Provider.printTree(Foo.str)) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Foo.scala new file mode 100644 index 000000000..1908f0673 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/Foo.scala @@ -0,0 +1,5 @@ +package macro + +object Foo { + def str: String = "abc" +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/changes/Foo.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/changes/Foo.scala new file mode 100644 index 000000000..e3deb0f43 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-client/changes/Foo.scala @@ -0,0 +1,3 @@ +package macro +object Foo { +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-provider/Provider.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-provider/Provider.scala new file mode 100644 index 000000000..facc4a468 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/macro-provider/Provider.scala @@ -0,0 +1,12 @@ +package macro +import scala.language.experimental.macros +import scala.reflect.macros._ + +object Provider { + def printTree(arg: Any) = macro printTreeImpl + def printTreeImpl(c: Context)(arg: c.Expr[Any]): c.Expr[String] = { + val argStr = arg.tree.toString + val literalStr = c.universe.Literal(c.universe.Constant(argStr)) + c.Expr[String](literalStr) + } +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/project/build.scala b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/project/build.scala new file mode 100644 index 000000000..a5382240f --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/project/build.scala @@ -0,0 +1,29 @@ +import sbt._ +import Keys._ + +object build extends Build { + val defaultSettings = Seq( + libraryDependencies <+= scalaVersion("org.scala-lang" % "scala-reflect" % _ ), + incOptions := incOptions.value.withNameHashing(true) + ) + + lazy val root = Project( + base = file("."), + id = "macro", + aggregate = Seq(macroProvider, macroClient), + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroProvider = Project( + base = file("macro-provider"), + id = "macro-provider", + settings = Defaults.defaultSettings ++ defaultSettings + ) + + lazy val macroClient = Project( + base = file("macro-client"), + id = "macro-client", + dependencies = Seq(macroProvider), + settings = Defaults.defaultSettings ++ defaultSettings + ) +} diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/test b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/test new file mode 100644 index 000000000..231939418 --- /dev/null +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep-nested/test @@ -0,0 +1,13 @@ +> compile + +# remove `Foo.str` which is an argument to a macro +# (this macro itself that is an argument to another macro) +$ copy-file macro-client/changes/Foo.scala macro-client/Foo.scala + +# we should recompile Foo.scala first and then fail to compile Client.scala due to missing +# `Foo.str` +-> macro-client/compile + +> clean + +-> compile diff --git a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test index e6486db5e..183aa6c49 100644 --- a/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test +++ b/sbt/src/sbt-test/source-dependencies/macro-arg-dep/test @@ -1,13 +1,10 @@ > compile -# remove `Foo.str` which is an argument to a macro that incremental compiler doesn't see in -# Client.scala because macro has been already expanded - +# remove `Foo.str` which is an argument to a macro $ copy-file macro-client/changes/Foo.scala macro-client/Foo.scala # we should recompile Foo.scala first and then fail to compile Client.scala due to missing -# `Foo.str`; however recompilation of Client.scala is never triggered due to missing -# dependency +# `Foo.str` -> macro-client/compile > clean